2022-02-25 04:58:08 -06:00
|
|
|
import {createReadStream} from 'fs';
|
|
|
|
import {createServer, ServerResponse} from 'http';
|
|
|
|
import {join} from 'path';
|
|
|
|
|
|
|
|
import compression from 'compression';
|
|
|
|
import D from 'debug';
|
|
|
|
import express from 'express';
|
|
|
|
import httpProxy from 'http-proxy';
|
|
|
|
import flatten from 'lodash.flatten';
|
|
|
|
|
|
|
|
import env from './environment';
|
|
|
|
|
|
|
|
const {
|
|
|
|
FLECKS_HTTP_OUTPUT = 'http',
|
|
|
|
FLECKS_ROOT = process.cwd(),
|
|
|
|
NODE_ENV,
|
|
|
|
} = process.env;
|
|
|
|
|
|
|
|
const debug = D('@flecks/http/server/http');
|
|
|
|
|
|
|
|
const deliverHtmlStream = (stream, flecks, req, res) => {
|
|
|
|
flecks.invokeComposed('@flecks/http/server/stream.html', stream, req).pipe(res);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const createHttpServer = async (flecks) => {
|
|
|
|
const {trust} = flecks.get('@flecks/http/server');
|
|
|
|
const {
|
|
|
|
devHost,
|
|
|
|
devPort,
|
|
|
|
host,
|
|
|
|
port,
|
|
|
|
} = env();
|
|
|
|
const app = express();
|
|
|
|
app.set('trust proxy', trust);
|
|
|
|
const httpServer = createServer(app);
|
|
|
|
httpServer.app = app;
|
|
|
|
flecks.set('$flecks/http/server.instance', httpServer);
|
|
|
|
// Compression. heheh
|
|
|
|
app.use(compression({level: 'production' === NODE_ENV ? 6 : 9}));
|
|
|
|
// Socket connection.
|
|
|
|
app.use(flecks.makeMiddleware('@flecks/http/server/request.socket'));
|
|
|
|
// Routes.
|
|
|
|
const routeMiddleware = flecks.makeMiddleware('@flecks/http/server/request.route');
|
|
|
|
const routes = flatten(flecks.invokeFlat('@flecks/http/routes'));
|
|
|
|
routes.forEach(({method, path, middleware}) => app[method](path, routeMiddleware, middleware));
|
|
|
|
// In development mode, create a proxy to the webpack-dev-server.
|
|
|
|
if ('production' !== NODE_ENV) {
|
|
|
|
const proxy = httpProxy.createProxyServer({
|
|
|
|
secure: false,
|
|
|
|
target: `http://${devHost}:${devPort}`,
|
|
|
|
});
|
|
|
|
proxy.on('proxyRes', async (proxyRes, req, res) => {
|
|
|
|
res.statusCode = proxyRes.statusCode;
|
|
|
|
// HTML.
|
|
|
|
if (proxyRes.headers['content-type'].match('text/html')) {
|
2022-02-26 11:58:17 -06:00
|
|
|
// Tests bypass middleware and stream processing.
|
|
|
|
const {pathname} = new URL(req.url, 'https://example.org/');
|
|
|
|
if ('/tests.html' === pathname) {
|
|
|
|
if (!res.headersSent) {
|
|
|
|
res.setHeader('Content-Type', proxyRes.headers['content-type']);
|
|
|
|
}
|
|
|
|
proxyRes.pipe(res);
|
|
|
|
return;
|
|
|
|
}
|
2022-02-25 04:58:08 -06:00
|
|
|
routeMiddleware(req, res, (error) => {
|
|
|
|
if (error) {
|
|
|
|
res.status(error.code || 500).end(error.stack);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!res.headersSent) {
|
|
|
|
res.setHeader('Content-Type', proxyRes.headers['content-type']);
|
|
|
|
}
|
|
|
|
deliverHtmlStream(proxyRes, flecks, req, res);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Any other assets.
|
|
|
|
else {
|
|
|
|
res.setHeader('Content-Type', proxyRes.headers['content-type']);
|
|
|
|
proxyRes.pipe(res);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
proxy.on('error', (error, req, res) => {
|
|
|
|
if (res instanceof ServerResponse) {
|
|
|
|
res.status(502).end('Bad Gateway (WDS)');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
app.all('*', (req, res) => proxy.web(req, res, {selfHandleResponse: true}));
|
|
|
|
httpServer.on('upgrade', (req, socket, head) => proxy.ws(req, socket, head));
|
|
|
|
httpServer.on('close', () => proxy.close());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Serve the document root, sans index.
|
|
|
|
app.use(express.static(join(FLECKS_ROOT, 'dist', FLECKS_HTTP_OUTPUT), {index: false}));
|
2022-02-26 11:58:17 -06:00
|
|
|
// Tests bypass middleware and stream processing.
|
|
|
|
app.get('/tests.html', (req, res) => {
|
|
|
|
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
|
|
|
const stream = createReadStream(join(FLECKS_ROOT, 'dist', FLECKS_HTTP_OUTPUT, 'tests.html'));
|
|
|
|
stream.pipe(res);
|
|
|
|
});
|
2022-02-25 04:58:08 -06:00
|
|
|
// Fallback to serving HTML.
|
|
|
|
app.get('*', routeMiddleware, async (req, res) => {
|
|
|
|
if (req.accepts('text/html')) {
|
|
|
|
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
|
|
|
const stream = createReadStream(join(FLECKS_ROOT, 'dist', FLECKS_HTTP_OUTPUT, 'index.html'));
|
|
|
|
deliverHtmlStream(stream, flecks, req, res);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
res.status(400).end('Bad Request');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const args = [port];
|
|
|
|
if (host) {
|
|
|
|
args.push(host);
|
|
|
|
}
|
|
|
|
args.push(async (error) => {
|
|
|
|
if (error) {
|
|
|
|
reject(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await Promise.all(flecks.invokeFlat('@flecks/http/server/up', httpServer));
|
|
|
|
debug('HTTP server up @ %s!', [host, port].filter((e) => !!e).join(':'));
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
httpServer.listen(...args);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
export const destroyHttpServer = (httpServer) => {
|
|
|
|
if (!httpServer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
httpServer.close();
|
|
|
|
debug('HTTP server down!');
|
|
|
|
};
|