Skip to content

Support custom hostname and TLS options #2588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 4, 2025
28 changes: 28 additions & 0 deletions fixtures/_experimentSound/localhost+2-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDR8FlEhQ8XZctL
mGdx78Z5EInK2mcfU9Nq4oUKkPeiql7Sd3cd3prd+m5MYylL255wAZf8Fdupkko2
cjQEsU24jUBWVG5q3YqZ+OidFYdxYnPbDhFtzL3Lr2zHGAi+MgqVENUF6ED2O5tU
9U1crceXmXHb3E8Sg+zXX6TMVCS11OH9VQ1ZutM0fFMPZxmfU5aOfd5kuTmTiaOt
OMyymroUPgzRW6UcbLfSdD7KuNFQgHDHiWkr2MtoZffgGM/cOfmCdgE85MdL6kyb
7TUTbAlTSpJNWwygObce7DN7qwM8HocRWSAfH+y3ewbs2NtzG1Az+a/kUBjhvmjT
WFiwDT+1AgMBAAECggEBALkJMXTeHh4OP2+ipVJb9r/P3tMnSornFEl5268jdNAv
f6HbX+a4xCDwUHUNVWGh8XRhQzcRgOllofl5EPYt3AXUoac1hZi1KStqooOJbTZ3
gwvIy33OXl5/gM2+Fj6k1oTqMAej3FXq1Y69InGUTX4F5b/V3u+/zWlKyHK7mxuT
LIP3/iAxbpST5FB1G7ZPyz5mvzvEQFRiv9ubuMxYg3fzORULtbnnHNAM5jLG3IzZ
9EM/1umaeZu1bg9rGVVgr0l/rWMJSg5u+Z6jcrt9+Oj7hYrMPGHKHXAPhC1gaq+k
tlKHj/mhmJyN0jUXt71UgrsWF3MhrK2W2AlIfxXhIIECgYEA8oaIEOaeNmtDqlH5
MmU0jd2EcMRnLrlfKs4fKObFoSMqELRB3yJ1u5Yt/wQCCSpN9bhkinR0l4Q7YsAy
4sg5Q3VaA/rUWkVKkv2sD04LbBmpHr5vksPnIPKcfAY3csQ1F5pu9gY6zgzdxBvN
nWEu8tqbUSdC5+0F+tB8IDW+/lUCgYEA3ZpVXmBLvgeZJ8rNjCCeKR5hXMYdFKqC
Yd5KfasTKH7h8a4GWo/mtUsS5UPRRjhrP5mPjdUKBFhSaJtX+k9Hhot58RN20fWR
4Ake4yV/cNFRykD1jsv9JJiPkggnVLVXN91+/EnKJozWv46q6nG44iX+q2w2i9FQ
6lohgwQp2+ECgYEAwKJY+0uiiUkD0woPbJb0emZj5wophvRYgfB80YkTmt0KcYAr
/icp6pjr6e3uDAedKrqOqWa8oQi3/sT45ibxTQKuQBEAkL8O79grzXBJJFDxgujy
SFnwgLwTzXNGoZL1NM1Gq4XhOX8Aut72n7Xsi5tV2MzdmMgsgr8MiK0ICo0CgYBA
h4yMauYjc/r5R2kLgQQNXTdk2JvnRK+q6Bww8/wkMq6AvfhDrtuztyTNdi4ekJdK
ceEHoB3GniGBLJs13JgrabocpVpYUXYlEwLXijfOFmYGy1u2NViFq5dDIvSxCg1X
yzwLI0GmcCSoq1bB5lO8Juw95skLdexmEdDoYfH+gQKBgDgaFlF9lYuKd0HhAxxm
oJ8WBv26FWxR8/UsvUPqI8Mzmb8J1Q22tZEMEVr8bN/HkcL+8DVQ4uh2zx9+rqC0
aPqJZNgePGpRDhZ/cwA8xsg+Xkou8rrFjpGRuVuG9FvMEexufkHoDc94ppONuwsR
QzjTUxgXdLjtxT95ISwX83Nr
-----END PRIVATE KEY-----
27 changes: 27 additions & 0 deletions fixtures/_experimentSound/localhost+2.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEgjCCAuqgAwIBAgIQBeTmjjlnyrCQUy06Xx/ybzANBgkqhkiG9w0BAQsFADCB
pTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT0wOwYDVQQLDDRMQVBU
T1AtQUFSSFFPMkxcV2Rlc3RATEFQVE9QLUFBUkhRTzJMIChXYWduZXIgRC4gRi4p
MUQwQgYDVQQDDDtta2NlcnQgTEFQVE9QLUFBUkhRTzJMXFdkZXN0QExBUFRPUC1B
QVJIUU8yTCAoV2FnbmVyIEQuIEYuKTAeFw0yNTAyMTQxNDI2MjFaFw0yNzA1MTQx
NDI2MjFaMGgxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0
ZTE9MDsGA1UECww0TEFQVE9QLUFBUkhRTzJMXFdkZXN0QExBUFRPUC1BQVJIUU8y
TCAoV2FnbmVyIEQuIEYuKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANHwWUSFDxdly0uYZ3HvxnkQicraZx9T02rihQqQ96KqXtJ3dx3emt36bkxjKUvb
nnABl/wV26mSSjZyNASxTbiNQFZUbmrdipn46J0Vh3Fic9sOEW3MvcuvbMcYCL4y
CpUQ1QXoQPY7m1T1TVytx5eZcdvcTxKD7NdfpMxUJLXU4f1VDVm60zR8Uw9nGZ9T
lo593mS5OZOJo604zLKauhQ+DNFbpRxst9J0Psq40VCAcMeJaSvYy2hl9+AYz9w5
+YJ2ATzkx0vqTJvtNRNsCVNKkk1bDKA5tx7sM3urAzwehxFZIB8f7Ld7BuzY23Mb
UDP5r+RQGOG+aNNYWLANP7UCAwEAAaNqMGgwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFLmYo+uZjtNZbL0jyR7XVnK/FICG
MCAGA1UdEQQZMBeCCWxvY2FsaG9zdIcEfwAAAYcEAAAAADANBgkqhkiG9w0BAQsF
AAOCAYEAbUhcggR4p8n/Us+ov8zvI+hBIrDi7GevWQQKrU1Rp2gzHV6glhqGMQ1A
BkGlj56L9O4P02awFZzB/55d2CsufzMD+d4aMKpdIJivwXByg6fJCtPOIprAiGeR
GuE9Q9ceUUJVrTYiy5CZeKTIQp4ZqWu5/wBQ+yvbsvxnzq2ESDpGmCkY6/ToMWmu
5cZPbZcyF7XBybSBdvaMGFsthRpawvQMsGHDZBaVhLn099Hjx2p35jNiM+HqCQXn
NweLYMJx9FZtOYyNJoTj0w97ViaSubz7V9n7LVIxW1QXmDXAk+75Fwq3x+n/tfhe
1BTLEOKvARvRVB6M1EE0z+zeNusHIu/TUMSmGv59OhsQdW5Lp1D0o3JK2mHu6tgP
+v1XRMaTQq73oHqU0oXCtsEdPuoFBNREBDhBOGB5wtFjfniu1zD0cKSDbVslSD5B
LKu9Yi1PzZTWVkaOWQolA6sYwWFLhQbHoyQoNjAJz58VNNtYIMjR6fZ0xTYiPQpJ
4Jh3TJKU
-----END CERTIFICATE-----
3 changes: 2 additions & 1 deletion webdev/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 3.7.2-wip

- Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483)
- Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483).
- Support the `--hostname` flag when the `--tls-cert-key` and `--tls-cert-chain` flags are present [#2588](https://github.com/dart-lang/webdev/pull/2588).

## 3.7.1

Expand Down
4 changes: 2 additions & 2 deletions webdev/lib/src/serve/webdev_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ class WebDevServer {
final serverContext = SecurityContext()
..useCertificateChain(tlsCertChain)
..usePrivateKey(tlsCertKey);
server =
await HttpMultiServer.loopbackSecure(options.port, serverContext);
server = await HttpMultiServer.bindSecure(
hostname, options.port, serverContext);
} else {
server = await HttpMultiServer.bind(hostname, options.port);
}
Expand Down
117 changes: 117 additions & 0 deletions webdev/test/tls_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';

import 'package:collection/collection.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:test_process/test_process.dart';

import 'package:webdev/src/logging.dart';
import 'package:webdev/src/serve/utils.dart';

import 'test_utils.dart';

void main() {
group('serve app with TLS options', () {
// Change to true for debugging.
final debug = false;

final testRunner = TestRunner();
late String exampleDirectory;

setUpAll(() async {
configureLogWriter(debug);
await testRunner.setUpAll();
exampleDirectory =
p.absolute(p.join(p.current, '..', 'fixtures', '_experimentSound'));

final process = await TestProcess.start(
'dart',
['pub', 'upgrade'],
workingDirectory: exampleDirectory,
environment: getPubEnvironment(),
);

await process.shouldExit(0);

await d
.file('.dart_tool/package_config.json', isNotEmpty)
.validate(exampleDirectory);
await d.file('pubspec.lock', isNotEmpty).validate(exampleDirectory);
});

test('listens on a loopback interface', () async {
final port = await findUnusedPort();
final args = [
'serve',
'web:$port',
'--hostname=0.0.0.0',
'--tls-cert-chain=localhost+2.pem',
'--tls-cert-key=localhost+2-key.pem',
];

final process =
await testRunner.runWebDev(args, workingDirectory: exampleDirectory);
await expectLater(process.stdout, emitsThrough(contains('Succeeded')));

final client = HttpClient()
..badCertificateCallback = (_, __, ___) => true;
try {
final request =
await client.getUrl(Uri.parse('https://localhost:$port'));
final response = await request.close();
expect(response.statusCode, equals(200));
} finally {
client.close(force: true);
}

await process.kill();
await process.shouldExit();
});

test('listens on a non-loopback interface', () async {
final port = await findUnusedPort();
final args = [
'serve',
'web:$port',
'--hostname=0.0.0.0',
'--tls-cert-chain=localhost+2.pem',
'--tls-cert-key=localhost+2-key.pem',
];

final process =
await testRunner.runWebDev(args, workingDirectory: exampleDirectory);
await expectLater(process.stdout, emitsThrough(contains('Succeeded')));

final interfaces = await NetworkInterface.list(
type: InternetAddressType.IPv4,
includeLoopback: false,
);
final nonLoopback = interfaces.expand((i) => i.addresses).firstOrNull;

if (nonLoopback == null) {
Logger.root.info(
'No non-loopback IPv4 address available, skipping hostname test.');
} else {
final client = HttpClient()
..badCertificateCallback = (_, __, ___) => true;
try {
final request = await client
.getUrl(Uri.parse('https://${nonLoopback.address}:$port'));
final response = await request.close();
expect(response.statusCode, equals(200));
} finally {
client.close(force: true);
}
}

await process.kill();
await process.shouldExit();
});
});
}
Loading