UNPKG

@web/dev-server-core

Version:
139 lines (122 loc) 4.43 kB
import Koa from 'koa'; import path from 'path'; import { FSWatcher } from 'chokidar'; import httpServer, { IncomingMessage, ServerResponse } from 'http'; import http2Server from 'http2'; import fs from 'fs'; import net, { Server, Socket, ListenOptions } from 'net'; import { DevServerCoreConfig } from './DevServerCoreConfig.js'; import { createMiddleware } from './createMiddleware.js'; import { Logger } from '../logger/Logger.js'; import { addPlugins } from './addPlugins.js'; /** * A request handler that returns a 301 HTTP Redirect to the same location as the original * request but using the https protocol */ function httpsRedirect(req: IncomingMessage, res: ServerResponse) { const { host } = req.headers; res.writeHead(301, { Location: `https://${host}${req.url}` }); res.end(); } /** * Creates a koa server with middlewares, but does not start it. Returns the koa app and * http server instances. */ export function createServer( logger: Logger, cfg: DevServerCoreConfig, fileWatcher: FSWatcher, middlewareMode = false, ) { const app = new Koa(); app.silent = true; app.on('error', error => { if (['EPIPE', 'ECONNRESET', 'ERR_STREAM_PREMATURE_CLOSE'].includes(error.code)) { return; } console.error('Error while handling server request.'); console.error(error); }); addPlugins(logger, cfg); // special case the legacy plugin, if it is given make sure the resolve module imports plugin // runs before the legacy plugin because it compiles away module syntax. ideally we have a // generic API for this, but we need to design that a bit more first const indexOfLegacy = cfg.plugins!.findIndex(p => p.name === 'legacy'); let indexOfResolve = cfg.plugins!.findIndex(p => p.name === 'resolve-module-imports'); if (indexOfLegacy !== -1 && indexOfResolve !== -1) { const legacy = cfg.plugins!.splice(indexOfLegacy, 1)[0]; // recompute after splicing indexOfResolve = cfg.plugins!.findIndex(p => p.name === 'resolve-module-imports'); cfg.plugins!.splice(indexOfResolve, 1, cfg.plugins![indexOfResolve], legacy); } const middleware = createMiddleware(cfg, logger, fileWatcher); for (const m of middleware) { app.use(m); } if (middlewareMode) { return { app }; } let server: Server; if (cfg.http2) { const dir = path.join(__dirname, '..'); const options = { key: fs.readFileSync( cfg.sslKey ? path.resolve(cfg.sslKey) : path.join(dir, '..', '.self-signed-dev-server-ssl.key'), ), cert: fs.readFileSync( cfg.sslCert ? path.resolve(cfg.sslCert) : path.join(dir, '..', '.self-signed-dev-server-ssl.cert'), ), allowHTTP1: true, maxSessionMemory: 20, }; const httpsRedirectServer = httpServer.createServer(httpsRedirect); server = http2Server.createSecureServer(options, app.callback()); let appServerPort: number; let httpsRedirectServerPort: number; /** * A connection handler that checks if the connection is using TLS */ const httpRedirectProxy = (socket: Socket) => { socket.once('data', buffer => { // A TLS handshake record starts with byte 22. const address = buffer[0] === 22 ? appServerPort : httpsRedirectServerPort; const proxy = (net as any).createConnection(address, () => { proxy.write(buffer); socket.pipe(proxy).pipe(socket); }); }); }; const wrapperServer = net.createServer(httpRedirectProxy); wrapperServer.addListener('close', () => { httpsRedirectServer.close(); server.close(); }); wrapperServer.addListener('listening', () => { const info = server.address(); if (!info || typeof info === 'string') { return; } const { address, port } = info; appServerPort = port + 1; httpsRedirectServerPort = port + 2; server.listen({ address, port: appServerPort }); httpsRedirectServer.listen({ address, port: httpsRedirectServerPort }); }); const serverListen = wrapperServer.listen.bind(wrapperServer); (wrapperServer as any).listen = (config: ListenOptions, callback: () => void) => { server.addListener('listening', callback); serverListen(config); return server; }; } else { server = httpServer.createServer(app.callback()); } return { server, app, }; }