Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/dev-server-https-http1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@web/dev-server-core': minor
'@web/dev-server': minor
---

Add an `https` option to serve over HTTPS with HTTP/1.1, without enabling HTTP/2. It uses `sslKey`/`sslCert`, falling back to the bundled self-signed certificate like `http2` does.
2 changes: 2 additions & 0 deletions docs/docs/dev-server/cli-and-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ interface DevServerConfig {

// whether to run the server with HTTP2
http2?: boolean;
// whether to run the server with HTTPS over HTTP/1.1, ignored when http2 is set
https?: boolean;
// path to SSL key
sslKey?: string;
// path to SSL certificate
Expand Down
4 changes: 4 additions & 0 deletions packages/dev-server-core/src/server/DevServerCoreConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export interface DevServerCoreConfig {
* Whether to run the server with HTTP2
*/
http2?: boolean;
/**
* Whether to run the server with HTTPS over HTTP/1.1. Ignored when http2 is set.
*/
https?: boolean;
/**
* Path to SSL key
*/
Expand Down
12 changes: 8 additions & 4 deletions packages/dev-server-core/src/server/createServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FSWatcher } from 'chokidar';
import fs from 'fs';
import httpServer, { IncomingMessage, ServerResponse } from 'http';
import http2Server from 'http2';
import httpsServer from 'https';
import Koa from 'koa';
import net, { ListenOptions, Server, Socket } from 'net';
import path from 'path';
Expand Down Expand Up @@ -65,7 +66,7 @@ export function createServer(
}

let server: Server;
if (cfg.http2) {
if (cfg.http2 || cfg.https) {
const dir = path.join(__dirname, '..');
const options = {
key: fs.readFileSync(
Expand All @@ -78,12 +79,15 @@ export function createServer(
? 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());
server = cfg.http2
? http2Server.createSecureServer(
{ ...options, allowHTTP1: true, maxSessionMemory: 20 },
app.callback(),
)
: httpsServer.createServer(options, app.callback());
let appServerPort: number;
let httpsRedirectServerPort: number;

Expand Down
2 changes: 1 addition & 1 deletion packages/dev-server-core/src/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function createTestServer(
await server.start();

const url = new URL('http://localhost');
url.protocol = config.http2 ? 'https' : 'http';
url.protocol = config.http2 || config.https ? 'https' : 'http';
url.port = port.toString();
return { server, port, host: url.toString().slice(0, -1) };
}
31 changes: 31 additions & 0 deletions packages/dev-server-core/test/server/DevServer.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import express from 'express';
import http from 'http';
import https from 'https';
import Koa from 'koa';
import { Server } from 'net';
import assert from 'node:assert/strict';
import { after, afterEach, before, beforeEach, describe, it, mock } from 'node:test';
import portfinder from 'portfinder';
import type { TLSSocket } from 'tls';
import { assertIncludes } from '../../../../test-helpers/node.js';
import type { ServerStartParams } from '../../dist/plugins/Plugin.js';
import type { DevServer } from '../../dist/server/DevServer.js';
Expand Down Expand Up @@ -90,6 +92,35 @@ describe('http2', () => {
});
});

describe('https', () => {
it('serves a website over HTTP/1.1 with TLS', async () => {
const { server, host } = await createTestServer({ hostname: 'localhost', https: true });
assert.ok(host.startsWith('https://'));

const { res, body, encrypted } = await new Promise<{
res: http.IncomingMessage;
body: string;
encrypted: boolean;
}>((resolve, reject) => {
https
.get(`${host}/index.html`, { rejectUnauthorized: false }, res => {
const { encrypted } = res.socket as TLSSocket;
let body = '';
res.setEncoding('utf8');
res.on('data', chunk => (body += chunk));
res.on('end', () => resolve({ res, body, encrypted }));
})
.on('error', reject);
});

assert.ok(encrypted);
assert.equal(res.httpVersion, '1.1');
assert.equal(res.statusCode, 200);
assertIncludes(body, '<title>My app</title>');
server.stop();
});
});

it('can run in middleware mode', async () => {
const { server: wdsServer } = await createTestServer({ middlewareMode: true });
assert.equal(wdsServer.server, undefined);
Expand Down
2 changes: 1 addition & 1 deletion packages/dev-server/src/config/parseConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function validate(config: Record<string, unknown>, key: string, type: string) {

const stringSettings = ['rootDir', 'hostname', 'basePath', 'appIndex', 'sslKey', 'sslCert'];
const numberSettings = ['port'];
const booleanSettings = ['watch', 'preserveSymlinks', 'http2', 'eventStream'];
const booleanSettings = ['watch', 'preserveSymlinks', 'http2', 'https', 'eventStream'];

export function validateConfig(config: Partial<DevServerConfig>) {
stringSettings.forEach(key => validate(config, key, 'string'));
Expand Down
2 changes: 1 addition & 1 deletion packages/dev-server/src/logger/logStartMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { bold, cyan, white } from 'nanocolors';
import { type DevServerConfig } from '../config/DevServerConfig';

const createAddress = (config: DevServerConfig, host: string, path: string) =>
`http${config.http2 ? 's' : ''}://${host}:${config.port}${path}`;
`http${config.http2 || config.https ? 's' : ''}://${host}:${config.port}${path}`;

function logNetworkAddress(config: DevServerConfig, logger: Logger, openPath: string) {
try {
Expand Down
2 changes: 1 addition & 1 deletion packages/dev-server/src/openBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function openBrowser(config: DevServerConfig) {
// construct a full URL to open if the user didn't provide a full URL
openPath = new URL(
openPath,
`http${config.http2 ? 's' : ''}://${config.hostname}:${config.port}`,
`http${config.http2 || config.https ? 's' : ''}://${config.hostname}:${config.port}`,
).href;
}

Expand Down