Create Runner
This commit is contained in:
parent
12f08e9df8
commit
7b6ccda237
6 changed files with 273 additions and 0 deletions
3
analysis_options.yaml
Normal file
3
analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
19
example/main.dart
Normal file
19
example/main.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_production/angel_production.dart';
|
||||
|
||||
main(List<String> args) {
|
||||
var runner = new Runner('example', configureServer);
|
||||
return runner.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);
|
||||
return 'Crashing in 3s...';
|
||||
});
|
||||
}
|
2
lib/angel_production.dart
Normal file
2
lib/angel_production.dart
Normal file
|
@ -0,0 +1,2 @@
|
|||
export 'src/options.dart';
|
||||
export 'src/runner.dart';
|
43
lib/src/options.dart
Normal file
43
lib/src/options.dart
Normal file
|
@ -0,0 +1,43 @@
|
|||
import 'dart:io';
|
||||
import 'package:args/args.dart';
|
||||
|
||||
class RunnerOptions {
|
||||
static final ArgParser argParser = new 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.')
|
||||
..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.');
|
||||
|
||||
final String hostname;
|
||||
final int concurrency, port;
|
||||
final bool useZone, respawn;
|
||||
|
||||
RunnerOptions(
|
||||
{this.hostname: '127.0.0.1',
|
||||
this.port: 3000,
|
||||
this.concurrency: 1,
|
||||
this.useZone: false,
|
||||
this.respawn: true});
|
||||
|
||||
factory RunnerOptions.fromArgResults(ArgResults argResults) {
|
||||
return new 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,
|
||||
);
|
||||
}
|
||||
}
|
200
lib/src/runner.dart
Normal file
200
lib/src/runner.dart
Normal file
|
@ -0,0 +1,200 @@
|
|||
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:args/args.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:io/ansi.dart';
|
||||
import 'package:io/io.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) {
|
||||
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(RunnerOptions options) {
|
||||
return _spawnIsolate(new Completer(), options);
|
||||
}
|
||||
|
||||
Future _spawnIsolate(Completer c, RunnerOptions options) {
|
||||
var onLogRecord = new ReceivePort();
|
||||
var onExit = new ReceivePort();
|
||||
var onError = new ReceivePort();
|
||||
var runnerArgs = new _RunnerArgs(
|
||||
name, configureServer, options, reflector, onLogRecord.sendPort);
|
||||
|
||||
Isolate.spawn(isolateMain, runnerArgs,
|
||||
onExit: onExit.sendPort,
|
||||
onError: onError.sendPort,
|
||||
errorsAreFatal: true && false)
|
||||
.then((isolate) {})
|
||||
.catchError(c.completeError);
|
||||
|
||||
onLogRecord.listen((msg) => handleLogRecord(msg as LogRecord));
|
||||
|
||||
onError.listen((msg) {
|
||||
if (msg is List) {
|
||||
var e = msg[0], st = StackTrace.fromString(msg[1].toString());
|
||||
handleLogRecord(new LogRecord(
|
||||
Level.SEVERE, 'Fatal error', runnerArgs.loggerName, e, st));
|
||||
} else {
|
||||
handleLogRecord(new LogRecord(
|
||||
Level.SEVERE, 'Fatal error', runnerArgs.loggerName, msg));
|
||||
}
|
||||
});
|
||||
|
||||
onExit.listen((_) {
|
||||
if (options.respawn) {
|
||||
handleLogRecord(new LogRecord(
|
||||
Level.WARNING,
|
||||
'Detected a crashed instance at ${new DateTime.now()}. Respawning immediately...',
|
||||
runnerArgs.loggerName));
|
||||
_spawnIsolate(c, options);
|
||||
} 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 {
|
||||
try {
|
||||
var argResults = RunnerOptions.argParser.parse(args);
|
||||
var options = new RunnerOptions.fromArgResults(argResults);
|
||||
|
||||
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...');
|
||||
print('Arguments: $args...\n');
|
||||
|
||||
await Future.wait(
|
||||
new List.generate(options.concurrency, (_) => spawnIsolate(options)));
|
||||
} on ArgParserException catch (e) {
|
||||
stderr
|
||||
..writeln(e.message)
|
||||
..writeln()
|
||||
..writeln('Options:')
|
||||
..writeln(RunnerOptions.argParser.usage);
|
||||
exitCode = ExitCode.usage.code;
|
||||
} catch (e) {
|
||||
stderr..writeln('fatal error: $e');
|
||||
exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void isolateMain(_RunnerArgs args) {
|
||||
hierarchicalLoggingEnabled = true;
|
||||
|
||||
var zone = Zone.current.fork(specification: new ZoneSpecification(
|
||||
print: (self, parent, zone, msg) {
|
||||
args.loggingSendPort
|
||||
.send(new LogRecord(Level.INFO, msg, args.loggerName));
|
||||
},
|
||||
));
|
||||
|
||||
zone.run(() async {
|
||||
var app = new Angel(reflector: args.reflector);
|
||||
await app.configure(args.configureServer);
|
||||
|
||||
if (app.logger == null) {
|
||||
app.logger = new 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('Listening at $url');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _RunnerArgs {
|
||||
final String name;
|
||||
|
||||
final AngelConfigurer configureServer;
|
||||
|
||||
final RunnerOptions options;
|
||||
|
||||
final Reflector reflector;
|
||||
|
||||
final SendPort loggingSendPort;
|
||||
|
||||
_RunnerArgs(this.name, this.configureServer, this.options, this.reflector,
|
||||
this.loggingSendPort);
|
||||
|
||||
String get loggerName => name;
|
||||
}
|
6
pubspec.yaml
Normal file
6
pubspec.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: angel_production
|
||||
dependencies:
|
||||
angel_framework: ^2.0.0-alpha
|
||||
args: ^1.0.0
|
||||
io: ^0.3.2
|
||||
logging: ^0.11.3
|
Loading…
Reference in a new issue