This commit is contained in:
Tobe O 2018-11-08 17:13:26 -05:00
parent 9ff915a105
commit 8291b3b280
7 changed files with 71 additions and 36 deletions

View file

@ -1,3 +1,5 @@
# 2.0.0
* Updates for Angel 2. Big thanks to @denkuy!
# 1.1.1 # 1.1.1
Removed reference to `io`; now works with HTTP/2. * Removed reference to `io`; now works with HTTP/2. Thanks to @daniel-v!
Thanks to @daniel-v!

View file

@ -1,2 +1,3 @@
analyzer: analyzer:
strong-mode: true strong-mode:
implicit-casts: false

View file

@ -2,14 +2,14 @@ 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_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/io_client.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
final Duration timeout = new Duration(seconds: 5); final Duration timeout = new Duration(seconds: 5);
main() async { main() async {
var app = new Angel(); var app = new Angel();
var client = new http.Client(); var client = new http.IOClient();
// Forward any /api requests to pub. // Forward any /api requests to pub.
// By default, if the host throws a 404, the request will fall through to the next handler. // By default, if the host throws a 404, the request will fall through to the next handler.
@ -37,7 +37,8 @@ main() async {
app.all('*', 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.fallback((req, res) => res.write('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(
@ -48,7 +49,9 @@ main() async {
}, },
); );
var server = await AngelHttp(app).startServer(InternetAddress.loopbackIPv4, 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

@ -11,7 +11,7 @@ final MediaType _fallbackMediaType = MediaType('application', 'octet-stream');
class Proxy { class Proxy {
String _prefix; String _prefix;
final http.Client httpClient; final http.BaseClient 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;
@ -34,8 +34,10 @@ class Proxy {
this.recoverFrom404: true, this.recoverFrom404: true,
this.timeout, this.timeout,
}) { }) {
if (this.recoverFromDead == null) throw ArgumentError.notNull("recoverFromDead"); if (this.recoverFromDead == null)
if (this.recoverFrom404 == null) throw ArgumentError.notNull("recoverFrom404"); throw ArgumentError.notNull("recoverFromDead");
if (this.recoverFrom404 == null)
throw ArgumentError.notNull("recoverFrom404");
_prefix = publicPath?.replaceAll(_straySlashes, '') ?? ''; _prefix = publicPath?.replaceAll(_straySlashes, '') ?? '';
} }
@ -56,7 +58,8 @@ class Proxy {
} }
/// 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(String path, RequestContext req, ResponseContext res) async { Future<bool> servePath(
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,7 +77,8 @@ 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': req.headers.host ?? req.headers.value('host') ?? 'none', 'x-forwarded-host':
req.headers.host ?? req.headers.value('host') ?? 'none',
'x-forwarded-proto': protocol, 'x-forwarded-proto': protocol,
}; };
@ -82,9 +86,10 @@ class Proxy {
headers[name] = values.join(','); headers[name] = values.join(',');
}); });
headers[HttpHeaders.cookieHeader] = req.cookies.map<String>((c) => '${c.name}=${c.value}').join('; '); headers[HttpHeaders.cookieHeader] =
req.cookies.map<String>((c) => '${c.name}=${c.value}').join('; ');
var body; List<int> body;
if (req.method != 'GET' && req.app.keepRawRequestBuffers == true) { if (req.method != 'GET' && req.app.keepRawRequestBuffers == true) {
body = (await req.parse()).originalBuffer; body = (await req.parse()).originalBuffer;
@ -110,7 +115,8 @@ class Proxy {
e, e,
stackTrace: st, stackTrace: st,
statusCode: 504, statusCode: 504,
message: 'Connection to remote host "$host" timed out after ${timeout.inMilliseconds}ms.', message:
'Connection to remote host "$host" timed out after ${timeout.inMilliseconds}ms.',
); );
} catch (e) { } catch (e) {
if (recoverFromDead) return true; if (recoverFromDead) return true;
@ -146,9 +152,12 @@ class Proxy {
rs.headers[HttpHeaders.transferEncodingHeader]?.isNotEmpty == true; rs.headers[HttpHeaders.transferEncodingHeader]?.isNotEmpty == true;
var proxiedHeaders = new Map<String, String>.from(rs.headers) var proxiedHeaders = new Map<String, String>.from(rs.headers)
..remove(HttpHeaders.contentEncodingHeader) // drop, http.Client has decoded ..remove(
..remove(HttpHeaders.transferEncodingHeader) // drop, http.Client has decoded HttpHeaders.contentEncodingHeader) // drop, http.Client has decoded
..[HttpHeaders.contentLengthHeader] = "${isContentLengthUnknown ? '-1' : rs.contentLength}"; ..remove(
HttpHeaders.transferEncodingHeader) // drop, http.Client has decoded
..[HttpHeaders.contentLengthHeader] =
"${isContentLengthUnknown ? '-1' : rs.contentLength}";
res res
..contentType = mediaType ..contentType = mediaType

View file

@ -1,15 +1,15 @@
name: angel_proxy name: angel_proxy
description: Angel middleware to forward requests to another server (i.e. pub serve). description: Angel middleware to forward requests to another server (i.e. pub serve).
version: 1.1.1 version: 2.0.0
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: ">=2.0.0 <3.0.0" sdk: ">=2.0.0 <3.0.0"
dependencies: dependencies:
angel_framework: ^2.0.0-alpha # The core server library. angel_framework: ^2.0.0-alpha
http: ^0.11.3 http: ^0.12.0
dev_dependencies: dev_dependencies:
angel_test: 2.0.0-alpha angel_test: ^2.0.0-alpha
logging:
test: ^1.0.0 test: ^1.0.0

View file

@ -3,21 +3,21 @@ 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_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/io_client.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'common.dart'; import 'common.dart';
main() { main() {
Angel app; Angel app;
http.Client client = new http.Client(); var client = new http.IOClient();
HttpServer server, testServer; HttpServer server, testServer;
String url; String url;
setUp(() async { setUp(() async {
app = new Angel()..keepRawRequestBuffers = true; app = new Angel()..keepRawRequestBuffers = true;
var appHttp = AngelHttp(app); var appHttp = AngelHttp(app);
var httpClient = new http.Client(); var httpClient = new http.IOClient();
testServer = await startTestServer(); testServer = await startTestServer();
@ -86,8 +86,9 @@ main() {
}); });
test('original buffer', () async { test('original buffer', () async {
var response = await client var response = await client.post('$url/proxy/body',
.post('$url/proxy/body', body: json.encode({'foo': 'bar'}), headers: {'content-type': 'application/json'}); body: json.encode({'foo': 'bar'}),
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

@ -4,7 +4,8 @@ import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.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/io_client.dart' as http;
import 'package:logging/logging.dart';
import 'package:mock_request/mock_request.dart'; import 'package:mock_request/mock_request.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -16,6 +17,7 @@ main() {
setUp(() async { setUp(() async {
testApp = new Angel(); testApp = new Angel();
testApp.get('/foo', (req, res) async { testApp.get('/foo', (req, res) async {
res.useBuffer();
res.write('pub serve'); res.write('pub serve');
}); });
testApp.get('/empty', (req, res) => res.close()); testApp.get('/empty', (req, res) => res.close());
@ -29,9 +31,13 @@ main() {
var server = await AngelHttp(testApp).startServer(); var server = await AngelHttp(testApp).startServer();
app = new Angel(); app = new Angel();
app.fallback((req, res) {
res.useBuffer();
return true;
});
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.IOClient();
layer = new Proxy( layer = new Proxy(
httpClient, httpClient,
@ -39,15 +45,25 @@ main() {
port: server.port, port: server.port,
publicPath: '/proxy', publicPath: '/proxy',
); );
app.all("*", layer.handleRequest);
app.responseFinalizers.add((req, ResponseContext res) async { app.fallback(layer.handleRequest);
print('Normal. Buf: ' + new String.fromCharCodes(res.buffer.toBytes()) + ', headers: ${res.headers}');
app.responseFinalizers.add((req, res) async {
print('Normal. Buf: ' +
new String.fromCharCodes(res.buffer.toBytes()) +
', headers: ${res.headers}');
}); });
app.encoders.addAll({'gzip': gzip.encoder}); app.encoders.addAll({'gzip': gzip.encoder});
client = await connectTo(app); client = await connectTo(app);
app.logger = testApp.logger = new Logger('proxy')
..onRecord.listen((rec) {
print(rec);
if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace);
});
}); });
tearDown(() async { tearDown(() async {
@ -62,8 +78,11 @@ main() {
var rq = new MockHttpRequest('GET', Uri.parse('/proxy/foo'))..close(); var rq = new MockHttpRequest('GET', Uri.parse('/proxy/foo'))..close();
var rqc = await HttpRequestContext.from(rq, app, '/proxy/foo'); var rqc = await HttpRequestContext.from(rq, app, '/proxy/foo');
var rsc = HttpResponseContext(rq.response, app); var rsc = HttpResponseContext(rq.response, app);
await app.executeHandler(layer, rqc, rsc); await app.executeHandler(layer.handleRequest, rqc, rsc);
var response = await rq.response.transform(gzip.decoder).transform(utf8.decoder).join(); var response = await rq.response
//.transform(gzip.decoder)
.transform(utf8.decoder)
.join();
expect(response, 'pub serve'); expect(response, 'pub serve');
}); });