Support SSL, HTTP2

This commit is contained in:
Tobe O 2019-04-28 13:51:08 -04:00
parent d7e96ae616
commit bbf16acaf5
10 changed files with 214 additions and 202230 deletions

View file

@ -22,14 +22,14 @@ import 'dart:isolate';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_production/angel_production.dart';
main(List<String> args) => new Runner('example', configureServer).run(args);
main(List<String> args) => Runner('example', configureServer).run(args);
Future configureServer(Angel app) async {
app.get('/', (req, res) => 'Hello, production world!');
app.get('/crash', (req, res) {
// We'll crash this instance deliberately, but the Runner will auto-respawn for us.
new Timer(const Duration(seconds: 3), Isolate.current.kill);
Timer(const Duration(seconds: 3), Isolate.current.kill);
return 'Crashing in 3s...';
});
}

29
dev.key Normal file
View file

@ -0,0 +1,29 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP
xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE
ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5
Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1
qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc
gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU
0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF
gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS
oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn
oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ
kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh
zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa
J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe
d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX
TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76
ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW
HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN
goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im
EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j
ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS
YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3
q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT
Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z
Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH
QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE
xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w
AUukhVtTNn4=
-----END ENCRYPTED PRIVATE KEY-----

57
dev.pem Normal file
View file

@ -0,0 +1,57 @@
-----BEGIN CERTIFICATE-----
MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa
MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq
Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu
EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki
we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb
N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI
7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg
hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS
YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd
AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4
CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM
4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG
MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5
V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx
EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP
DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE
YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu
MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7
B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd
IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb
oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC
cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8
x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ
e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX
NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4
0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh
FKvRDxsW
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv
dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw
siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj
kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2
hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV
DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU
ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD
26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ
lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X
J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/
uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE
4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k
t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W
r6AL284qtw==
-----END CERTIFICATE-----

View file

