Add 'packages/production/' from commit '5ac875c504cd98a5075e0316da9c153b313fd3f9'

git-subtree-dir: packages/production
git-subtree-mainline: c97363d290
git-subtree-split: 5ac875c504
This commit is contained in:
Tobe O 2020-02-15 18:28:31 -05:00
commit cf2ad35d2b
14 changed files with 680 additions and 0 deletions

13
packages/production/.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
.dart_tool/
.packages
.pub/
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/

View file

@ -0,0 +1,9 @@
# 1.0.0
* Support SSL/HTTP2.
* Support muting the logger via `--quiet`.
# 1.0.0-alpha.1
* Import `framework/http`.
# 1.0.0-alpha
* Initial version.

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 The Angel Framework
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,116 @@
# production
[![Pub](https://img.shields.io/pub/v/angel_production.svg)](https://pub.dartlang.org/packages/angel_production)
Helpers for concurrency, message-passing, rotating loggers, and other production functionality in Angel.
![Screenshot](screenshot.png)
This will become the de-facto way to run Angel applications in deployed environments, as it
takes care of inter-isolate communication, respawning dead processes, and other housekeeping for you automatically.
Most users will want to use the `Runner` class.
## `Runner`
`Runner` is a utility, powered by `package:args`, that is intended to be the entry point of your application.
Instantiate it as follows, and your file will become a command-line executable that spawns multiple instances of your
application:
```dart
import 'dart:async';
import 'dart:isolate';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_production/angel_production.dart';
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.
Timer(const Duration(seconds: 3), Isolate.current.kill);
return 'Crashing in 3s...';
});
}
```
`Runner` will automatically re-spawn crashed instances, unless `--no-respawn` is passed. This can prevent
your server from entirely going down at the first error, and adds a layer of fault tolerance to your
infrastructure.
When combined with `systemd`, deploying Angel applications on Linux can be very simple.
## Message Passing
The `Runner` class uses [`package:pub_sub`](https://github.com/thosakwe/pub_sub) to coordinate
message passing between isolates.
When one isolate sends a message, all other isolates will
receive the same message, except for the isolate that sent it.
It is injected into your application's `Container` as
`pub_sub.Client`, so you can use it as follows:
```dart
// Use the injected `pub_sub.Client` to send messages.
var client = app.container.make<pub_sub.Client>();
// We can listen for an event to perform some behavior.
//
// Here, we use message passing to synchronize some common state.
var onGreetingChanged = await client.subscribe('user_upgraded');
onGreetingChanged
.cast<User>()
.listen((user) {
// Do something...
});
```
## Run-time Metadata
At run-time, you may want to know information about the currently-running instance,
for example, which number instance. For this, the `InstanceInfo` class is injected
into each instance:
```dart
var instanceInfo = app.container.make<InstanceInfo>();
print('This is instance #${instanceInfo.id}');
```
## Command-line Options
The `Runner` class supplies options like the following:
```
Tobes-MacBook-Air:production thosakwe$ dart example/main.dart --help
____________ ________________________
___ |__ | / /_ ____/__ ____/__ /
__ /| |_ |/ /_ / __ __ __/ __ /
_ ___ | /| / / /_/ / _ /___ _ /___
/_/ |_/_/ |_/ ____/ /_____/ /_____/
A batteries-included, full-featured, full-stack framework in Dart.
https://angel-dart.github.io
Options:
-h, --help Print this help information.
--[no-]respawn Automatically respawn crashed application instances.
(defaults to on)
--use-zone Create a new Zone for each request.
--quiet Completely mute logging.
--ssl Listen for HTTPS instead of HTTP.
--http2 Listen for HTTP/2 instead of HTTP/1.1.
-a, --address The address to listen on.
(defaults to "127.0.0.1")
-j, --concurrency The number of isolates to spawn.
(defaults to "4")
-p, --port The port to listen on.
(defaults to "3000")
--certificate-file The PEM certificate file to read.
--certificate-password The PEM certificate file password.
--key-file The PEM key file to read.
--key-password The PEM key file password.
```

View file

@ -0,0 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

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-----

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

@ -0,0 +1,46 @@
import 'dart:async';
import 'dart:isolate';
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) => Runner('example', configureServer).run(args);
Future configureServer(Angel app) async {
// Use the injected `pub_sub.Client` to send messages.
var client = app.container.make<pub_sub.Client>();
var greeting = 'Hello! This is the default greeting.';
// We can listen for an event to perform some behavior.
//
// Here, we use message passing to synchronize some common state.
var onGreetingChanged = await client.subscribe('greeting_changed');
onGreetingChanged
.cast<String>()
.listen((newGreeting) => greeting = newGreeting);
// 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);
// This route will push a new value for `greeting`.
app.get('/change_greeting/:newGreeting', (req, res) {
greeting = req.params['newGreeting'] as String;
client.publish('greeting_changed', greeting);
return 'Changed greeting -> $greeting';
});
// 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.
Timer(const Duration(seconds: 3), Isolate.current.kill);
return 'Crashing in 3s...';
});
}

