This commit is contained in:
Tobe O 2019-10-12 11:19:57 -04:00
parent 49ea99ed6c
commit daca263062
7 changed files with 54 additions and 35 deletions

View file

@ -1,3 +1,9 @@
# 2.2.0
* Use `http.Client` instead of `http.BaseClient`, and make it an
optional parameter.
* Allow `baseUrl` to accept `Uri` or `String`.
* Add `Proxy.pushState`.
# 2.1.2 # 2.1.2
* Apply lints. * Apply lints.

View file

@ -10,12 +10,9 @@ import 'package:angel_proxy/angel_proxy.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
main() async { main() async {
// ... // Forward requests instead of serving statically.
// You can also pass a URI, instead of a string.
var client = http.Client(); var proxy1 = Proxy('http://localhost:3000');
// Forward requests instead of serving statically
var proxy1 = Proxy(client, Uri.parse('http://localhost:3000'));
// handle all methods (GET, POST, ...) // handle all methods (GET, POST, ...)
app.fallback(proxy.handleRequest); app.fallback(proxy.handleRequest);
@ -24,12 +21,12 @@ main() async {
You can also restrict the proxy to serving only from a specific root: You can also restrict the proxy to serving only from a specific root:
```dart ```dart
Proxy(client, baseUrl, publicPath: '/remote'); Proxy(baseUrl, publicPath: '/remote');
``` ```
Also, you can map requests to a root path on the remote server: Also, you can map requests to a root path on the remote server:
```dart ```dart
Proxy(client, baseUrl.replace(path: '/path')); Proxy(baseUrl.replace(path: '/path'));
``` ```
Request bodies will be forwarded as well, if they are not empty. This allows things like POST requests to function. Request bodies will be forwarded as well, if they are not empty. This allows things like POST requests to function.

View file

@ -2,20 +2,17 @@ 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/io_client.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
final Duration timeout = Duration(seconds: 5); final Duration timeout = Duration(seconds: 5);
main() async { main() async {
var app = Angel(); var app = Angel();
var client = 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.
var pubProxy = Proxy( var pubProxy = Proxy(
client, 'https://pub.dartlang.org',
Uri.parse('https://pub.dartlang.org'),
publicPath: '/pub', publicPath: '/pub',
timeout: timeout, timeout: timeout,
); );
@ -25,8 +22,7 @@ main() async {
// //
// Play around with this at http://www.websocket.org/echo.html. // Play around with this at http://www.websocket.org/echo.html.
var echoProxy = Proxy( var echoProxy = Proxy(
client, 'http://echo.websocket.org',
Uri.parse('http://echo.websocket.org'),
publicPath: '/echo', publicPath: '/echo',
timeout: timeout, timeout: timeout,
); );
@ -40,8 +36,7 @@ main() async {
// Anything else should fall through to dartlang.org. // Anything else should fall through to dartlang.org.
var dartlangProxy = Proxy( var dartlangProxy = Proxy(
client, 'https://dartlang.org',
Uri.parse('https://dartlang.org'),
timeout: timeout, timeout: timeout,
recoverFrom404: false, recoverFrom404: false,
); );

View file

@ -10,10 +10,14 @@ import 'package:path/path.dart' as p;
final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
final MediaType _fallbackMediaType = MediaType('application', 'octet-stream'); final MediaType _fallbackMediaType = MediaType('application', 'octet-stream');
/// A middleware class that forwards requests (reverse proxies) to an upstream server.
///
/// Supports WebSockets, in addition to regular HTTP requests.
class Proxy { class Proxy {
String _prefix; String _prefix;
final http.BaseClient httpClient; /// The underlying [Client] to use.
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;
@ -25,26 +29,53 @@ class Proxy {
final Duration timeout; final Duration timeout;
Proxy( Proxy(
this.httpClient, baseUrl, {
this.baseUrl, { http.Client httpClient,
this.publicPath = '/', this.publicPath = '/',
this.recoverFromDead = true, this.recoverFromDead = true,
this.recoverFrom404 = true, this.recoverFrom404 = true,
this.timeout, this.timeout,
}) { }) : this.baseUrl = baseUrl is Uri ? baseUrl : Uri.parse(baseUrl.toString()),
if (!baseUrl.hasScheme || !baseUrl.hasAuthority) this.httpClient = httpClient ?? http.Client() {
if (!this.baseUrl.hasScheme || !this.baseUrl.hasAuthority) {
throw ArgumentError( throw ArgumentError(
'Invalid `baseUrl`. URI must have both a scheme and authority.'); 'Invalid `baseUrl`. URI must have both a scheme and authority.');
if (this.recoverFromDead == null) }
if (this.recoverFromDead == null) {
throw ArgumentError.notNull("recoverFromDead"); throw ArgumentError.notNull("recoverFromDead");
if (this.recoverFrom404 == null) }
if (this.recoverFrom404 == null) {
throw ArgumentError.notNull("recoverFrom404"); throw ArgumentError.notNull("recoverFrom404");
}
_prefix = publicPath?.replaceAll(_straySlashes, '') ?? ''; _prefix = publicPath?.replaceAll(_straySlashes, '') ?? '';
} }
void close() => httpClient.close(); void close() => httpClient.close();
/// A handler that serves the file at the given path, unless the user has requested that path.
///
/// You can also limit this functionality to specific values of the `Accept` header, ex. `text/html`.
/// If [accepts] is `null`, OR at least one of the content types in [accepts] is present,
/// the view will be served.
RequestHandler pushState(String path, {Iterable accepts}) {
var vPath = path.replaceAll(_straySlashes, '');
if (_prefix?.isNotEmpty == true) vPath = '$_prefix/$vPath';
return (RequestContext req, ResponseContext res) {
var path = req.path.replaceAll(_straySlashes, '');
if (path == vPath) return Future<bool>.value(true);
if (accepts?.isNotEmpty == true) {
if (!accepts.any((x) => req.accepts(x, strict: true))) {
return Future<bool>.value(true);
}
}
return servePath(vPath, req, res);
};
}
/// Handles an incoming HTTP request. /// Handles an incoming HTTP request.
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, '');

View file

@ -1,6 +1,6 @@
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: 2.1.2 version: 2.2.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:

View file

@ -17,12 +17,10 @@ main() {
setUp(() async { setUp(() async {
app = Angel(); app = Angel();
var appHttp = AngelHttp(app); var appHttp = AngelHttp(app);
var httpClient = http.IOClient();
testServer = await startTestServer(); testServer = await startTestServer();
var proxy1 = Proxy( var proxy1 = Proxy(
httpClient,
Uri( Uri(
scheme: 'http', scheme: 'http',
host: testServer.address.address, host: testServer.address.address,
@ -30,7 +28,7 @@ main() {
publicPath: '/proxy', publicPath: '/proxy',
); );
var proxy2 = Proxy(httpClient, proxy1.baseUrl.replace(path: '/foo')); var proxy2 = Proxy(proxy1.baseUrl.replace(path: '/foo'));
print('Proxy 1 on: ${proxy1.baseUrl}'); print('Proxy 1 on: ${proxy1.baseUrl}');
print('Proxy 2 on: ${proxy2.baseUrl}'); print('Proxy 2 on: ${proxy2.baseUrl}');
@ -42,10 +40,6 @@ main() {
res.write('intercept empty'); res.write('intercept empty');
}); });
app.shutdownHooks.add((_) async {
httpClient.close();
});
app.logger = Logger('angel'); app.logger = Logger('angel');
Logger.root.onRecord.listen((rec) { Logger.root.onRecord.listen((rec) {

View file

@ -4,7 +4,6 @@ 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/io_client.dart' as http;
import 'package:logging/logging.dart'; 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';
@ -37,10 +36,7 @@ main() {
}); });
app.get('/bar', (req, res) => res.write('normal')); app.get('/bar', (req, res) => res.write('normal'));
var httpClient = http.IOClient();
layer = Proxy( layer = Proxy(
httpClient,
Uri(scheme: 'http', host: server.address.address, port: server.port), Uri(scheme: 'http', host: server.address.address, port: server.port),
publicPath: '/proxy', publicPath: '/proxy',
); );