Migrating to dart2 & angel2

This commit is contained in:
denkuy 2018-11-02 05:22:32 +01:00
parent 103a4340af
commit 7c702012c1
6 changed files with 106 additions and 80 deletions

View file

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_proxy/angel_proxy.dart'; import 'package:angel_proxy/angel_proxy.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -19,7 +20,7 @@ main() async {
publicPath: '/pub', publicPath: '/pub',
timeout: timeout, timeout: timeout,
); );
app.use(pubProxy.handleRequest); app.all("/pub/*", pubProxy.handleRequest);
// Pub's HTML assumes that the site's styles, etc. are on the absolute path `/static`. // Pub's HTML assumes that the site's styles, etc. are on the absolute path `/static`.
// This is not the case here. Let's patch that up: // This is not the case here. Let's patch that up:
@ -35,10 +36,10 @@ main() async {
timeout: timeout, timeout: timeout,
recoverFrom404: false, recoverFrom404: false,
); );
app.use(dartlangProxy.handleRequest); app.all('*', dartlangProxy.handleRequest);
// In case we can't connect to dartlang.org, show an error. // In case we can't connect to dartlang.org, show an error.
app.use('Couldn\'t connect to Pub or dartlang.'); app.fallback((req, res) => res.write('Couldn\'t connect to Pub or dartlang.'));
app.logger = new Logger('angel') app.logger = new Logger('angel')
..onRecord.listen( ..onRecord.listen(
@ -49,7 +50,7 @@ main() async {
}, },
); );
var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 8080); var server = await AngelHttp(app).startServer(InternetAddress.loopbackIPv4, 8080);
print('Listening at http://${server.address.address}:${server.port}'); print('Listening at http://${server.address.address}:${server.port}');
print('Check this out! http://${server.address.address}:${server.port}/pub/packages/angel_framework'); print('Check this out! http://${server.address.address}:${server.port}/pub/packages/angel_framework');
} }

View file

