commit
a1c65849b9
15 changed files with 220 additions and 124 deletions
|
@ -1,5 +1,10 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 8.2.0
|
||||||
|
|
||||||
|
* Add `addResponseHeader` to `AngelHttp` to add headers to HTTP default response
|
||||||
|
* Add `removeResponseHeader` to `AngelHttp` to remove headers from HTTP default response
|
||||||
|
|
||||||
## 8.1.1
|
## 8.1.1
|
||||||
|
|
||||||
* Updated broken image on README.md
|
* Updated broken image on README.md
|
||||||
|
|
|
@ -22,9 +22,6 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
HttpRequestContext, HttpResponseContext> {
|
HttpRequestContext, HttpResponseContext> {
|
||||||
@override
|
@override
|
||||||
Uri get uri {
|
Uri get uri {
|
||||||
//if (server == null) {
|
|
||||||
// throw ArgumentError("[AngelHttp] Server instance not intialised");
|
|
||||||
//}
|
|
||||||
return Uri(
|
return Uri(
|
||||||
scheme: 'http', host: server?.address.address, port: server?.port);
|
scheme: 'http', host: server?.address.address, port: server?.port);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +35,7 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
|
|
||||||
/// An instance mounted on a server started by the [serverGenerator].
|
/// An instance mounted on a server started by the [serverGenerator].
|
||||||
factory AngelHttp.custom(Angel app, ServerGeneratorType serverGenerator,
|
factory AngelHttp.custom(Angel app, ServerGeneratorType serverGenerator,
|
||||||
{bool useZone = true}) {
|
{bool useZone = true, Map<String, String> headers = const {}}) {
|
||||||
return AngelHttp._(app, serverGenerator, useZone);
|
return AngelHttp._(app, serverGenerator, useZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,15 +64,6 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
return AngelHttp.fromSecurityContext(app, serverContext, useZone: useZone);
|
return AngelHttp.fromSecurityContext(app, serverContext, useZone: useZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use [server] instead.
|
|
||||||
//@deprecated
|
|
||||||
//HttpServer get httpServer {
|
|
||||||
//if (server == null) {
|
|
||||||
// throw ArgumentError("[AngelHttp] Server instance not initialised");
|
|
||||||
//}
|
|
||||||
// return server;
|
|
||||||
//}
|
|
||||||
|
|
||||||
Future handleRequest(HttpRequest request) =>
|
Future handleRequest(HttpRequest request) =>
|
||||||
handleRawRequest(request, request.response);
|
handleRawRequest(request, request.response);
|
||||||
|
|
||||||
|
@ -88,6 +76,20 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
return await super.close();
|
return await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove headers from HTTP Response
|
||||||
|
void removeResponseHeader(Map<String, Object> headers) {
|
||||||
|
headers.forEach((key, value) {
|
||||||
|
server?.defaultResponseHeaders.remove(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add headers to HTTP Response
|
||||||
|
void addResponseHeader(Map<String, Object> headers) {
|
||||||
|
headers.forEach((key, value) {
|
||||||
|
server?.defaultResponseHeaders.add(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future closeResponse(HttpResponse response) => response.close();
|
Future closeResponse(HttpResponse response) => response.close();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: angel3_framework
|
name: angel3_framework
|
||||||
version: 8.1.1
|
version: 8.2.0
|
||||||
description: A high-powered HTTP server extensible framework with dependency injection, routing and much more.
|
description: A high-powered HTTP server extensible framework with dependency injection, routing and much more.
|
||||||
homepage: https://angel3-framework.web.app/
|
homepage: https://angel3-framework.web.app/
|
||||||
repository: https://github.com/dukefirehawk/angel/tree/master/packages/framework
|
repository: https://github.com/dukefirehawk/angel/tree/master/packages/framework
|
||||||
|
|
|
@ -27,6 +27,7 @@ import 'service_map_test.dart' as service_map;
|
||||||
import 'services_test.dart' as services;
|
import 'services_test.dart' as services;
|
||||||
import 'streaming_test.dart' as streaming;
|
import 'streaming_test.dart' as streaming;
|
||||||
import 'view_generator_test.dart' as view_generator;
|
import 'view_generator_test.dart' as view_generator;
|
||||||
|
//import 'response_header_test.dart' as response_header;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
/// For running with coverage
|
/// For running with coverage
|
||||||
|
@ -36,6 +37,7 @@ void main() {
|
||||||
group('accepts', accepts.main);
|
group('accepts', accepts.main);
|
||||||
group('anonymous service', anonymous_service.main);
|
group('anonymous service', anonymous_service.main);
|
||||||
group('body', body.main);
|
group('body', body.main);
|
||||||
|
//group('response_header', response_header.main);
|
||||||
group('controller', controller.main);
|
group('controller', controller.main);
|
||||||
group('detach', detach.main);
|
group('detach', detach.main);
|
||||||
group('di', di.main);
|
group('di', di.main);
|
||||||
|
|
58
packages/framework/test/response_header_test.dart
Normal file
58
packages/framework/test/response_header_test.dart
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:angel3_container/mirrors.dart';
|
||||||
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
import 'package:angel3_framework/src/http/angel_http.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Angel app;
|
||||||
|
late AngelHttp http;
|
||||||
|
late HttpClient client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = Angel(reflector: MirrorsReflector());
|
||||||
|
http = AngelHttp(app);
|
||||||
|
|
||||||
|
await http.startServer();
|
||||||
|
|
||||||
|
var formData = {'id': 100, 'name': 'William'};
|
||||||
|
app.get('/api/v1/user/list', (RequestContext req, res) async {
|
||||||
|
//await req.parseBody();
|
||||||
|
//res.write('Hello, World!');
|
||||||
|
res.json(formData);
|
||||||
|
});
|
||||||
|
|
||||||
|
client = HttpClient();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
client.close();
|
||||||
|
await http.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Remove Response Header', () async {
|
||||||
|
http.removeResponseHeader({'x-frame-options': 'SAMEORIGIN'});
|
||||||
|
|
||||||
|
var request = await client.get('localhost', 3000, '/api/v1/user/list');
|
||||||
|
HttpClientResponse response = await request.close();
|
||||||
|
//print(response.headers);
|
||||||
|
expect(response.headers['x-frame-options'], isNull);
|
||||||
|
}, skip: true);
|
||||||
|
|
||||||
|
test('Add Response Header', () async {
|
||||||
|
http.addResponseHeader({
|
||||||
|
'X-XSRF_TOKEN':
|
||||||
|
'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'
|
||||||
|
});
|
||||||
|
|
||||||
|
var request = await client.get('localhost', 3000, '/api/v1/user/list');
|
||||||
|
HttpClientResponse response = await request.close();
|
||||||
|
//print(response.headers);
|
||||||
|
expect(
|
||||||
|
response.headers['X-XSRF_TOKEN'],
|
||||||
|
equals([
|
||||||
|
'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'
|
||||||
|
]));
|
||||||
|
}, skip: true);
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 8.1.0
|
||||||
|
|
||||||
|
* Updated `vm_service` to 13.0.0
|
||||||
|
* Added configurable HTTP response header
|
||||||
|
|
||||||
## 8.0.0
|
## 8.0.0
|
||||||
|
|
||||||
* Require Dart >= 3.0
|
* Require Dart >= 3.0
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
Supports *hot reloading* of Angel3 servers on file changes. This is faster and more reliable than merely reactively restarting a `Process`. This package only works with the [Angel3 framework](https://pub.dev/packages/angel3_framework).
|
Supports *hot reloading* of Angel3 servers on file changes. This is faster and more reliable than merely reactively restarting a `Process`. This package only works with the [Angel3 framework](https://pub.dev/packages/angel3_framework).
|
||||||
|
|
||||||
**Not recommended to use in production, unless you are specifically intending for a "hot code push" in production..**
|
**Not recommended to use in production, unless you are specifically intending for a "hot code push" in production.**
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -23,11 +23,7 @@ dependencies:
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
This package is dependent on the Dart VM service, so you *must* run Dart with the `--observe` (or `--enable-vm-service`) argument!!!
|
This package is dependent on the Dart VM service, so you *must* run Dart with the `--observe` (or `--enable-vm-service`) argument. Usage is fairly simple. Pass a function that creates an `Angel` server, along with a collection of paths to watch, to the `HotReloader` constructor. The recommended pattern is to only use hot-reloading in your application entry point. Create your `Angel` instance within a separate function, conventionally named `createServer`.
|
||||||
|
|
||||||
Usage is fairly simple. Pass a function that creates an `Angel` server, along with a collection of paths to watch, to the `HotReloader` constructor. The rest is history!!!
|
|
||||||
|
|
||||||
The recommended pattern is to only use hot-reloading in your application entry point. Create your `Angel` instance within a separate function, conventionally named `createServer`.
|
|
||||||
|
|
||||||
You can watch:
|
You can watch:
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:glob/glob.dart';
|
||||||
import 'package:glob/list_local_fs.dart';
|
import 'package:glob/list_local_fs.dart';
|
||||||
import 'package:belatuk_html_builder/elements.dart';
|
import 'package:belatuk_html_builder/elements.dart';
|
||||||
import 'package:io/ansi.dart';
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:vm_service/vm_service.dart' as vm;
|
import 'package:vm_service/vm_service.dart' as vm;
|
||||||
import 'package:vm_service/vm_service_io.dart' as vm;
|
import 'package:vm_service/vm_service_io.dart' as vm;
|
||||||
|
@ -19,8 +20,6 @@ import 'package:watcher/watcher.dart';
|
||||||
|
|
||||||
/// A utility class that watches the filesystem for changes, and starts new instances of an Angel server.
|
/// A utility class that watches the filesystem for changes, and starts new instances of an Angel server.
|
||||||
class HotReloader {
|
class HotReloader {
|
||||||
late vm.VmService _client;
|
|
||||||
vm.IsolateRef? _mainIsolate;
|
|
||||||
final StreamController<WatchEvent> _onChange =
|
final StreamController<WatchEvent> _onChange =
|
||||||
StreamController<WatchEvent>.broadcast();
|
StreamController<WatchEvent>.broadcast();
|
||||||
final List _paths = [];
|
final List _paths = [];
|
||||||
|
@ -28,8 +27,10 @@ class HotReloader {
|
||||||
final Queue<HttpRequest> _requestQueue = Queue<HttpRequest>();
|
final Queue<HttpRequest> _requestQueue = Queue<HttpRequest>();
|
||||||
late HttpServer _io;
|
late HttpServer _io;
|
||||||
AngelHttp? _server;
|
AngelHttp? _server;
|
||||||
Duration? _timeout;
|
late Duration _timeout;
|
||||||
|
late vm.VmService _client;
|
||||||
vm.VM? _vmachine;
|
vm.VM? _vmachine;
|
||||||
|
vm.IsolateRef? _mainIsolate;
|
||||||
|
|
||||||
/// If `true` (default), then developers can `press 'r' to reload` the application on-the-fly.
|
/// If `true` (default), then developers can `press 'r' to reload` the application on-the-fly.
|
||||||
///
|
///
|
||||||
|
@ -47,7 +48,7 @@ class HotReloader {
|
||||||
///
|
///
|
||||||
/// If the timeout expires, then the request will be immediately terminated with a `502 Bad Gateway` error.
|
/// If the timeout expires, then the request will be immediately terminated with a `502 Bad Gateway` error.
|
||||||
/// Default: `5s`
|
/// Default: `5s`
|
||||||
Duration? get timeout => _timeout;
|
Duration get timeout => _timeout;
|
||||||
|
|
||||||
/// The Dart VM service host.
|
/// The Dart VM service host.
|
||||||
///
|
///
|
||||||
|
@ -116,20 +117,22 @@ class HotReloader {
|
||||||
Future handleRequest(HttpRequest request) async {
|
Future handleRequest(HttpRequest request) async {
|
||||||
if (_server != null) {
|
if (_server != null) {
|
||||||
return await _handle(request);
|
return await _handle(request);
|
||||||
} else if (timeout == null) {
|
//} else if (timeout == null) {
|
||||||
_requestQueue.add(request);
|
// _requestQueue.add(request);
|
||||||
} else {
|
} else {
|
||||||
_requestQueue.add(request);
|
_requestQueue.add(request);
|
||||||
Timer(timeout!, () {
|
Timer(timeout, () {
|
||||||
if (_requestQueue.remove(request)) {
|
if (_requestQueue.remove(request)) {
|
||||||
// Send 502 response
|
// Send 502 response
|
||||||
sendError(request, HttpStatus.badGateway, '502 Bad Gateway',
|
sendError(request, HttpStatus.badGateway, '502 Bad Gateway',
|
||||||
'Request timed out after ${timeout!.inMilliseconds}ms.');
|
'Request timed out after ${timeout.inMilliseconds}ms.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger? get _appLogger => _server?.app.logger;
|
||||||
|
|
||||||
Future<AngelHttp> _generateServer() async {
|
Future<AngelHttp> _generateServer() async {
|
||||||
var s = await generator();
|
var s = await generator();
|
||||||
await Future.forEach(s.startupHooks, s.configure);
|
await Future.forEach(s.startupHooks, s.configure);
|
||||||
|
@ -138,23 +141,24 @@ class HotReloader {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _logWarning(String msg) {
|
void _logWarning(String msg) {
|
||||||
if (_server?.app.logger != null) {
|
if (_appLogger != null) {
|
||||||
_server?.app.logger.warning(msg);
|
_appLogger?.warning(msg);
|
||||||
} else {
|
} else {
|
||||||
print(yellow.wrap('WARNING: $msg'));
|
print(yellow.wrap('WARNING: $msg'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _logInfo(String msg) {
|
void _logInfo(String msg) {
|
||||||
if (_server?.app.logger != null) {
|
if (_appLogger != null) {
|
||||||
_server?.app.logger.info(msg);
|
_appLogger?.info(msg);
|
||||||
} else {
|
} else {
|
||||||
print(lightGray.wrap(msg));
|
print(lightGray.wrap(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts listening to requests and filesystem events.
|
/// Starts listening to requests and filesystem events.
|
||||||
Future<HttpServer> startServer([address, int? port]) async {
|
Future<HttpServer> startServer(
|
||||||
|
[String address = '127.0.0.1', int port = 3000]) async {
|
||||||
var isHot = true;
|
var isHot = true;
|
||||||
_server = await _generateServer();
|
_server = await _generateServer();
|
||||||
|
|
||||||
|
@ -185,9 +189,10 @@ class HotReloader {
|
||||||
_mainIsolate ??= _vmachine?.isolates?.first;
|
_mainIsolate ??= _vmachine?.isolates?.first;
|
||||||
|
|
||||||
if (_vmachine != null) {
|
if (_vmachine != null) {
|
||||||
for (var isolate in _vmachine!.isolates ?? <vm.IsolateRef>[]) {
|
for (var isolate in _vmachine?.isolates ?? <vm.IsolateRef>[]) {
|
||||||
if (isolate.id != null) {
|
var isolateId = isolate.id;
|
||||||
await _client.setIsolatePauseMode(isolate.id!,
|
if (isolateId != null) {
|
||||||
|
await _client.setIsolatePauseMode(isolateId,
|
||||||
exceptionPauseMode: 'None');
|
exceptionPauseMode: 'None');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +208,8 @@ class HotReloader {
|
||||||
while (_requestQueue.isNotEmpty) {
|
while (_requestQueue.isNotEmpty) {
|
||||||
await _handle(_requestQueue.removeFirst());
|
await _handle(_requestQueue.removeFirst());
|
||||||
}
|
}
|
||||||
var server = _io = await HttpServer.bind(address ?? '127.0.0.1', port ?? 0);
|
var server = _io = await HttpServer.bind(address, port);
|
||||||
|
//server.defaultResponseHeaders();
|
||||||
server.listen(handleRequest);
|
server.listen(handleRequest);
|
||||||
|
|
||||||
// Print a Flutter-like prompt...
|
// Print a Flutter-like prompt...
|
||||||
|
@ -213,7 +219,7 @@ class HotReloader {
|
||||||
|
|
||||||
Uri? observatoryUri;
|
Uri? observatoryUri;
|
||||||
if (isHot) {
|
if (isHot) {
|
||||||
observatoryUri = await dev.Service.getInfo().then((i) => i.serverUri!);
|
observatoryUri = await dev.Service.getInfo().then((i) => i.serverUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
print(styleBold.wrap(
|
print(styleBold.wrap(
|
||||||
|
@ -260,7 +266,7 @@ class HotReloader {
|
||||||
if (ch == $R) {
|
if (ch == $R) {
|
||||||
_logInfo('Manually restarting server...\n');
|
_logInfo('Manually restarting server...\n');
|
||||||
await _killServer();
|
await _killServer();
|
||||||
await _server!.close();
|
await _server?.close();
|
||||||
var addr = _io.address.address;
|
var addr = _io.address.address;
|
||||||
var port = _io.port;
|
var port = _io.port;
|
||||||
await _io.close(force: true);
|
await _io.close(force: true);
|
||||||
|
@ -359,21 +365,23 @@ class HotReloader {
|
||||||
scheduleMicrotask(() async {
|
scheduleMicrotask(() async {
|
||||||
// Disconnect active WebSockets
|
// Disconnect active WebSockets
|
||||||
try {
|
try {
|
||||||
var ws = _server!.app.container.make<AngelWebSocket>();
|
var ws = _server?.app.container.make<AngelWebSocket>();
|
||||||
|
|
||||||
|
if (ws != null) {
|
||||||
for (var client in ws.clients) {
|
for (var client in ws.clients) {
|
||||||
try {
|
try {
|
||||||
await client.close();
|
await client.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logWarning(
|
_logWarning(
|
||||||
'Couldn\'t close WebSocket from session #${client.request.session!.id}: $e');
|
'Couldn\'t close WebSocket from session #${client.request.session?.id}: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// await Future.forEach(
|
// await Future.forEach(
|
||||||
// _server.app.shutdownHooks, _server.app.configure);
|
// _server.app.shutdownHooks, _server.app.configure);
|
||||||
await _server!.app.close();
|
await _server?.app.close();
|
||||||
_server!.app.logger.clearListeners();
|
_server?.app.logger.clearListeners();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Fail silently...
|
// Fail silently...
|
||||||
}
|
}
|
||||||
|
@ -387,12 +395,17 @@ class HotReloader {
|
||||||
_server = null;
|
_server = null;
|
||||||
|
|
||||||
if (hot) {
|
if (hot) {
|
||||||
var report = await _client.reloadSources(_mainIsolate!.id!);
|
var mainIsolateId = _mainIsolate?.id;
|
||||||
|
if (mainIsolateId != null) {
|
||||||
|
var report = await _client.reloadSources(mainIsolateId);
|
||||||
|
|
||||||
if (report.success != null) {
|
if (report.success != null) {
|
||||||
_logWarning(
|
_logWarning(
|
||||||
'Hot reload failed - perhaps some sources have not been generated yet.');
|
'Hot reload failed - perhaps some sources have not been generated yet.');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
_logWarning('Hot reload failed - isolate id does not exist.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var s = await _generateServer();
|
var s = await _generateServer();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: angel3_hot
|
name: angel3_hot
|
||||||
version: 8.0.0
|
version: 8.1.0
|
||||||
description: Supports hot reloading/hot code push of Angel3 servers on file changes.
|
description: Supports hot reloading/hot code push of Angel3 servers on file changes.
|
||||||
homepage: https://angel3-framework.web.app/
|
homepage: https://angel3-framework.web.app/
|
||||||
repository: https://github.com/dukefirehawk/angel/tree/master/packages/hot
|
repository: https://github.com/dukefirehawk/angel/tree/master/packages/hot
|
||||||
|
@ -13,17 +13,17 @@ dependencies:
|
||||||
glob: ^2.1.0
|
glob: ^2.1.0
|
||||||
io: ^1.0.0
|
io: ^1.0.0
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
vm_service: ^11.6.0
|
vm_service: ^13.0.0
|
||||||
watcher: ^1.0.0
|
watcher: ^1.0.0
|
||||||
|
logging: ^1.2.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
http: ^1.0.0
|
http: ^1.0.0
|
||||||
logging: ^1.2.0
|
|
||||||
lints: ^2.1.0
|
lints: ^2.1.0
|
||||||
# dependency_overrides:
|
dependency_overrides:
|
||||||
# angel3_container:
|
# angel3_container:
|
||||||
# path: ../container/angel_container
|
# path: ../container/angel_container
|
||||||
# angel3_framework:
|
angel3_framework:
|
||||||
# path: ../framework
|
path: ../framework
|
||||||
# angel3_http_exception:
|
# angel3_http_exception:
|
||||||
# path: ../http_exception
|
# path: ../http_exception
|
||||||
# angel3_model:
|
# angel3_model:
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 8.1.0
|
||||||
|
|
||||||
|
* Added optional `responseHeaders` and `removeResponseHeaders` to `Runner`
|
||||||
|
|
||||||
## 8.0.0
|
## 8.0.0
|
||||||
|
|
||||||
* Require Dart >= 3.0
|
* Require Dart >= 3.0
|
||||||
|
|
|
@ -9,9 +9,7 @@ Helpers for concurrency, message-passing, rotating loggers, and other production
|
||||||
|
|
||||||
![Screenshot](angel3-screenshot.png)
|
![Screenshot](angel3-screenshot.png)
|
||||||
|
|
||||||
This will become the de-facto way to run Angel3 applications in deployed environments, as it takes care of inter-isolate communication, respawning dead processes, and other housekeeping for you automatically.
|
This will become the de-facto way to run Angel3 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.
|
||||||
|
|
||||||
Most users will want to use the `Runner` class.
|
|
||||||
|
|
||||||
## `Runner`
|
## `Runner`
|
||||||
|
|
||||||
|
@ -45,15 +43,11 @@ When combined with `systemd`, deploying Angel3 applications on Linux can be very
|
||||||
## Message Passing
|
## Message Passing
|
||||||
|
|
||||||
The `Runner` class uses [`belatuk_pub_sub`](<https://pub.dev/packages/belatuk_pub_sub>) to coordinate
|
The `Runner` class uses [`belatuk_pub_sub`](<https://pub.dev/packages/belatuk_pub_sub>) to coordinate
|
||||||
message passing between isolates.
|
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:
|
||||||
|
|
||||||
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
|
```dart
|
||||||
// Use the injected `pub_sub.Client` to send messages.
|
// Use the injected `pub_sub.Client` to send messages.
|
||||||
var client = app.container.make<pub_sub.Client>();
|
var client = app.container.make<Client>();
|
||||||
|
|
||||||
// We can listen for an event to perform some behavior.
|
// We can listen for an event to perform some behavior.
|
||||||
//
|
//
|
||||||
|
@ -66,6 +60,34 @@ onGreetingChanged
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Customising Response Header
|
||||||
|
|
||||||
|
Additional parameters can be passed to the `Runner` class to:
|
||||||
|
|
||||||
|
1. Remove headers from HTTP response.
|
||||||
|
2. Add headers to HTTP response.
|
||||||
|
|
||||||
|
For example, the following code snippet removes `X-FRAME-OPTIONS` and adds `X-XSRF_TOKEN` to the response header.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
|
||||||
|
void main(List<String> args) {
|
||||||
|
// Remove 'X-FRAME-OPTIONS'
|
||||||
|
var removeHeader = {'X-FRAME-OPTIONS': 'SAMEORIGIN'};
|
||||||
|
|
||||||
|
// Add 'X-XSRF_TOKEN'
|
||||||
|
var customHeader = {
|
||||||
|
'X-XSRF-TOKEN':
|
||||||
|
'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'
|
||||||
|
};
|
||||||
|
|
||||||
|
Runner('example', configureServer,
|
||||||
|
removeResponseHeaders: removeHeader, responseHeaders: customHeader)
|
||||||
|
.run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Run-time Metadata
|
## 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:
|
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:
|
||||||
|
|
|
@ -2,13 +2,16 @@ import 'dart:async';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel3_production/angel3_production.dart';
|
import 'package:angel3_production/angel3_production.dart';
|
||||||
import 'package:belatuk_pub_sub/belatuk_pub_sub.dart' as pub_sub;
|
import 'package:belatuk_pub_sub/belatuk_pub_sub.dart';
|
||||||
|
|
||||||
void main(List<String> args) => Runner('example', configureServer).run(args);
|
void main(List<String> args) {
|
||||||
|
Runner('example', configureServer).run(args);
|
||||||
|
}
|
||||||
|
|
||||||
Future configureServer(Angel app) async {
|
Future configureServer(Angel app) async {
|
||||||
// Use the injected `pub_sub.Client` to send messages.
|
// Use the injected `pub_sub.Client` to send messages.
|
||||||
var client = app.container.make<pub_sub.Client>();
|
var client = app.container.make<Client>();
|
||||||
|
|
||||||
var greeting = 'Hello! This is the default greeting.';
|
var greeting = 'Hello! This is the default greeting.';
|
||||||
|
|
||||||
// We can listen for an event to perform some behavior.
|
// We can listen for an event to perform some behavior.
|
||||||
|
|
|
@ -32,13 +32,12 @@ class RunnerOptions {
|
||||||
..addOption('key-file', help: 'The PEM key file to read.')
|
..addOption('key-file', help: 'The PEM key file to read.')
|
||||||
..addOption('key-password', help: 'The PEM key file password.');
|
..addOption('key-password', help: 'The PEM key file password.');
|
||||||
|
|
||||||
final String? hostname,
|
final String hostname;
|
||||||
certificateFile,
|
final String? certificateFile, keyFile, certificatePassword, keyPassword;
|
||||||
keyFile,
|
|
||||||
certificatePassword,
|
|
||||||
keyPassword;
|
|
||||||
final int concurrency, port;
|
final int concurrency, port;
|
||||||
final bool useZone, respawn, quiet, ssl, http2;
|
final bool useZone, respawn, quiet, ssl, http2;
|
||||||
|
final Map<String, Object> removeResponseHeaders = {};
|
||||||
|
final Map<String, Object> responseHeaders = {};
|
||||||
|
|
||||||
RunnerOptions(
|
RunnerOptions(
|
||||||
{this.hostname = '127.0.0.1',
|
{this.hostname = '127.0.0.1',
|
||||||
|
@ -56,26 +55,18 @@ class RunnerOptions {
|
||||||
|
|
||||||
factory RunnerOptions.fromArgResults(ArgResults argResults) {
|
factory RunnerOptions.fromArgResults(ArgResults argResults) {
|
||||||
return RunnerOptions(
|
return RunnerOptions(
|
||||||
hostname: argResults['address'] as String?,
|
hostname: argResults['address'] as String,
|
||||||
port: int.parse(argResults['port'] as String),
|
port: int.parse(argResults['port'] as String),
|
||||||
concurrency: int.parse(argResults['concurrency'] as String),
|
concurrency: int.parse(argResults['concurrency'] as String),
|
||||||
useZone: argResults['use-zone'] as bool? ?? false,
|
useZone: argResults['use-zone'] as bool? ?? false,
|
||||||
respawn: argResults['respawn'] as bool? ?? true,
|
respawn: argResults['respawn'] as bool? ?? true,
|
||||||
quiet: argResults['quiet'] as bool? ?? false,
|
quiet: argResults['quiet'] as bool? ?? false,
|
||||||
certificateFile: argResults.wasParsed('certificate-file')
|
certificateFile: argResults['certificate-file'] as String?,
|
||||||
? argResults['certificate-file'] as String?
|
keyFile: argResults['key-file'] as String?,
|
||||||
: null,
|
|
||||||
keyFile: argResults.wasParsed('key-file')
|
|
||||||
? argResults['key-file'] as String?
|
|
||||||
: null,
|
|
||||||
ssl: argResults['ssl'] as bool? ?? false,
|
ssl: argResults['ssl'] as bool? ?? false,
|
||||||
http2: argResults['http2'] as bool? ?? false,
|
http2: argResults['http2'] as bool? ?? false,
|
||||||
certificatePassword: argResults.wasParsed('certificate-password')
|
certificatePassword: argResults['certificate-password'] as String?,
|
||||||
? argResults['certificate-password'] as String?
|
keyPassword: argResults['key-password'] as String?,
|
||||||
: null,
|
|
||||||
keyPassword: argResults.wasParsed('key-password')
|
|
||||||
? argResults['key-password'] as String?
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
import 'package:belatuk_pub_sub/belatuk_pub_sub.dart';
|
||||||
|
import 'package:belatuk_pub_sub/isolate.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:angel3_container/angel3_container.dart';
|
import 'package:angel3_container/angel3_container.dart';
|
||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
@ -10,8 +12,6 @@ import 'package:args/args.dart';
|
||||||
import 'package:io/ansi.dart';
|
import 'package:io/ansi.dart';
|
||||||
import 'package:io/io.dart';
|
import 'package:io/io.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:belatuk_pub_sub/isolate.dart' as pub_sub;
|
|
||||||
import 'package:belatuk_pub_sub/belatuk_pub_sub.dart' as pub_sub;
|
|
||||||
import 'instance_info.dart';
|
import 'instance_info.dart';
|
||||||
import 'options.dart';
|
import 'options.dart';
|
||||||
|
|
||||||
|
@ -24,35 +24,21 @@ class Runner {
|
||||||
final AngelConfigurer configureServer;
|
final AngelConfigurer configureServer;
|
||||||
final Reflector reflector;
|
final Reflector reflector;
|
||||||
|
|
||||||
|
final Map<String, Object> removeResponseHeaders;
|
||||||
|
final Map<String, Object> responseHeaders;
|
||||||
|
|
||||||
Runner(this.name, this.configureServer,
|
Runner(this.name, this.configureServer,
|
||||||
{this.reflector = const EmptyReflector()});
|
{this.reflector = const EmptyReflector(),
|
||||||
|
this.removeResponseHeaders = const {},
|
||||||
|
this.responseHeaders = const {}});
|
||||||
|
|
||||||
static const String asciiArt2 = '''
|
static const String _asciiArt = '''
|
||||||
|
|
||||||
___ _ ________________ _____
|
|
||||||
/ | / | / / ____/ ____/ / |__ /
|
|
||||||
/ /| | / |/ / / __/ __/ / / /_ <
|
|
||||||
/ ___ |/ /| / /_/ / /___/ /______/ /
|
|
||||||
/_/ |_/_/ |_/\\____/_____/_____/____/
|
|
||||||
|
|
||||||
''';
|
|
||||||
|
|
||||||
static const String asciiArt = '''
|
|
||||||
|
|
||||||
_ _ _ ____ _____ _ _____
|
_ _ _ ____ _____ _ _____
|
||||||
/ \\ | \\ | |/ ___| ____| | |___ /
|
/ \\ | \\ | |/ ___| ____| | |___ /
|
||||||
/ _ \\ | \\| | | _| _| | | |_ \\
|
/ _ \\ | \\| | | _| _| | | |_ \\
|
||||||
/ ___ \\| |\\ | |_| | |___| |___ ___) |
|
/ ___ \\| |\\ | |_| | |___| |___ ___) |
|
||||||
/_/ \\_\\_| \\_|\\____|_____|_____|____/
|
/_/ \\_\\_| \\_|\\____|_____|_____|____/
|
||||||
''';
|
|
||||||
|
|
||||||
static const String asciiArtOld = '''
|
|
||||||
____________ ________________________
|
|
||||||
___ |__ | / /_ ____/__ ____/__ /
|
|
||||||
__ /| |_ |/ /_ / __ __ __/ __ /
|
|
||||||
_ ___ | /| / / /_/ / _ /___ _ /___
|
|
||||||
/_/ |_/_/ |_/ ____/ /_____/ /_____/
|
|
||||||
|
|
||||||
''';
|
''';
|
||||||
|
|
||||||
static final DateFormat _defaultDateFormat =
|
static final DateFormat _defaultDateFormat =
|
||||||
|
@ -173,11 +159,14 @@ _ ___ | /| / / /_/ / _ /___ _ /___
|
||||||
|
|
||||||
/// Starts a number of isolates, running identical instances of an Angel application.
|
/// Starts a number of isolates, running identical instances of an Angel application.
|
||||||
Future run(List<String> args) async {
|
Future run(List<String> args) async {
|
||||||
pub_sub.Server? server;
|
Server? server;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var argResults = RunnerOptions.argParser.parse(args);
|
var argResults = RunnerOptions.argParser.parse(args);
|
||||||
|
|
||||||
var options = RunnerOptions.fromArgResults(argResults);
|
var options = RunnerOptions.fromArgResults(argResults);
|
||||||
|
options.responseHeaders.addAll(responseHeaders);
|
||||||
|
options.removeResponseHeaders.addAll(removeResponseHeaders);
|
||||||
|
|
||||||
if (options.ssl || options.http2) {
|
if (options.ssl || options.http2) {
|
||||||
if (options.certificateFile == null) {
|
if (options.certificateFile == null) {
|
||||||
|
@ -188,7 +177,7 @@ _ ___ | /| / / /_/ / _ /___ _ /___
|
||||||
}
|
}
|
||||||
|
|
||||||
print(darkGray.wrap(
|
print(darkGray.wrap(
|
||||||
'$asciiArt\n\nA batteries-included, full-featured, full-stack framework in Dart.\n\nhttps://angel3-framework.web.app\n'));
|
'$_asciiArt\n\nA batteries-included, full-featured, full-stack framework in Dart.\n\nhttps://angel3-framework.web.app\n'));
|
||||||
|
|
||||||
if (argResults['help'] == true) {
|
if (argResults['help'] == true) {
|
||||||
stdout
|
stdout
|
||||||
|
@ -199,12 +188,12 @@ _ ___ | /| / / /_/ / _ /___ _ /___
|
||||||
|
|
||||||
print('Starting `$name` application...');
|
print('Starting `$name` application...');
|
||||||
|
|
||||||
var adapter = pub_sub.IsolateAdapter();
|
var adapter = IsolateAdapter();
|
||||||
server = pub_sub.Server([adapter]);
|
server = Server([adapter]);
|
||||||
|
|
||||||
// Register clients
|
// Register clients
|
||||||
for (var i = 0; i < Platform.numberOfProcessors; i++) {
|
for (var i = 0; i < Platform.numberOfProcessors; i++) {
|
||||||
server.registerClient(pub_sub.ClientInfo('client$i'));
|
server.registerClient(ClientInfo('client$i'));
|
||||||
}
|
}
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
|
@ -240,11 +229,10 @@ _ ___ | /| / / /_/ / _ /___ _ /___
|
||||||
));
|
));
|
||||||
|
|
||||||
zone.run(() async {
|
zone.run(() async {
|
||||||
var client =
|
var client = IsolateClient('client${argsWithId.id}', args.pubSubSendPort);
|
||||||
pub_sub.IsolateClient('client${argsWithId.id}', args.pubSubSendPort);
|
|
||||||
|
|
||||||
var app = Angel(reflector: args.reflector)
|
var app = Angel(reflector: args.reflector)
|
||||||
..container.registerSingleton<pub_sub.Client>(client)
|
..container.registerSingleton<Client>(client)
|
||||||
..container.registerSingleton(InstanceInfo(id: argsWithId.id));
|
..container.registerSingleton(InstanceInfo(id: argsWithId.id));
|
||||||
|
|
||||||
app.shutdownHooks.add((_) => client.close());
|
app.shutdownHooks.add((_) => client.close());
|
||||||
|
@ -292,6 +280,13 @@ _ ___ | /| / / /_/ / _ /___ _ /___
|
||||||
}
|
}
|
||||||
|
|
||||||
await driver.startServer(args.options.hostname, args.options.port);
|
await driver.startServer(args.options.hostname, args.options.port);
|
||||||
|
|
||||||
|
// Only apply the headers to AngelHttp instance
|
||||||
|
if (driver is AngelHttp) {
|
||||||
|
driver.addResponseHeader(args.options.responseHeaders);
|
||||||
|
driver.removeResponseHeader(args.options.removeResponseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
serverUrl = driver.uri;
|
serverUrl = driver.uri;
|
||||||
if (args.options.ssl || args.options.http2) {
|
if (args.options.ssl || args.options.http2) {
|
||||||
serverUrl = serverUrl.replace(scheme: 'https');
|
serverUrl = serverUrl.replace(scheme: 'https');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: angel3_production
|
name: angel3_production
|
||||||
version: 8.0.0
|
version: 8.1.0
|
||||||
description: Helpers for concurrency, message-passing, rotating loggers, and other production functionality in Angel3.
|
description: Helpers for concurrency, message-passing, rotating loggers, and other production functionality in Angel3.
|
||||||
homepage: https://angel3-framework.web.app
|
homepage: https://angel3-framework.web.app
|
||||||
repository: https://github.com/dukefirehawk/angel/tree/master/packages/production
|
repository: https://github.com/dukefirehawk/angel/tree/master/packages/production
|
||||||
|
@ -7,7 +7,7 @@ environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
angel3_container: ^8.0.0
|
angel3_container: ^8.0.0
|
||||||
angel3_framework: ^8.0.0
|
angel3_framework: ^8.2.0
|
||||||
belatuk_pub_sub: ^6.0.0
|
belatuk_pub_sub: ^6.0.0
|
||||||
args: ^2.4.0
|
args: ^2.4.0
|
||||||
io: ^1.0.0
|
io: ^1.0.0
|
||||||
|
@ -15,11 +15,11 @@ dependencies:
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.1.0
|
lints: ^2.1.0
|
||||||
# dependency_overrides:
|
dependency_overrides:
|
||||||
# angel3_container:
|
# angel3_container:
|
||||||
# path: ../container/angel_container
|
# path: ../container/angel_container
|
||||||
# angel3_framework:
|
angel3_framework:
|
||||||
# path: ../framework
|
path: ../framework
|
||||||
# angel3_http_exception:
|
# angel3_http_exception:
|
||||||
# path: ../http_exception
|
# path: ../http_exception
|
||||||
# angel3_model:
|
# angel3_model:
|
||||||
|
|
Loading…
Reference in a new issue