@ -4,7 +4,7 @@ import 'package:angel_framework/angel_framework.dart';
import 'package:angel_production/angel_production.dart';
import 'package:pub_sub/pub_sub.dart' as pub_sub;
main(List<String> args) => new Runner('example', configureServer).run(args);
main(List<String> args) => Runner('example', configureServer).run(args);
Future configureServer(Angel app) async {
// Use the injected `pub_sub.Client` to send messages.
@ -22,6 +22,11 @@ Future configureServer(Angel app) async {
// Add some routes...
app.get('/', (req, res) => 'Hello, production world!');
app.get('/404', (req, res) {
res.statusCode = 404;
return res.close();
});
// Create some routes to demonstrate message passing.
app.get('/greeting', (req, res) => greeting);
@ -35,7 +40,7 @@ Future configureServer(Angel app) async {
// The `Runner` helps with fault tolerance.
app.get('/crash', (req, res) {
// We'll crash this instance deliberately, but the Runner will auto-respawn for us.
new Timer(const Duration(seconds: 3), Isolate.current.kill);
Timer(const Duration(seconds: 3), Isolate.current.kill);
return 'Crashing in 3s...';
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:args/args.dart';
class RunnerOptions {
static final ArgParser argParser = new ArgParser()
static final ArgParser argParser = ArgParser()
..addFlag('help',
abbr: 'h', help: 'Print this help information.', negatable: false)
..addFlag('respawn',
@ -11,6 +11,11 @@ class RunnerOptions {
negatable: true)
..addFlag('use-zone',
negatable: false, help: 'Create a new Zone for each request.')
..addFlag('quiet', negatable: false, help: 'Completely mute logging.')
..addFlag('ssl',
negatable: false, help: 'Listen for HTTPS instead of HTTP.')
..addFlag('http2',
negatable: false, help: 'Listen for HTTP/2 instead of HTTP/1.1.')
..addOption('address',
abbr: 'a', defaultsTo: '127.0.0.1', help: 'The address to listen on.')
..addOption('concurrency',
@ -18,26 +23,57 @@ class RunnerOptions {
defaultsTo: Platform.numberOfProcessors.toString(),
help: 'The number of isolates to spawn.')
..addOption('port',
abbr: 'p', defaultsTo: '3000', help: 'The port to listen on.');
abbr: 'p', defaultsTo: '3000', help: 'The port to listen on.')
..addOption('certificate-file', help: 'The PEM certificate file to read.')
..addOption('certificate-password',
help: 'The PEM certificate file password.')
..addOption('key-file', help: 'The PEM key file to read.')
..addOption('key-password', help: 'The PEM key file password.');
final String hostname;
final String hostname,
certificateFile,
keyFile,
certificatePassword,
keyPassword;
final int concurrency, port;
final bool useZone, respawn;
final bool useZone, respawn, quiet, ssl, http2;
RunnerOptions(
{this.hostname = '127.0.0.1',
this.port = 3000,
this.concurrency = 1,
this.useZone = false,
this.respawn = true});
this.respawn = true,
this.quiet = false,
this.certificateFile,
this.keyFile,
this.ssl = false,
this.http2 = false,
this.certificatePassword,
this.keyPassword});
factory RunnerOptions.fromArgResults(ArgResults argResults) {
return new RunnerOptions(
return RunnerOptions(
hostname: argResults['address'] as String,
port: int.parse(argResults['port'] as String),
concurrency: int.parse(argResults['concurrency'] as String),
useZone: argResults['use-zone'] as bool,
respawn: argResults['respawn'] as bool,
quiet: argResults['quiet'] as bool,
certificateFile: argResults.wasParsed('certificate-file')
? argResults['certificate-file'] as String
: null,
keyFile: argResults.wasParsed('key-file')
? argResults['key-file'] as String
: null,
ssl: argResults['ssl'] as bool,
http2: argResults['http2'] as bool,
certificatePassword: argResults.wasParsed('certificate-password')
? argResults['certificate-password'] as String
: null,
keyPassword: argResults.wasParsed('key-password')
? argResults['key-password'] as String
: null,
);
}
}

View file

@ -4,6 +4,7 @@ import 'dart:isolate';
import 'package:angel_container/angel_container.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_framework/http2.dart';
import 'package:args/args.dart';
import 'package:io/ansi.dart';
import 'package:io/io.dart';
@ -73,17 +74,17 @@ _ ___ | /| / / /_/ / _ /___ _ /___
///
/// If respawning is enabled, the [Future] will *never* complete.
Future spawnIsolate(int id, RunnerOptions options, SendPort pubSubSendPort) {
return _spawnIsolate(id, new Completer(), options, pubSubSendPort);
return _spawnIsolate(id, Completer(), options, pubSubSendPort);
}
Future _spawnIsolate(
int id, Completer c, RunnerOptions options, SendPort pubSubSendPort) {
var onLogRecord = new ReceivePort();
var onExit = new ReceivePort();
var onError = new ReceivePort();
var runnerArgs = new _RunnerArgs(name, configureServer, options, reflector,
var onLogRecord = ReceivePort();
var onExit = ReceivePort();
var onError = ReceivePort();
var runnerArgs = _RunnerArgs(name, configureServer, options, reflector,
onLogRecord.sendPort, pubSubSendPort);
var argsWithId = new _RunnerArgsWithId(id, runnerArgs);
var argsWithId = _RunnerArgsWithId(id, runnerArgs);
Isolate.spawn(isolateMain, argsWithId,
onExit: onExit.sendPort,
@ -97,19 +98,19 @@ _ ___ | /| / / /_/ / _ /___ _ /___
onError.listen((msg) {
if (msg is List) {
var e = msg[0], st = StackTrace.fromString(msg[1].toString());
handleLogRecord(new LogRecord(
handleLogRecord(LogRecord(
Level.SEVERE, 'Fatal error', runnerArgs.loggerName, e, st));
} else {
handleLogRecord(new LogRecord(
Level.SEVERE, 'Fatal error', runnerArgs.loggerName, msg));
handleLogRecord(
LogRecord(Level.SEVERE, 'Fatal error', runnerArgs.loggerName, msg));
}
});
onExit.listen((_) {
if (options.respawn) {
handleLogRecord(new LogRecord(
handleLogRecord(LogRecord(
Level.WARNING,
'Instance #$id at ${new DateTime.now()}. Respawning immediately...',
'Instance #$id at ${DateTime.now()} crashed. Respawning immediately...',
runnerArgs.loggerName));
_spawnIsolate(id, c, options, pubSubSendPort);
} else {
@ -129,7 +130,15 @@ _ ___ | /| / / /_/ / _ /___ _ /___
try {
var argResults = RunnerOptions.argParser.parse(args);
var options = new RunnerOptions.fromArgResults(argResults);
var options = RunnerOptions.fromArgResults(argResults);
if (options.ssl || options.http2) {
if (options.certificateFile == null) {
throw ArgParserException('Missing --certificate-file option.');
} else if (options.keyFile == null) {
throw ArgParserException('Missing --key-file option.');
}
}
print(darkGray.wrap(asciiArt.trim() +
'\n\n' +
@ -143,29 +152,30 @@ _ ___ | /| / / /_/ / _ /___ _ /___
}
print('Starting `${name}` application...');
print('Arguments: $args...\n');
var adapter = new pub_sub.IsolateAdapter();
server = new pub_sub.Server([adapter]);
var adapter = pub_sub.IsolateAdapter();
server = pub_sub.Server([adapter]);
// Register clients
for (int i = 0; i < Platform.numberOfProcessors; i++) {
server.registerClient(new pub_sub.ClientInfo('client$i'));
server.registerClient(pub_sub.ClientInfo('client$i'));
}
server.start();
await Future.wait(new List.generate(options.concurrency,
await Future.wait(List.generate(options.concurrency,
(id) => spawnIsolate(id, options, adapter.receivePort.sendPort)));
} on ArgParserException catch (e) {
stderr
..writeln(e.message)
..writeln(red.wrap(e.message))
..writeln()
..writeln('Options:')
..writeln(RunnerOptions.argParser.usage);
..writeln(red.wrap('Options:'))
..writeln(red.wrap(RunnerOptions.argParser.usage));
exitCode = ExitCode.usage.code;
} catch (e) {
stderr..writeln('fatal error: $e');
} catch (e, st) {
stderr
..writeln(red.wrap('fatal error: $e'))
..writeln(red.wrap(st.toString()));
exitCode = 1;
} finally {
await server?.close();
@ -176,37 +186,66 @@ _ ___ | /| / / /_/ / _ /___ _ /___
var args = argsWithId.args;
hierarchicalLoggingEnabled = true;
var zone = Zone.current.fork(specification: new ZoneSpecification(
var zone = Zone.current.fork(specification: ZoneSpecification(
print: (self, parent, zone, msg) {
args.loggingSendPort
.send(new LogRecord(Level.INFO, msg, args.loggerName));
args.loggingSendPort.send(LogRecord(Level.INFO, msg, args.loggerName));
},
));
zone.run(() async {
var client = new pub_sub.IsolateClient(
'client${argsWithId.id}', args.pubSubSendPort);
var client =
pub_sub.IsolateClient('client${argsWithId.id}', args.pubSubSendPort);
var app = new Angel(reflector: args.reflector)
var app = Angel(reflector: args.reflector)
..container.registerSingleton<pub_sub.Client>(client)
..container.registerSingleton(new InstanceInfo(id: argsWithId.id));
..container.registerSingleton(InstanceInfo(id: argsWithId.id));
app.shutdownHooks.add((_) => client.close());
await app.configure(args.configureServer);
if (app.logger == null) {
app.logger = new Logger(args.loggerName)
app.logger = Logger(args.loggerName)
..onRecord.listen(Runner.handleLogRecord);
}
var http =
new AngelHttp.custom(app, startShared, useZone: args.options.useZone);
var server =
await http.startServer(args.options.hostname, args.options.port);
var url = new Uri(
scheme: 'http', host: server.address.address, port: server.port);
print('Instance #${argsWithId.id} listening at $url');
AngelHttp http;
SecurityContext securityContext;
Uri serverUrl;
if (args.options.ssl || args.options.http2) {
securityContext = SecurityContext();
securityContext.useCertificateChain(args.options.certificateFile,
password: args.options.certificatePassword);
securityContext.usePrivateKey(args.options.keyFile,
password: args.options.keyPassword);
}
if (args.options.ssl) {
http = AngelHttp.custom(app, startSharedSecure(securityContext),
useZone: args.options.useZone);
} else {
http =
AngelHttp.custom(app, startShared, useZone: args.options.useZone);
}
Driver driver;
if (args.options.http2) {
securityContext.setAlpnProtocols(['h2'], true);
var http2 = AngelHttp2.custom(app, securityContext, startSharedHttp2,
useZone: args.options.useZone);
http2.onHttp1.listen(http.handleRequest);
driver = http2;
} else {
driver = http;
}
await driver.startServer(args.options.hostname, args.options.port);
serverUrl = driver.uri;
if (args.options.ssl || args.options.http2)
serverUrl = serverUrl.replace(scheme: 'https');
print('Instance #${argsWithId.id} listening at $serverUrl');
});
}
}

View file

@ -13,4 +13,4 @@ dependencies:
logging: ^0.11.3
pub_sub: ^2.0.0
dev_dependencies:
pedantic: ^1.0.0
pedantic: ^1.0.0