@ -1,18 +1,18 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:http/src/base_client.dart' as http; import 'package:http_parser/http_parser.dart';
import 'package:http/src/request.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/src/response.dart' as http;
import 'package:http/src/streamed_response.dart' as http;
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
final MediaType _fallbackMediaType = MediaType('application', 'octet-stream');
class Proxy { class Proxy {
String _prefix; String _prefix;
final Angel app; final Angel app;
final http.BaseClient httpClient; final http.Client httpClient;
/// If `true` (default), then the plug-in will ignore failures to connect to the proxy, and allow other handlers to run. /// If `true` (default), then the plug-in will ignore failures to connect to the proxy, and allow other handlers to run.
final bool recoverFromDead; final bool recoverFromDead;
@ -20,6 +20,8 @@ class Proxy {
final String host, mapTo, publicPath; final String host, mapTo, publicPath;
final int port; final int port;
final String protocol; final String protocol;
/// If `null` then no timout is added for requests
final Duration timeout; final Duration timeout;
Proxy( Proxy(
@ -34,7 +36,10 @@ class Proxy {
this.recoverFrom404: true, this.recoverFrom404: true,
this.timeout, this.timeout,
}) { }) {
_prefix = publicPath.replaceAll(_straySlashes, ''); if (this.recoverFromDead == null) throw ArgumentError.notNull("recoverFromDead");
if (this.recoverFrom404 == null) throw ArgumentError.notNull("recoverFrom404");
_prefix = publicPath?.replaceAll(_straySlashes, '') ?? '';
} }
void close() => httpClient.close(); void close() => httpClient.close();
@ -43,20 +48,17 @@ class Proxy {
Future<bool> handleRequest(RequestContext req, ResponseContext res) { Future<bool> handleRequest(RequestContext req, ResponseContext res) {
var path = req.path.replaceAll(_straySlashes, ''); var path = req.path.replaceAll(_straySlashes, '');
if (_prefix?.isNotEmpty == true) { if (_prefix.isNotEmpty) {
if (!path.startsWith(_prefix)) if (!path.startsWith(_prefix)) return new Future<bool>.value(true);
return new Future<bool>.value(true);
else {
path = path.replaceFirst(_prefix, '').replaceAll(_straySlashes, ''); path = path.replaceFirst(_prefix, '').replaceAll(_straySlashes, '');
} }
}
return servePath(path, req, res); return servePath(path, req, res);
} }
/// Proxies a request to the given path on the remote server. /// Proxies a request to the given path on the remote server.
Future<bool> servePath( Future<bool> servePath(String path, RequestContext req, ResponseContext res) async {
String path, RequestContext req, ResponseContext res) async {
http.StreamedResponse rs; http.StreamedResponse rs;
final mapping = '$mapTo/$path'.replaceAll(_straySlashes, ''); final mapping = '$mapTo/$path'.replaceAll(_straySlashes, '');
@ -74,8 +76,7 @@ class Proxy {
'host': port == null ? host : '$host:$port', 'host': port == null ? host : '$host:$port',
'x-forwarded-for': req.remoteAddress.address, 'x-forwarded-for': req.remoteAddress.address,
'x-forwarded-port': req.uri.port.toString(), 'x-forwarded-port': req.uri.port.toString(),
'x-forwarded-host': 'x-forwarded-host': req.headers.host ?? req.headers.value('host') ?? 'none',
req.headers.host ?? req.headers.value('host') ?? 'none',
'x-forwarded-proto': protocol, 'x-forwarded-proto': protocol,
}; };
@ -83,19 +84,18 @@ class Proxy {
headers[name] = values.join(','); headers[name] = values.join(',');
}); });
headers['cookie'] = headers[HttpHeaders.cookieHeader] = req.cookies.map<String>((c) => '${c.name}=${c.value}').join('; ');
req.cookies.map<String>((c) => '${c.name}=${c.value}').join('; ');
var body; var body;
if (req.method != 'GET' && app.storeOriginalBuffer == true) { if (req.method != 'GET' && app.keepRawRequestBuffers == true) {
await req.parse(); body = (await req.parse()).originalBuffer;
if (req.originalBuffer?.isNotEmpty == true) body = req.originalBuffer;
} }
var rq = new http.Request(req.method, Uri.parse(url)); var rq = new http.Request(req.method, Uri.parse(url));
rq.headers.addAll(headers); rq.headers.addAll(headers);
rq.headers['host'] = rq.url.host; rq.headers['host'] = rq.url.host;
rq.encoding = Utf8Codec(allowMalformed: true);
if (body != null) rq.bodyBytes = body; if (body != null) rq.bodyBytes = body;
@ -106,37 +106,51 @@ class Proxy {
if (timeout != null) future = future.timeout(timeout); if (timeout != null) future = future.timeout(timeout);
rs = await future; rs = await future;
} on TimeoutException catch (e, st) { } on TimeoutException catch (e, st) {
if (recoverFromDead != false) if (recoverFromDead) return true;
return true;
else
throw new AngelHttpException( throw new AngelHttpException(
e, e,
stackTrace: st, stackTrace: st,
statusCode: 504, statusCode: 504,
message: message: 'Connection to remote host "$host" timed out after ${timeout.inMilliseconds}ms.',
'Connection to remote host "$host" timed out after ${timeout.inMilliseconds}ms.',
); );
} catch (e) { } catch (e) {
if (recoverFromDead != false) if (recoverFromDead) return true;
return true;
else
rethrow; rethrow;
} }
if (rs.statusCode == 404 && recoverFrom404 != false) return true; if (rs.statusCode == 404 && recoverFrom404) return true;
if (rs.contentLength == 0 && recoverFromDead) return true;
MediaType mediaType;
if (rs.headers.containsKey(HttpHeaders.contentTypeHeader)) {
try {
mediaType = MediaType.parse(rs.headers[HttpHeaders.contentTypeHeader]);
} on FormatException catch (e, st) {
if (recoverFromDead) return true;
throw new AngelHttpException(
e,
stackTrace: st,
statusCode: 504,
message: 'Host "$host" returned a malformed content-type',
);
}
} else {
mediaType = _fallbackMediaType;
}
var proxiedHeaders = new Map<String, String>.from(rs.headers);
// http/2 client implementations usually get confused by transfer-encoding
res res
..contentType = mediaType
..statusCode = rs.statusCode ..statusCode = rs.statusCode
..headers.addAll(new Map<String, String>.from(rs.headers) ..headers.addAll(proxiedHeaders);
..remove(HttpHeaders.TRANSFER_ENCODING));
if (rs.contentLength == 0 && recoverFromDead != false) return true;
var stream = rs.stream; var stream = rs.stream;
if (rs.headers['content-encoding'] == 'gzip') // [upgrading to dart2] Keeping this workaround as a reference. It's not properly typed for dart2
stream = stream.transform(GZIP.encoder); //if (rs.headers[HttpHeaders.contentEncodingHeader] == 'gzip') stream = stream.transform(gzip.encoder);
await stream.pipe(res); await stream.pipe(res);

View file

@ -4,11 +4,12 @@ version: 1.1.1
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/proxy homepage: https://github.com/angel-dart/proxy
environment: environment:
sdk: ">=1.19.0 <3.0.0" sdk: ">=2.0.0 <3.0.0"
dependencies: dependencies:
angel_framework: ^1.1.0-alpha angel_framework: ^2.0.0-alpha # The core server library.
http: ^0.11.3 http: ^0.11.3
dev_dependencies:
angel_test: ^1.1.0-alpha dev_dependencies:
test: ^0.12.15 angel_test: 2.0.0-alpha
test: ^1.0.0

View file

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_proxy/angel_proxy.dart'; import 'package:angel_proxy/angel_proxy.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -14,10 +15,11 @@ main() {
String url; String url;
setUp(() async { setUp(() async {
app = new Angel()..storeOriginalBuffer = true; app = new Angel()..keepRawRequestBuffers = true;
var appHttp = AngelHttp(app);
var httpClient = new http.Client(); var httpClient = new http.Client();
testServer = await testApp().startServer(); testServer = await startTestServer();
var proxy1 = new Proxy( var proxy1 = new Proxy(
app, app,
@ -34,10 +36,10 @@ main() {
mapTo: '/foo', mapTo: '/foo',
); );
app.use(proxy1.handleRequest); app.all("/proxy/*", proxy1.handleRequest);
app.use(proxy2.handleRequest); app.all("*", proxy2.handleRequest);
app.use((req, res) { app.fallback((req, res) {
print('Intercepting empty from ${req.uri}'); print('Intercepting empty from ${req.uri}');
res.write('intercept empty'); res.write('intercept empty');
}); });
@ -54,7 +56,9 @@ main() {
if (rec.stackTrace != null) print(rec.stackTrace); if (rec.stackTrace != null) print(rec.stackTrace);
}); });
server = await app.startServer(); await appHttp.startServer();
server = appHttp.httpServer;
url = 'http://${server.address.address}:${server.port}'; url = 'http://${server.address.address}:${server.port}';
}); });
@ -68,7 +72,7 @@ main() {
test('publicPath', () async { test('publicPath', () async {
final response = await client.get('$url/proxy/hello'); final response = await client.get('$url/proxy/hello');
print('Response: ${response.body}'); print('Response: ${response.body}');
expect(response.body, equals('"world"')); expect(response.body, equals('world'));
}); });
test('empty', () async { test('empty', () async {
@ -80,13 +84,12 @@ main() {
test('mapTo', () async { test('mapTo', () async {
final response = await client.get('$url/bar'); final response = await client.get('$url/bar');
print('Response: ${response.body}'); print('Response: ${response.body}');
expect(response.body, equals('"baz"')); expect(response.body, equals('baz'));
}); });
test('original buffer', () async { test('original buffer', () async {
var response = await client.post('$url/proxy/body', var response = await client
body: JSON.encode({'foo': 'bar'}), .post('$url/proxy/body', body: json.encode({'foo': 'bar'}), headers: {'content-type': 'application/json'});
headers: {'content-type': 'application/json'});
print('Response: ${response.body}'); print('Response: ${response.body}');
expect(response.body, isNotEmpty); expect(response.body, isNotEmpty);
expect(response.body, isNot('intercept empty')); expect(response.body, isNot('intercept empty'));

View file

@ -1,18 +1,26 @@
import 'dart:async';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
Angel testApp() { Future<HttpServer> startTestServer() {
final app = new Angel()..lazyParseBodies = true; final app = new Angel()
..eagerParseRequestBodies = false
..keepRawRequestBuffers = true;
app.get('/hello', 'world'); app.get('/hello', (req, res) => res.write('world'));
app.get('/foo/bar', 'baz'); app.get('/foo/bar', (req, res) => res.write('baz'));
app.post('/body', (RequestContext req, res) async { app.post('/body', (RequestContext req, res) async {
var body = await req.lazyBody(); var body = await req.parseBody();
print('Body: $body'); app.logger.info('Body: $body');
return body; return body;
}); });
app.logger = new Logger('testApp'); app.logger = new Logger('testApp');
var server = AngelHttp(app);
app.dumpTree();
return app..dumpTree(); return server.startServer();
} }

View file

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_proxy/angel_proxy.dart'; import 'package:angel_proxy/angel_proxy.dart';
import 'package:angel_test/angel_test.dart'; import 'package:angel_test/angel_test.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -10,43 +11,42 @@ import 'package:test/test.dart';
main() { main() {
Angel app, testApp; Angel app, testApp;
TestClient client; TestClient client;
Proxy layer;
setUp(() async { setUp(() async {
testApp = new Angel(); testApp = new Angel();
testApp.get('/foo', (req, res) async { testApp.get('/foo', (req, res) async {
res.write('pub serve'); res.write('pub serve');
}); });
testApp.get('/empty', (req, res) => res.end()); testApp.get('/empty', (req, res) => res.close());
testApp.responseFinalizers.add((req, ResponseContext res) async { testApp.responseFinalizers.add((req, res) async {
print('OUTGOING: ' + new String.fromCharCodes(res.buffer.toBytes())); print('OUTGOING: ' + new String.fromCharCodes(res.buffer.toBytes()));
}); });
testApp.injectEncoders({'gzip': GZIP.encoder}); testApp.encoders.addAll({'gzip': gzip.encoder});
var server = await testApp.startServer(); var server = await AngelHttp(testApp).startServer();
app = new Angel(); app = new Angel();
app.get('/bar', (req, res) => res.write('normal')); app.get('/bar', (req, res) => res.write('normal'));
var httpClient = new http.Client(); var httpClient = new http.Client();
var layer = new Proxy( layer = new Proxy(
app, app,
httpClient, httpClient,
server.address.address, server.address.address,
port: server.port, port: server.port,
publicPath: '/proxy', publicPath: '/proxy',
); );
app.use(layer.handleRequest); app.all("*", layer.handleRequest);
app.responseFinalizers.add((req, ResponseContext res) async { app.responseFinalizers.add((req, ResponseContext res) async {
print('Normal. Buf: ' + print('Normal. Buf: ' + new String.fromCharCodes(res.buffer.toBytes()) + ', headers: ${res.headers}');
new String.fromCharCodes(res.buffer.toBytes()) +
', headers: ${res.headers}');
}); });
app.injectEncoders({'gzip': GZIP.encoder}); app.encoders.addAll({'gzip': gzip.encoder});
client = await connectTo(app); client = await connectTo(app);
}); });
@ -61,11 +61,10 @@ main() {
test('proxied', () async { test('proxied', () async {
var rq = new MockHttpRequest('GET', Uri.parse('/proxy/foo'))..close(); var rq = new MockHttpRequest('GET', Uri.parse('/proxy/foo'))..close();
await app.handleRequest(rq); var rqc = await HttpRequestContext.from(rq, app, '/proxy/foo');
var response = await rq.response var rsc = HttpResponseContext(rq.response, app);
.transform(GZIP.decoder) await app.executeHandler(layer, rqc, rsc);
.transform(UTF8.decoder) var response = await rq.response.transform(gzip.decoder).transform(utf8.decoder).join();
.join();
expect(response, 'pub serve'); expect(response, 'pub serve');
}); });