From e05d35ee67a5705e8849da6f6310fa039fd833a5 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 20 Jun 2017 15:31:35 -0400 Subject: [PATCH] 1.0.8 --- .idea/angel_proxy.iml | 2 +- .vscode/launch.json | 11 ++++ README.md | 3 +- example/multiple.dart | 38 +++++++++++++ lib/src/proxy_layer.dart | 101 +++++++++++++++++++++-------------- lib/src/pub_serve_layer.dart | 6 ++- pubspec.yaml | 6 ++- 7 files changed, 121 insertions(+), 46 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 example/multiple.dart diff --git a/.idea/angel_proxy.iml b/.idea/angel_proxy.iml index 085f4ea4..f5c40a17 100644 --- a/.idea/angel_proxy.iml +++ b/.idea/angel_proxy.iml @@ -5,6 +5,7 @@ + @@ -13,7 +14,6 @@ - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..b78e04ca --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Multiple Proxies", + "type": "dart-cli", + "request": "launch", + "program": "${workspaceRoot}/example/multiple.dart" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 0b859cd7..bdd1658a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # angel_proxy -[![version 1.0.7](https://img.shields.io/badge/version-1.0.7-brightgreen.svg)](https://pub.dartlang.org/packages/angel_proxy) -[![build status](https://travis-ci.org/angel-dart/proxy.svg)](https://travis-ci.org/angel-dart/proxy) +[![Pub](https://img.shields.io/pub/v/angel_proxy.svg)](https://pub.dartlang.org/packages/angel_proxy)[![build status](https://travis-ci.org/angel-dart/proxy.svg)](https://travis-ci.org/angel-dart/proxy) Angel middleware to forward requests to another server (i.e. pub serve). diff --git a/example/multiple.dart b/example/multiple.dart new file mode 100644 index 00000000..0bce8c21 --- /dev/null +++ b/example/multiple.dart @@ -0,0 +1,38 @@ +import 'dart:io'; +import 'package:angel_diagnostics/angel_diagnostics.dart'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_proxy/angel_proxy.dart'; + +final Duration TIMEOUT = new Duration(seconds: 5); + +main() async { + var app = new Angel(); + + // Forward any /api requests to pub. + // By default, if the host throws a 404, the request will fall through to the next handler. + var pubProxy = new ProxyLayer('pub.dartlang.org', 80, + publicPath: '/pub', timeout: TIMEOUT); + await app.configure(pubProxy); + + // 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: + app.get('/static/*', (RequestContext req, res) { + return pubProxy.serveFile(req.path, req, res); + }); + + // Anything else should fall through to dartlang.org. + await app.configure(new ProxyLayer('dartlang.org', 80, timeout: TIMEOUT)); + + // In case we can't connect to dartlang.org, show an error. + app.after.add('Couldn\'t connect to Pub or dartlang.'); + + await app.configure(logRequests()); + + app.fatalErrorStream.listen((AngelFatalError e) { + print(e.error); + print(e.stack); + }); + + var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 8080); + print('Listening at http://${server.address.address}:${server.port}'); +} diff --git a/lib/src/proxy_layer.dart b/lib/src/proxy_layer.dart index 09430d59..02da0210 100644 --- a/lib/src/proxy_layer.dart +++ b/lib/src/proxy_layer.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:angel_framework/angel_framework.dart'; @@ -51,30 +52,31 @@ class ProxyLayer { final String host, mapTo, publicPath; final int port; final String protocol; + final Duration timeout; - ProxyLayer(this.host, this.port, - {this.debug: false, - this.mapTo: '/', - this.publicPath: '/', - this.protocol: 'http', - this.recoverFromDead: true, - this.recoverFrom404: true, - this.streamToIO: false, - SecurityContext securityContext}) { + ProxyLayer( + this.host, + this.port, { + this.debug: false, + this.mapTo: '/', + this.publicPath: '/', + this.protocol: 'http', + this.recoverFromDead: true, + this.recoverFrom404: true, + this.streamToIO: false, + this.timeout, + SecurityContext securityContext, + }) { _client = new HttpClient(context: securityContext); _prefix = publicPath.replaceAll(_straySlashes, ''); } call(Angel app) async => serve(this.app = app); - _printDebug(msg) { - if (debug == true) print(msg); - } - void close() => _client.close(force: true); void serve(Router router) { - _printDebug('Public path prefix: "$_prefix"'); + // _printDebug('Public path prefix: "$_prefix"'); handler(RequestContext req, ResponseContext res) async { var path = req.path.replaceAll(_straySlashes, ''); @@ -93,34 +95,55 @@ class ProxyLayer { } // Create mapping - _printDebug('Serving path $_path via proxy'); + // _printDebug('Serving path $_path via proxy'); final mapping = '$mapTo/$_path'.replaceAll(_straySlashes, ''); - _printDebug('Mapped path $_path to path $mapping on proxy $host:$port'); + // _printDebug('Mapped path $_path to path $mapping on proxy $host:$port'); try { - final rq = await _client.open(req.method, host, port, mapping); - _printDebug('Opened client request'); + Future accessRemote() async { + var ips = await InternetAddress.lookup(host); + if (ips.isEmpty) + throw new StateError('Could not resolve remote host "$host".'); + var address = ips.first.address; + final rq = await _client.open(req.method, address, port, mapping); + // _printDebug('Opened client request at "$address:$port/$mapping"'); - copyHeaders(req.headers, rq.headers); - _printDebug('Copied headers'); - rq.cookies.addAll(req.cookies ?? []); - _printDebug('Added cookies'); - rq.headers - .set('X-Forwarded-For', req.io.connectionInfo.remoteAddress.address); - rq.headers - ..set('X-Forwarded-Port', req.io.connectionInfo.remotePort.toString()) - ..set('X-Forwarded-Host', - req.headers.host ?? req.headers.value(HttpHeaders.HOST) ?? 'none') - ..set('X-Forwarded-Proto', protocol); - _printDebug('Added X-Forwarded headers'); + copyHeaders(req.headers, rq.headers); + rq.headers.set(HttpHeaders.HOST, host); + // _printDebug('Copied headers'); + rq.cookies.addAll(req.cookies ?? []); + // _printDebug('Added cookies'); + rq.headers.set( + 'X-Forwarded-For', req.io.connectionInfo.remoteAddress.address); + rq.headers + ..set('X-Forwarded-Port', req.io.connectionInfo.remotePort.toString()) + ..set('X-Forwarded-Host', + req.headers.host ?? req.headers.value(HttpHeaders.HOST) ?? 'none') + ..set('X-Forwarded-Proto', protocol); + // _printDebug('Added X-Forwarded headers'); - if (app.storeOriginalBuffer == true) { - await req.parse(); - if (req.originalBuffer?.isNotEmpty == true) rq.add(req.originalBuffer); + if (app.storeOriginalBuffer == true) { + await req.parse(); + if (req.originalBuffer?.isNotEmpty == true) + rq.add(req.originalBuffer); + } + + await rq.flush(); + return await rq.close(); } - await rq.flush(); - rs = await rq.close(); + var future = accessRemote(); + if (timeout != null) future = future.timeout(timeout); + rs = await future; + } on TimeoutException catch (e, st) { + if (recoverFromDead != false) + return true; + else + throw new AngelHttpException(e, + stackTrace: st, + statusCode: HttpStatus.GATEWAY_TIMEOUT, + message: + 'Connection to remote host "$host" timed out after ${timeout.inMilliseconds}ms.'); } catch (e) { if (recoverFromDead != false) return true; @@ -128,8 +151,8 @@ class ProxyLayer { rethrow; } - _printDebug( - 'Proxy responded to $mapping with status code ${rs.statusCode}'); + // _printDebug( + // 'Proxy responded to $mapping with status code ${rs.statusCode}'); if (rs.statusCode == 404 && recoverFrom404 != false) return true; @@ -137,7 +160,7 @@ class ProxyLayer { ..statusCode = rs.statusCode ..contentType = rs.headers.contentType; - _printDebug('Proxy response headers:\n${rs.headers}'); + // _printDebug('Proxy response headers:\n${rs.headers}'); if (streamToIO == true) { res @@ -150,7 +173,7 @@ class ProxyLayer { if (rs.headers.contentType != null) res.io.headers.contentType = rs.headers.contentType; - _printDebug('Outgoing content length: ${res.io.contentLength}'); + // _printDebug('Outgoing content length: ${res.io.contentLength}'); if (rs.headers[HttpHeaders.CONTENT_ENCODING]?.contains('gzip') == true) { res.io.headers.set(HttpHeaders.CONTENT_ENCODING, 'gzip'); diff --git a/lib/src/pub_serve_layer.dart b/lib/src/pub_serve_layer.dart index f2e8fca5..3ea53d71 100644 --- a/lib/src/pub_serve_layer.dart +++ b/lib/src/pub_serve_layer.dart @@ -11,7 +11,8 @@ class PubServeLayer extends ProxyLayer { String mapTo: '/', int port: 8080, String protocol: 'http', - String publicPath: '/'}) + String publicPath: '/', + Duration timeout}) : super(host, port, debug: debug, mapTo: mapTo, @@ -19,7 +20,8 @@ class PubServeLayer extends ProxyLayer { publicPath: publicPath, recoverFromDead: recoverFromDead != false, recoverFrom404: recoverFrom404 != false, - streamToIO: streamToIO != false); + streamToIO: streamToIO != false, + timeout: timeout); @override void serve(Router router) { diff --git a/pubspec.yaml b/pubspec.yaml index e12c3d69..47b04e9a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_proxy description: Angel middleware to forward requests to another server (i.e. pub serve). -version: 1.0.7 +version: 1.0.8 author: Tobe O homepage: https://github.com/angel-dart/proxy environment: @@ -9,6 +9,8 @@ dependencies: angel_framework: ^1.0.0-dev dev_dependencies: angel_compress: ^1.0.0 + angel_diagnostics: ^1.0.0 angel_test: ^1.0.0 http: ^0.11.3 - test: ^0.12.15 \ No newline at end of file + test: ^0.12.15 + \ No newline at end of file