View file

@ -0,0 +1,3 @@
export 'src/instance_info.dart';
export 'src/options.dart';
export 'src/runner.dart';

View file

@ -0,0 +1,6 @@
/// Information about the currently-running instance.
class InstanceInfo {
final int id;
const InstanceInfo({this.id});
}

View file

@ -0,0 +1,79 @@
import 'dart:io';
import 'package:args/args.dart';
class RunnerOptions {
static final ArgParser argParser = ArgParser()
..addFlag('help',
abbr: 'h', help: 'Print this help information.', negatable: false)
..addFlag('respawn',
help: 'Automatically respawn crashed application instances.',
defaultsTo: true,
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',
abbr: 'j',
defaultsTo: Platform.numberOfProcessors.toString(),
help: 'The number of isolates to spawn.')
..addOption('port',
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,
certificateFile,
keyFile,
certificatePassword,
keyPassword;
final int concurrency, port;
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.quiet = false,
this.certificateFile,
this.keyFile,
this.ssl = false,
this.http2 = false,
this.certificatePassword,
this.keyPassword});
factory RunnerOptions.fromArgResults(ArgResults argResults) {
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

@ -0,0 +1,281 @@
import 'dart:async';
import 'dart:io';
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';
import 'package:logging/logging.dart';
import 'package:pub_sub/isolate.dart' as pub_sub;
import 'package:pub_sub/pub_sub.dart' as pub_sub;
import 'instance_info.dart';
import 'options.dart';
/// A command-line utility for easier running of multiple instances of an Angel application.
///
/// Makes it easy to do things like configure SSL, log messages, and send messages between
/// all running instances.
class Runner {
final String name;
final AngelConfigurer configureServer;
final Reflector reflector;
Runner(this.name, this.configureServer,
{this.reflector = const EmptyReflector()});
static const String asciiArt = '''
____________ ________________________
___ |__ | / /_ ____/__ ____/__ /
__ /| |_ |/ /_ / __ __ __/ __ /
_ ___ | /| / / /_/ / _ /___ _ /___
/_/ |_/_/ |_/ \____/ /_____/ /_____/
''';
static void handleLogRecord(LogRecord record, RunnerOptions options) {
if (options.quiet) return;
var code = chooseLogColor(record.level);
if (record.error == null) print(code.wrap(record.toString()));
if (record.error != null) {
var err = record.error;
if (err is AngelHttpException && err.statusCode != 500) return;
print(code.wrap(record.toString() + '\n'));
print(code.wrap(err.toString()));
if (record.stackTrace != null) {
print(code.wrap(record.stackTrace.toString()));
}
}
}
/// Chooses a color based on the logger [level].
static AnsiCode chooseLogColor(Level level) {
if (level == Level.SHOUT)
return backgroundRed;
else if (level == Level.SEVERE)
return red;
else if (level == Level.WARNING)
return yellow;
else if (level == Level.INFO)
return cyan;
else if (level == Level.FINER || level == Level.FINEST) return lightGray;
return resetAll;
}
/// Spawns a new instance of the application in a separate isolate.
///
/// If the command-line arguments permit, then the instance will be respawned on crashes.
///
/// The returned [Future] completes when the application instance exits.
///
/// If respawning is enabled, the [Future] will *never* complete.
Future spawnIsolate(int id, RunnerOptions options, SendPort pubSubSendPort) {
return _spawnIsolate(id, Completer(), options, pubSubSendPort);
}
Future _spawnIsolate(
int id, Completer c, RunnerOptions options, SendPort pubSubSendPort) {
var onLogRecord = ReceivePort();
var onExit = ReceivePort();
var onError = ReceivePort();
var runnerArgs = _RunnerArgs(name, configureServer, options, reflector,
onLogRecord.sendPort, pubSubSendPort);
var argsWithId = _RunnerArgsWithId(id, runnerArgs);
Isolate.spawn(isolateMain, argsWithId,
onExit: onExit.sendPort,
onError: onError.sendPort,
errorsAreFatal: true && false)
.then((isolate) {})
.catchError(c.completeError);
onLogRecord.listen((msg) => handleLogRecord(msg as LogRecord, options));
onError.listen((msg) {
if (msg is List) {
var e = msg[0], st = StackTrace.fromString(msg[1].toString());
handleLogRecord(
LogRecord(
Level.SEVERE, 'Fatal error', runnerArgs.loggerName, e, st),
options);
} else {
handleLogRecord(
LogRecord(Level.SEVERE, 'Fatal error', runnerArgs.loggerName, msg),
options);
}
});
onExit.listen((_) {
if (options.respawn) {
handleLogRecord(
LogRecord(
Level.WARNING,
'Instance #$id at ${DateTime.now()} crashed. Respawning immediately...',
runnerArgs.loggerName),
options);
_spawnIsolate(id, c, options, pubSubSendPort);
} else {
c.complete();
}
});
return c.future
.whenComplete(onExit.close)
.whenComplete(onError.close)
.whenComplete(onLogRecord.close);
}
/// Starts a number of isolates, running identical instances of an Angel application.
Future run(List<String> args) async {
pub_sub.Server server;
try {
var argResults = RunnerOptions.argParser.parse(args);
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' +
"A batteries-included, full-featured, full-stack framework in Dart." +
'\n\n' +
'https://angel-dart.github.io\n'));
if (argResults['help'] == true) {
stdout..writeln('Options:')..writeln(RunnerOptions.argParser.usage);
return;
}
print('Starting `${name}` application...');
var adapter = pub_sub.IsolateAdapter();
server = pub_sub.Server([adapter]);
// Register clients
for (int i = 0; i < Platform.numberOfProcessors; i++) {
server.registerClient(pub_sub.ClientInfo('client$i'));
}
server.start();
await Future.wait(List.generate(options.concurrency,
(id) => spawnIsolate(id, options, adapter.receivePort.sendPort)));
} on ArgParserException catch (e) {
stderr
..writeln(red.wrap(e.message))
..writeln()
..writeln(red.wrap('Options:'))
..writeln(red.wrap(RunnerOptions.argParser.usage));
exitCode = ExitCode.usage.code;
} catch (e, st) {
stderr
..writeln(red.wrap('fatal error: $e'))
..writeln(red.wrap(st.toString()));
exitCode = 1;
} finally {
await server?.close();
}
}
static void isolateMain(_RunnerArgsWithId argsWithId) {
var args = argsWithId.args;
hierarchicalLoggingEnabled = true;
var zone = Zone.current.fork(specification: ZoneSpecification(
print: (self, parent, zone, msg) {
args.loggingSendPort.send(LogRecord(Level.INFO, msg, args.loggerName));
},
));
zone.run(() async {
var client =
pub_sub.IsolateClient('client${argsWithId.id}', args.pubSubSendPort);
var app = Angel(reflector: args.reflector)
..container.registerSingleton<pub_sub.Client>(client)
..container.registerSingleton(InstanceInfo(id: argsWithId.id));
app.shutdownHooks.add((_) => client.close());
await app.configure(args.configureServer);
if (app.logger == null) {
app.logger = Logger(args.loggerName)
..onRecord.listen((rec) => Runner.handleLogRecord(rec, args.options));
}
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');
});
}
}
class _RunnerArgsWithId {
final int id;
final _RunnerArgs args;
_RunnerArgsWithId(this.id, this.args);
}
class _RunnerArgs {
final String name;
final AngelConfigurer configureServer;
final RunnerOptions options;
final Reflector reflector;
final SendPort loggingSendPort, pubSubSendPort;
_RunnerArgs(this.name, this.configureServer, this.options, this.reflector,
this.loggingSendPort, this.pubSubSendPort);
String get loggerName => name;
}

View file

@ -0,0 +1,16 @@
name: angel_production
version: 1.0.0
description: Helpers for concurrency, message-passing, rotating loggers, and other production functionality in Angel.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/production
environment:
sdk: ">=2.0.0-dev <3.0.0"
dependencies:
angel_container: ^1.0.0-alpha
angel_framework: ^2.0.0-alpha
args: ^1.0.0
io: ^0.3.2
logging: ^0.11.3
pub_sub: ^2.0.0
dev_dependencies:
pedantic: ^1.0.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB