Fixed compatibility issues with 1.1.3

This commit is contained in:
Tobe O 2018-06-07 12:11:03 -04:00
parent 4639efd67f
commit 0c3936d602
10 changed files with 128 additions and 95 deletions

View file

@ -2,6 +2,7 @@
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.pub" /> <excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" /> <excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />

View file

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
<option name="VMOptions" value="--observe" />
<option name="filePath" value="$PROJECT_DIR$/example/basic/main.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$/example/basic" />
<method />
</configuration>
</component>

View file

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart (No VM service)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/example/basic/main.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$/example/basic" />
<method />
</configuration>
</component>

View file

@ -1,8 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="server.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
<option name="VMOptions" value="--observe" />
<option name="filePath" value="$PROJECT_DIR$/example/basic/server.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$/example/basic" />
<method />
</configuration>
</component>

View file

@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="server.dart (No VM service)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/example/basic/server.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$/example/basic" />
<method />
</configuration>
</component>

4
CHANGELOG.md Normal file
View file

@ -0,0 +1,4 @@
# 1.1.1
* Disable the observatory from pausing the isolate
on exceptions, because Angel already handles
all exceptions by itself.

View file

@ -1,8 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:io' show Directory;
import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_hot/angel_hot.dart'; import 'package:angel_hot/angel_hot.dart';
import 'package:dart2_constant/convert.dart';
import 'package:dart2_constant/io.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'src/foo.dart'; import 'src/foo.dart';
@ -10,19 +11,19 @@ main() async {
var hot = new HotReloader(createServer, [ var hot = new HotReloader(createServer, [
new Directory('src'), new Directory('src'),
new Directory('src'), new Directory('src'),
'server.dart', 'main.dart',
Uri.parse('package:angel_hot/angel_hot.dart') Uri.parse('package:angel_hot/angel_hot.dart')
]); ]);
var server = await hot.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); var server = await hot.startServer('127.0.0.1', 3000);
print( print('Hot server listening at http://${server.address.address}:${server
'Hot server listening at http://${server.address.address}:${server.port}'); .port}');
} }
Future<Angel> createServer() async { Future<Angel> createServer() async {
var app = new Angel(); var app = new Angel();
app.lazyParseBodies = true; app.lazyParseBodies = true;
app.injectSerializer(JSON.encode); app.serializer = json.encode;
// Edit this line, and then refresh the page in your browser! // Edit this line, and then refresh the page in your browser!
app.get('/', {'hello': 'hot world!'}); app.get('/', {'hello': 'hot world!'});
@ -31,18 +32,18 @@ Future<Angel> createServer() async {
app.use(() => throw new AngelHttpException.notFound()); app.use(() => throw new AngelHttpException.notFound());
app.injectEncoders({ app.injectEncoders({
'gzip': GZIP.encoder, 'gzip': gzip.encoder,
'deflate': ZLIB.encoder, 'deflate': zlib.encoder,
}); });
app.logger = new Logger('angel') app.logger = new Logger('angel')
..onRecord.listen((rec) { ..onRecord.listen((rec) {
print(rec); print(rec);
if (rec.error != null) { if (rec.error != null) {
print(rec.error); print(rec.error);
print(rec.stackTrace); print(rec.stackTrace);
} }
}); });
return app; return app;
} }

View file

@ -1,10 +1,25 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:convert'; import 'dart:io'
import 'dart:io'; show
ContentType,
Directory,
File,
FileStat,
FileSystemEntity,
FileSystemException,
HttpHeaders,
HttpRequest,
HttpServer,
Link,
Platform,
exit,
stderr;
import 'dart:isolate'; import 'dart:isolate';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_websocket/server.dart'; import 'package:angel_websocket/server.dart';
import 'package:dart2_constant/convert.dart';
import 'package:dart2_constant/io.dart';
import 'package:glob/glob.dart'; import 'package:glob/glob.dart';
import 'package:html_builder/elements.dart'; import 'package:html_builder/elements.dart';
import 'package:html_builder/html_builder.dart'; import 'package:html_builder/html_builder.dart';
@ -15,13 +30,15 @@ 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 {
vm.VmService _client; vm.VmService _client;
vm.IsolateRef _mainIsolate;
final StreamController<WatchEvent> _onChange = final StreamController<WatchEvent> _onChange =
new StreamController<WatchEvent>.broadcast(); new StreamController<WatchEvent>.broadcast();
final List _paths = []; final List _paths = [];
final StringRenderer _renderer = new StringRenderer(pretty: false); final StringRenderer _renderer = new StringRenderer(pretty: false);
final Queue<HttpRequest> _requestQueue = new Queue<HttpRequest>(); final Queue<HttpRequest> _requestQueue = new Queue<HttpRequest>();
Angel _server; AngelHttp _server;
Duration _timeout; Duration _timeout;
vm.VM _vmachine;
/// Invoked to load a new instance of [Angel] on file changes. /// Invoked to load a new instance of [Angel] on file changes.
final FutureOr<Angel> Function() generator; final FutureOr<Angel> Function() generator;
@ -62,9 +79,44 @@ class HotReloader {
_onChange.close(); _onChange.close();
} }
void sendError(HttpRequest request, int status, String title_, e) {
var doc = html(lang: 'en', c: [
head(c: [
meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
title(c: [text(title_)])
]),
body(c: [
h1(c: [text(title_)]),
i(c: [text(e.toString())])
])
]);
var response = request.response;
response.statusCode = HttpStatus.badGateway;
response.headers
..contentType = ContentType.HTML
..set(HttpHeaders.SERVER, 'angel_hot');
if (request.headers
.value(HttpHeaders.ACCEPT_ENCODING)
?.toLowerCase()
?.contains('gzip') ==
true) {
response
..headers.set(HttpHeaders.CONTENT_ENCODING, 'gzip')
..add(gzip.encode(utf8.encode(_renderer.render(doc))));
} else
response.write(_renderer.render(doc));
response.close();
}
Future _handle(HttpRequest request) {
return _server.handleRequest(request);
}
Future handleRequest(HttpRequest request) async { Future handleRequest(HttpRequest request) async {
if (_server != null) if (_server != null)
return await _server.handleRequest(request); return await _handle(request);
else if (timeout == null) else if (timeout == null)
_requestQueue.add(request); _requestQueue.add(request);
else { else {
@ -72,48 +124,18 @@ class HotReloader {
new Timer(timeout, () { new Timer(timeout, () {
if (_requestQueue.remove(request)) { if (_requestQueue.remove(request)) {
// Send 502 response // Send 502 response
var doc = html(lang: 'en', c: [ sendError(request, HttpStatus.badGateway, '502 Bad Gateway',
head(c: [ 'Request timed out after ${timeout.inMilliseconds}ms.');
meta(
name: 'viewport',
content: 'width=device-width, initial-scale=1'),
title(c: [text('502 Bad Gateway')])
]),
body(c: [
h1(c: [text('502 Bad Gateway')]),
i(c: [
text('Request timed out after ${timeout.inMilliseconds}ms.')
])
])
]);
var response = request.response;
response.statusCode = HttpStatus.BAD_GATEWAY;
response.headers
..contentType = ContentType.HTML
..set(HttpHeaders.SERVER, 'angel_hot');
if (request.headers
.value(HttpHeaders.ACCEPT_ENCODING)
?.toLowerCase()
?.contains('gzip') ==
true) {
response
..headers.set(HttpHeaders.CONTENT_ENCODING, 'gzip')
..add(GZIP.encode(UTF8.encode(_renderer.render(doc))));
} else
response.write(_renderer.render(doc));
response.close();
} }
}); });
} }
} }
Future<Angel> _generateServer() async { Future<AngelHttp> _generateServer() async {
var s = await generator() as Angel; var s = await generator();
await Future.forEach(s.startupHooks, s.configure); await Future.forEach(s.startupHooks, s.configure);
s.optimizeForProduction(); s.optimizeForProduction();
return s; return new AngelHttp(s);
} }
/// Starts listening to requests and filesystem events. /// Starts listening to requests and filesystem events.
@ -122,17 +144,21 @@ class HotReloader {
print( print(
'WARNING: You have instantiated a HotReloader without providing any filesystem paths to watch.'); 'WARNING: You have instantiated a HotReloader without providing any filesystem paths to watch.');
var s = _server = await _generateServer(); _client = await vm.vmServiceConnect(
while (!_requestQueue.isEmpty) vmServiceHost ?? 'localhost', vmServicePort ?? 8181);
await s.handleRequest(_requestQueue.removeFirst()); _vmachine ??= await _client.getVM();
_mainIsolate ??= _vmachine.isolates.first;
await _client.setExceptionPauseMode(_mainIsolate.id, 'None');
_server = await _generateServer();
while (!_requestQueue.isEmpty) await _handle(_requestQueue.removeFirst());
_onChange.stream _onChange.stream
.transform(new _Debounce(new Duration(seconds: 1))) .transform(new _Debounce(new Duration(seconds: 1)))
.listen(_handleWatchEvent); .listen(_handleWatchEvent);
await _listenToFilesystem(); await _listenToFilesystem();
var server = await HttpServer.bind( var server = await HttpServer.bind(address ?? '127.0.0.1', port ?? 0);
address ?? InternetAddress.LOOPBACK_IP_V4, port ?? 0);
server.listen(handleRequest); server.listen(handleRequest);
return server; return server;
} }
@ -174,14 +200,14 @@ class HotReloader {
_listen() async { _listen() async {
try { try {
var stat = await FileStat.stat(path); var stat = await FileStat.stat(path);
if (stat.type == FileSystemEntityType.LINK) { if (stat.type == FileSystemEntityType.link) {
var lnk = new Link(path); var lnk = new Link(path);
var p = await lnk.resolveSymbolicLinks(); var p = await lnk.resolveSymbolicLinks();
return await _listenToStat(p); return await _listenToStat(p);
} else if (stat.type == FileSystemEntityType.FILE) { } else if (stat.type == FileSystemEntityType.file) {
var file = new File(path); var file = new File(path);
if (!await file.exists()) return null; if (!await file.exists()) return null;
} else if (stat.type == FileSystemEntityType.DIRECTORY) { } else if (stat.type == FileSystemEntityType.directory) {
var dir = new Directory(path); var dir = new Directory(path);
if (!await dir.exists()) return null; if (!await dir.exists()) return null;
} else } else
@ -204,7 +230,8 @@ class HotReloader {
if (r == null) { if (r == null) {
print( print(
'WARNING: Unable to watch path "$path" from working directory "${Directory.current.path}". Please ensure that it exists.'); 'WARNING: Unable to watch path "$path" from working directory "${Directory
.current.path}". Please ensure that it exists.');
} }
} }
@ -216,27 +243,24 @@ class HotReloader {
// Do this asynchronously, because we really don't care about the old server anymore. // Do this asynchronously, because we really don't care about the old server anymore.
new Future(() async { new Future(() async {
// Disconnect active WebSockets // Disconnect active WebSockets
var ws = old.container.make(AngelWebSocket) as AngelWebSocket; var ws = old.app.container.make(AngelWebSocket) as AngelWebSocket;
for (var client in ws.clients) { for (var client in ws.clients) {
try { try {
await client.close(WebSocketStatus.GOING_AWAY); await client.close(WebSocketStatus.goingAway);
} catch (e) { } catch (e) {
stderr.writeln( stderr.writeln(
'Couldn\'t close WebSocket from session #${client.request.session.id}: $e'); 'Couldn\'t close WebSocket from session #${client.request
.session.id}: $e');
} }
} }
Future.forEach(old.shutdownHooks, old.configure); Future.forEach(old.app.shutdownHooks, old.app.configure);
}); });
} }
_server = null; _server = null;
_client ??= await vm.vmServiceConnect( var report = await _client.reloadSources(_mainIsolate.id);
vmServiceHost ?? 'localhost', vmServicePort ?? 8181);
var vmachine = await _client.getVM();
var mainIsolate = vmachine.isolates.first;
var report = await _client.reloadSources(mainIsolate.id);
if (!report.success) { if (!report.success) {
stderr.writeln('Hot reload failed!!!'); stderr.writeln('Hot reload failed!!!');
@ -246,12 +270,11 @@ class HotReloader {
var s = await _generateServer(); var s = await _generateServer();
_server = s; _server = s;
while (!_requestQueue.isEmpty) while (!_requestQueue.isEmpty) await _handle(_requestQueue.removeFirst());
await s.handleRequest(_requestQueue.removeFirst());
} }
} }
class _Debounce<S> implements StreamTransformer<S, S> { class _Debounce<S> extends StreamTransformerBase<S, S> {
final Duration _delay; final Duration _delay;
const _Debounce(this._delay); const _Debounce(this._delay);

View file

@ -1,16 +1,20 @@
name: angel_hot name: angel_hot
description: Supports hot reloading of Angel servers on file changes. description: Supports hot reloading of Angel servers on file changes.
version: 1.1.0 version: 1.1.1
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/hot homepage: https://github.com/angel-dart/hot
environment: environment:
sdk: ">=1.19.0" sdk: ">=1.19.0 <3.0.0"
dependencies: dependencies:
angel_framework: ^1.1.0-alpha angel_framework: ^1.1.0
angel_websocket: ^1.1.0-alpha angel_websocket: ^1.1.0-alpha
dart2_constant: ^1.0.0
glob: ^1.0.0
html_builder: ^1.0.0 html_builder: ^1.0.0
vm_service_lib: 0.3.5 vm_service_lib: ^0.3.5
watcher: ^0.9.0
dev_dependencies: dev_dependencies:
angel_test: ^1.0.0 angel_test: ^1.0.0
http: ^0.11.3 http: ^0.11.3
logging: ^0.11.0
test: ^0.12.15 test: ^0.12.15