From dbb483b953f7a61383facd791b79b42997a1053a Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 8 Nov 2018 19:14:32 -0500 Subject: [PATCH] port to uri --- CHANGELOG.md | 10 ++++-- example/main.dart | 17 +++++++-- lib/src/proxy_layer.dart | 74 +++++++++++++++++++++++++++------------- pubspec.yaml | 3 +- test/basic_test.dart | 10 ++---- test/pub_serve_test.dart | 3 +- 6 files changed, 79 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c3a6ec..691074fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ +# 2.1.0 +- Use `Uri` instead of archaic `host`, `port`, and `mapTo`. Also cleaner + safer + easier. + # 2.0.0 -* Updates for Angel 2. Big thanks to @denkuy! + +- Updates for Angel 2. Big thanks to @denkuy! +- Use `package:path` for better path resolution. # 1.1.1 -* Removed reference to `io`; now works with HTTP/2. Thanks to @daniel-v! + +- Removed reference to `io`; now works with HTTP/2. Thanks to @daniel-v! diff --git a/example/main.dart b/example/main.dart index b8fe17fe..e1382445 100644 --- a/example/main.dart +++ b/example/main.dart @@ -15,12 +15,25 @@ main() async { // By default, if the host throws a 404, the request will fall through to the next handler. var pubProxy = new Proxy( client, - 'https://pub.dartlang.org', + Uri.parse('https://pub.dartlang.org'), publicPath: '/pub', timeout: timeout, ); app.all("/pub/*", pubProxy.handleRequest); + // Surprise! We can also proxy WebSockets. + // + // Play around with this at http://www.websocket.org/echo.html. + var echoProxy = new Proxy( + client, + Uri.parse('http://echo.websocket.org'), + publicPath: '/echo', + timeout: timeout, + recoverFromDead: false, + recoverFrom404: false, + ); + app.get('/echo', echoProxy.handleRequest); + // 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) { @@ -30,7 +43,7 @@ main() async { // Anything else should fall through to dartlang.org. var dartlangProxy = new Proxy( client, - 'https://dartlang.org', + Uri.parse('https://dartlang.org'), timeout: timeout, recoverFrom404: false, ); diff --git a/lib/src/proxy_layer.dart b/lib/src/proxy_layer.dart index ab89472f..2d955958 100644 --- a/lib/src/proxy_layer.dart +++ b/lib/src/proxy_layer.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'dart:io'; import 'dart:convert'; import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; import 'package:http_parser/http_parser.dart'; import 'package:http/http.dart' as http; +import 'package:path/path.dart' as p; final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); final MediaType _fallbackMediaType = MediaType('application', 'octet-stream'); @@ -16,20 +18,16 @@ class Proxy { /// 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 recoverFrom404; - final String host, mapTo, publicPath; - final int port; - final String protocol; + final Uri baseUrl; + final String publicPath; /// If `null` then no timout is added for requests final Duration timeout; Proxy( this.httpClient, - this.host, { - this.port, - this.mapTo: '/', + this.baseUrl, { this.publicPath: '/', - this.protocol: 'http', this.recoverFromDead: true, this.recoverFrom404: true, this.timeout, @@ -49,9 +47,11 @@ class Proxy { var path = req.path.replaceAll(_straySlashes, ''); if (_prefix.isNotEmpty) { - if (!path.startsWith(_prefix)) return new Future.value(true); + if (!p.isWithin(_prefix, path) && !p.equals(_prefix, path)) { + return new Future.value(true); + } - path = path.replaceFirst(_prefix, '').replaceAll(_straySlashes, ''); + path = p.relative(path, from: _prefix); } return servePath(path, req, res); @@ -62,24 +62,52 @@ class Proxy { String path, RequestContext req, ResponseContext res) async { http.StreamedResponse rs; - final mapping = '$mapTo/$path'.replaceAll(_straySlashes, ''); + var uri = baseUrl.replace(path: p.join(baseUrl.path, path)); + + print('a $uri'); try { + print(req is HttpRequestContext && + WebSocketTransformer.isUpgradeRequest(req.rawRequest)); + + if (req is HttpRequestContext && + WebSocketTransformer.isUpgradeRequest(req.rawRequest)) { + print('ws!!!'); + res.detach(); + print('detached'); + uri = uri.replace(scheme: uri.scheme == 'https' ? 'wss' : 'ws'); + print(uri); + + try { + var local = await WebSocketTransformer.upgrade(req.rawRequest); + print('local!'); + var remote = await WebSocket.connect(uri.toString()); + print('remote!'); + + dynamic Function(dynamic) log(String type) { + return (x) { + print('$type: $x'); + return x; + }; + } + + local.map(log('local->remote')).pipe(remote); + remote.map(log('local->remote')).pipe(local); + return false; + } catch (e, st) { + throw new AngelHttpException(e, + message: 'Could not connect WebSocket', stackTrace: st); + } + } + Future accessRemote() async { - var url = port == null ? host : '$host:$port'; - url = url.replaceAll(_straySlashes, ''); - url = '$url/$mapping'; - - if (!url.startsWith(protocol)) url = '$protocol://$url'; - url = url.replaceAll(_straySlashes, ''); - var headers = { - 'host': port == null ? host : '$host:$port', + 'host': uri.authority, 'x-forwarded-for': req.remoteAddress.address, 'x-forwarded-port': req.uri.port.toString(), 'x-forwarded-host': req.headers.host ?? req.headers.value('host') ?? 'none', - 'x-forwarded-proto': protocol, + 'x-forwarded-proto': uri.scheme, }; req.headers.forEach((name, values) { @@ -95,7 +123,7 @@ class Proxy { body = (await req.parse()).originalBuffer; } - var rq = new http.Request(req.method, Uri.parse(url)); + var rq = new http.Request(req.method, uri); rq.headers.addAll(headers); rq.headers['host'] = rq.url.host; rq.encoding = Utf8Codec(allowMalformed: true); @@ -116,10 +144,10 @@ class Proxy { stackTrace: st, statusCode: 504, message: - 'Connection to remote host "$host" timed out after ${timeout.inMilliseconds}ms.', + 'Connection to remote host "$uri" timed out after ${timeout.inMilliseconds}ms.', ); } catch (e) { - if (recoverFromDead) return true; + if (recoverFromDead && e is! AngelHttpException) return true; rethrow; } @@ -137,7 +165,7 @@ class Proxy { e, stackTrace: st, statusCode: 504, - message: 'Host "$host" returned a malformed content-type', + message: 'Host "$uri" returned a malformed content-type', ); } } else { diff --git a/pubspec.yaml b/pubspec.yaml index 9cf544ac..e1232d7a 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: 2.0.0 +version: 2.1.0 author: Tobe O homepage: https://github.com/angel-dart/proxy environment: @@ -9,6 +9,7 @@ dependencies: angel_framework: ^2.0.0-alpha http: ^0.12.0 http_parser: ^3.0.0 + path: ^1.0.0 dev_dependencies: angel_test: ^2.0.0-alpha logging: diff --git a/test/basic_test.dart b/test/basic_test.dart index 400b872d..daeb4190 100644 --- a/test/basic_test.dart +++ b/test/basic_test.dart @@ -23,16 +23,10 @@ main() { var proxy1 = new Proxy( httpClient, - testServer.address.address, - port: testServer.port, + new Uri(host: testServer.address.address, port: testServer.port), publicPath: '/proxy', ); - var proxy2 = new Proxy( - httpClient, - testServer.address.address, - port: testServer.port, - mapTo: '/foo', - ); + var proxy2 = new Proxy(httpClient, proxy1.baseUrl.replace(path: '/foo')); app.all("/proxy/*", proxy1.handleRequest); app.all("*", proxy2.handleRequest); diff --git a/test/pub_serve_test.dart b/test/pub_serve_test.dart index 3ad916da..238f5459 100644 --- a/test/pub_serve_test.dart +++ b/test/pub_serve_test.dart @@ -41,8 +41,7 @@ main() { layer = new Proxy( httpClient, - server.address.address, - port: server.port, + new Uri(host: server.address.address, port: server.port), publicPath: '/proxy', );