1.1.0-alpha

This commit is contained in:
Tobe O 2017-09-24 14:49:18 -04:00
parent 487e825f8d
commit 4d7cbb679e
13 changed files with 216 additions and 260 deletions

View file

@ -5,10 +5,7 @@
<excludeFolder url="file://$MODULE_DIR$/.pub" /> <excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" /> <excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/packages" />
<excludeFolder url="file://$MODULE_DIR$/packages" />
<excludeFolder url="file://$MODULE_DIR$/temp" /> <excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/tmp" /> <excludeFolder url="file://$MODULE_DIR$/tmp" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

View file

@ -2,6 +2,7 @@
<configuration default="false" name="All Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true"> <configuration default="false" name="All Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
<option name="filePath" value="$PROJECT_DIR$" /> <option name="filePath" value="$PROJECT_DIR$" />
<option name="scope" value="FOLDER" /> <option name="scope" value="FOLDER" />
<option name="testRunnerOptions" value="-j 4" />
<method /> <method />
</configuration> </configuration>
</component> </component>

View file

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="multiple.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
<option name="filePath" value="$PROJECT_DIR$/example/multiple.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method />
</configuration>
</component>

View file

@ -1,25 +1,33 @@
# angel_proxy # 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)
[![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`).
Angel middleware to forward requests to another server (i.e. pub serve).
```dart ```dart
import 'package:angel_proxy/angel_proxy.dart'; import 'package:angel_proxy/angel_proxy.dart';
import 'package:http/http.dart' as http;
main() async { main() async {
// ... // ...
// Forward requests instead of serving statically var client = new http.Client();
await app.configure(new ProxyLayer('localhost', 3000)); var proxy = new Proxy(app, client, 'http://localhost:3000');
// Or, use one for pub serve. // Forward requests instead of serving statically
// app.use(proxy.handleRequest);
// This automatically deactivates itself if the app is
// in production.
await app.configure(new PubServeLayer());
} }
``` ```
You can also restrict the proxy to serving only from a specific root:
```dart
new Proxy(app, client, '<host>', publicPath: '/remote');
```
Also, you can map requests to a root path on the remote server
```dart
new Proxy(app, client, '<host>', mapTo: '/path');
```
If your app's `storeOriginalBuffer` is `true`, then request bodies will be forwarded If your app's `storeOriginalBuffer` is `true`, then request bodies will be forwarded
as well, if they are not empty. This allows things like POST requests to function. as well, if they are not empty. This allows things like POST requests to function.

View file

@ -1,38 +1,55 @@
import 'dart:io'; import 'dart:io';
import 'package:angel_diagnostics/angel_diagnostics.dart';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_proxy/angel_proxy.dart'; import 'package:angel_proxy/angel_proxy.dart';
import 'package:http/http.dart' as http;
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();
// 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 = new ProxyLayer('pub.dartlang.org', 80, var pubProxy = new Proxy(
publicPath: '/pub', timeout: TIMEOUT); app,
await app.configure(pubProxy); client,
'https://pub.dartlang.org',
publicPath: '/pub',
timeout: timeout,
);
app.use(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:
app.get('/static/*', (RequestContext req, res) { app.get('/static/*', (RequestContext req, res) {
return pubProxy.serveFile(req.path, req, res); return pubProxy.servePath(req.path, req, res);
}); });
// Anything else should fall through to dartlang.org. // Anything else should fall through to dartlang.org.
await app.configure(new ProxyLayer('dartlang.org', 80, timeout: TIMEOUT)); var dartlangProxy = new Proxy(
app,
client,
'https://dartlang.org',
timeout: timeout,
recoverFrom404: false,
);
app.use(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.after.add('Couldn\'t connect to Pub or dartlang.'); app.use('Couldn\'t connect to Pub or dartlang.');
await app.configure(logRequests()); app.logger = new Logger('angel')
..onRecord.listen(
app.fatalErrorStream.listen((AngelFatalError e) { (rec) {
print(e.error); print(rec);
print(e.stack); if (rec.error != null) print(rec.error);
}); if (rec.stackTrace != null) print(rec.stackTrace);
},
);
var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 8080); var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 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');
} }

View file

@ -1,4 +1,3 @@
library angel_proxy; library angel_proxy;
export 'src/proxy_layer.dart'; export 'src/proxy_layer.dart';
export 'src/pub_serve_layer.dart';

View file

@ -1,142 +1,107 @@
import 'dart:async'; import 'dart:async';
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:http/src/base_client.dart' as http;
import 'package:http/src/request.dart' as http;
import 'package:http/src/response.dart' as http;
import 'package:http/src/streamed_response.dart' as http;
final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?'); final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?');
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
/// Used to mount a route for a [ProxyLayer]. class Proxy {
typedef Route ProxyLayerRouteAssigner(Router router, String path, handler);
String _pathify(String path) {
var p = path.replaceAll(_straySlashes, '');
Map<String, String> replace = {};
for (Match match in _param.allMatches(p)) {
if (match[3] != null) replace[match[0]] = ':${match[1]}';
}
replace.forEach((k, v) {
p = p.replaceAll(k, v);
});
return p;
}
/// Copies HTTP headers ;)
void copyHeaders(HttpHeaders from, HttpHeaders to) {
from.forEach((k, v) {
if (k != HttpHeaders.SERVER &&
(k != HttpHeaders.CONTENT_ENCODING || !v.contains('gzip')))
to.set(k, v);
});
/*to
..chunkedTransferEncoding = from.chunkedTransferEncoding
..contentLength = from.contentLength
..contentType = from.contentType
..date = from.date
..expires = from.expires
..host = from.host
..ifModifiedSince = from.ifModifiedSince
..persistentConnection = from.persistentConnection
..port = from.port;*/
}
class ProxyLayer {
Angel app;
HttpClient _client;
String _prefix; String _prefix;
final Angel app;
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;
final bool debug, recoverFrom404, streamToIO; final bool recoverFrom404;
final String host, mapTo, publicPath; final String host, mapTo, publicPath;
final int port; final int port;
final String protocol; final String protocol;
final Duration timeout; final Duration timeout;
ProxyLayerRouteAssigner routeAssigner;
ProxyLayer( Proxy(
this.host, this.app,
this.port, { this.httpClient,
this.debug: false, this.host, {
this.port,
this.mapTo: '/', this.mapTo: '/',
this.publicPath: '/', this.publicPath: '/',
this.protocol: 'http', this.protocol: 'http',
this.recoverFromDead: true, this.recoverFromDead: true,
this.recoverFrom404: true, this.recoverFrom404: true,
this.streamToIO: false,
this.timeout, this.timeout,
this.routeAssigner,
SecurityContext securityContext,
}) { }) {
_client = new HttpClient(context: securityContext);
_prefix = publicPath.replaceAll(_straySlashes, ''); _prefix = publicPath.replaceAll(_straySlashes, '');
routeAssigner ??=
(Router router, String path, handler) => router.get(path, handler);
} }
call(Angel app) async => serve(this.app = app); void close() => httpClient.close();
void close() => _client.close(force: true); /// Handles an incoming HTTP request.
Future<bool> handleRequest(RequestContext req, ResponseContext res) {
void serve(Router router) {
// _printDebug('Public path prefix: "$_prefix"');
handler(RequestContext req, ResponseContext res) async {
var path = req.path.replaceAll(_straySlashes, ''); var path = req.path.replaceAll(_straySlashes, '');
return serveFile(path, req, res);
if (_prefix?.isNotEmpty == true) {
if (!path.startsWith(_prefix))
return new Future<bool>.value(true);
else {
path = path.replaceFirst(_prefix, '').replaceAll(_straySlashes, '');
}
} }
routeAssigner(router, '$publicPath/*', handler); return servePath(path, req, res);
} }
serveFile(String path, RequestContext req, ResponseContext res) async { /// Proxies a request to the given path on the remote server.
var _path = path; Future<bool> servePath(
HttpClientResponse rs; String path, RequestContext req, ResponseContext res) async {
http.StreamedResponse rs;
if (_prefix.isNotEmpty) { final mapping = '$mapTo/$path'.replaceAll(_straySlashes, '');
_path = path.replaceAll(new RegExp('^' + _pathify(_prefix)), '');
}
// Create mapping
// _printDebug('Serving path $_path via proxy');
final mapping = '$mapTo/$_path'.replaceAll(_straySlashes, '');
// _printDebug('Mapped path $_path to path $mapping on proxy $host:$port');
try { try {
Future<HttpClientResponse> accessRemote() async { Future<http.StreamedResponse> accessRemote() async {
var ips = await InternetAddress.lookup(host); var url = port == null ? host : '$host:$port';
if (ips.isEmpty) url = url.replaceAll(_straySlashes, '');
throw new StateError('Could not resolve remote host "$host".'); url = '$url/$mapping';
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); if (!url.startsWith('http')) url = 'http://$url';
rq.headers.set(HttpHeaders.HOST, host); url = url.replaceAll(_straySlashes, '');
// _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) { var headers = <String, String>{
'host': port == null ? host : '$host:$port',
'x-forwarded-for': req.io.connectionInfo.remoteAddress.address,
'x-forwarded-port': req.io.connectionInfo.remotePort.toString(),
'x-forwarded-host':
req.headers.host ?? req.headers.value('host') ?? 'none',
'x-forwarded-proto': protocol,
};
req.headers.forEach((name, values) {
headers[name] = values.join(',');
});
headers['cookie'] =
req.cookies.map<String>((c) => '${c.name}=${c.value}').join('; ');
var body;
if (req.method != 'GET' && app.storeOriginalBuffer == true) {
await req.parse(); await req.parse();
if (req.originalBuffer?.isNotEmpty == true) if (req.originalBuffer?.isNotEmpty == true) body = req.originalBuffer;
rq.add(req.originalBuffer);
} }
await rq.flush(); var rq = new http.Request(req.method, Uri.parse(url));
return await rq.close(); rq.headers.addAll(headers);
rq.headers['host'] = rq.url.host;
if (body != null) rq.bodyBytes = body;
return await httpClient.send(rq);
} }
var future = accessRemote(); var future = accessRemote();
@ -146,11 +111,13 @@ class ProxyLayer {
if (recoverFromDead != false) if (recoverFromDead != false)
return true; return true;
else else
throw new AngelHttpException(e, throw new AngelHttpException(
e,
stackTrace: st, stackTrace: st,
statusCode: HttpStatus.GATEWAY_TIMEOUT, 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 != false)
return true; return true;
@ -158,44 +125,21 @@ class ProxyLayer {
rethrow; rethrow;
} }
// _printDebug(
// 'Proxy responded to $mapping with status code ${rs.statusCode}');
if (rs.statusCode == 404 && recoverFrom404 != false) return true; if (rs.statusCode == 404 && recoverFrom404 != false) return true;
res res
..statusCode = rs.statusCode ..statusCode = rs.statusCode
..contentType = rs.headers.contentType; ..headers.addAll(rs.headers);
// _printDebug('Proxy response headers:\n${rs.headers}'); if (rs.contentLength == 0 && recoverFromDead != false) return true;
if (streamToIO == true) { var stream = rs.stream;
res
..willCloseItself = true
..end();
copyHeaders(rs.headers, res.io.headers); if (rs.headers['content-encoding'] == 'gzip')
res.io.statusCode = rs.statusCode; stream = stream.transform(GZIP.encoder);
if (rs.headers.contentType != null) await stream.pipe(res);
res.io.headers.contentType = rs.headers.contentType;
// _printDebug('Outgoing content length: ${res.io.contentLength}'); return false;
if (rs.headers[HttpHeaders.CONTENT_ENCODING]?.contains('gzip') == true) {
res.io.headers.set(HttpHeaders.CONTENT_ENCODING, 'gzip');
await rs.transform(GZIP.encoder).pipe(res.io);
} else
await rs.pipe(res.io);
} else {
rs.headers.forEach((k, v) {
if (k != HttpHeaders.CONTENT_ENCODING || !v.contains('gzip'))
res.headers[k] = v.join(',');
});
await rs.forEach(res.buffer.add);
}
return res.buffer.isEmpty;
} }
} }

View file

@ -1,37 +0,0 @@
import 'package:angel_route/angel_route.dart';
import 'proxy_layer.dart';
class PubServeLayer extends ProxyLayer {
PubServeLayer(
{bool debug: false,
bool recoverFromDead: true,
bool recoverFrom404: true,
bool streamToIO: true,
String host: 'localhost',
String mapTo: '/',
int port: 8080,
String protocol: 'http',
String publicPath: '/',
ProxyLayerRouteAssigner routeAssigner,
Duration timeout})
: super(host, port,
debug: debug,
mapTo: mapTo,
protocol: protocol,
publicPath: publicPath,
recoverFromDead: recoverFromDead != false,
recoverFrom404: recoverFrom404 != false,
streamToIO: streamToIO != false,
routeAssigner: routeAssigner,
timeout: timeout);
@override
void serve(Router router) {
if (app?.isProduction == true) {
// Auto-deactivate in production ;)
return;
}
super.serve(router);
}
}

View file

@ -1,16 +1,14 @@
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.0.9 version: 1.1.0-alpha
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" sdk: ">=1.19.0"
dependencies: dependencies:
angel_framework: ^1.0.0-dev angel_framework: ^1.1.0-alpha
dev_dependencies:
angel_compress: ^1.0.0
angel_diagnostics: ^1.0.0
angel_test: ^1.0.0
http: ^0.11.3 http: ^0.11.3
dev_dependencies:
angel_test: ^1.1.0-alpha
test: ^0.12.15 test: ^0.12.15

View file

@ -2,8 +2,8 @@ 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_proxy/angel_proxy.dart'; import 'package:angel_proxy/angel_proxy.dart';
import 'package:angel_test/angel_test.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'common.dart'; import 'common.dart';
@ -15,18 +15,44 @@ main() {
setUp(() async { setUp(() async {
app = new Angel()..storeOriginalBuffer = true; app = new Angel()..storeOriginalBuffer = true;
var httpClient = new http.Client();
testServer = await testApp().startServer(); testServer = await testApp().startServer();
await app.configure(new ProxyLayer( var proxy1 = new Proxy(
testServer.address.address, testServer.port, app,
httpClient,
testServer.address.address,
port: testServer.port,
publicPath: '/proxy', publicPath: '/proxy',
routeAssigner: (router, path, handler) => router.all(path, handler))); );
await app.configure(new ProxyLayer( var proxy2 = new Proxy(
testServer.address.address, testServer.port, app,
mapTo: '/foo')); httpClient,
testServer.address.address,
port: testServer.port,
mapTo: '/foo',
);
app.after.add((req, res) async => res.write('intercept empty')); app.use(proxy1.handleRequest);
app.use(proxy2.handleRequest);
app.use((req, res) {
print('Intercepting empty from ${req.uri}');
res.write('intercept empty');
});
app.shutdownHooks.add((_) async {
httpClient.close();
});
app.logger = new Logger('angel');
Logger.root.onRecord.listen((rec) {
print(rec);
if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace);
});
server = await app.startServer(); server = await app.startServer();
url = 'http://${server.address.address}:${server.port}'; url = 'http://${server.address.address}:${server.port}';
@ -48,11 +74,6 @@ main() {
test('empty', () async { test('empty', () async {
var response = await client.get('$url/proxy/empty'); var response = await client.get('$url/proxy/empty');
print('Response: ${response.body}'); print('Response: ${response.body}');
// Shouldn't say it is gzipped...
expect(response, isNot(hasHeader('content-encoding')));
// Should have gzipped body
expect(response.body, 'intercept empty'); expect(response.body, 'intercept empty');
}); });
@ -64,10 +85,10 @@ main() {
test('original buffer', () async { test('original buffer', () async {
var response = await client.post('$url/proxy/body', var response = await client.post('$url/proxy/body',
body: {'foo': 'bar'}, body: JSON.encode({'foo': 'bar'}),
headers: {'content-type': 'application/x-www-form-urlencoded'}); headers: {'content-type': 'application/json'});
print('Response: ${response.body}'); print('Response: ${response.body}');
expect(response.body, isNotEmpty); expect(response.body, isNotEmpty);
expect(JSON.decode(response.body), {'foo': 'bar'}); expect(response.body, isNot('intercept empty'));
}); });
} }

View file

@ -1,16 +1,18 @@
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:logging/logging.dart';
Angel testApp() { Angel testApp() {
final app = new Angel(); final app = new Angel()..lazyParseBodies = true;
app.get('/hello', 'world'); app.get('/hello', 'world');
app.get('/foo/bar', 'baz'); app.get('/foo/bar', 'baz');
app.post('/body', (req, res) => req.lazyBody()); app.post('/body', (RequestContext req, res) async {
var body = await req.lazyBody();
app.fatalErrorStream.listen((e) { print('Body: $body');
print('FATAL IN TEST APP: ${e.error}'); return body;
print(e.stack);
}); });
app.logger = new Logger('testApp');
return app..dumpTree(); return app..dumpTree();
} }

View file

@ -1,9 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:angel_compress/angel_compress.dart';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.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:mock_request/mock_request.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
main() { main() {
@ -12,27 +13,40 @@ main() {
setUp(() async { setUp(() async {
testApp = new Angel(); testApp = new Angel();
testApp.get('/foo', (req, res) => res.write('pub serve')); testApp.get('/foo', (req, res) async {
res.write('pub serve');
});
testApp.get('/empty', (req, res) => res.end()); testApp.get('/empty', (req, res) => res.end());
testApp.responseFinalizers.add(gzip());
testApp.responseFinalizers.add((req, ResponseContext res) async {
print('OUTGOING: ' + new String.fromCharCodes(res.buffer.toBytes()));
});
testApp.injectEncoders({'gzip': GZIP.encoder});
var server = await testApp.startServer(); var server = await 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 layer = new PubServeLayer(
debug: true, var httpClient = new http.Client();
var layer = new Proxy(
app,
httpClient,
server.address.address,
port: server.port,
publicPath: '/proxy', publicPath: '/proxy',
host: server.address.address, );
port: server.port); app.use(layer.handleRequest);
print('streamToIO: ${layer.streamToIO}');
await app.configure(layer);
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()) + new String.fromCharCodes(res.buffer.toBytes()) +
', headers: ${res.headers}'); ', headers: ${res.headers}');
}); });
app.responseFinalizers.add(gzip());
app.injectEncoders({'gzip': GZIP.encoder});
client = await connectTo(app); client = await connectTo(app);
}); });
@ -46,34 +60,19 @@ main() {
}); });
test('proxied', () async { test('proxied', () async {
var response = await client.get('/proxy/foo'); var rq = new MockHttpRequest('GET', Uri.parse('/proxy/foo'))..close();
await app.handleRequest(rq);
// Should say it is gzipped... var response = await rq.response.transform(UTF8.decoder).join();
expect(response, hasHeader('content-encoding', 'gzip')); expect(response, 'pub serve');
// Should have gzipped body
//
// We have to decode it, because `mock_request` does not auto-decode.
expect(response, hasBody('pub serve'));
}); });
test('empty', () async { test('empty', () async {
var response = await client.get('/proxy/empty'); var response = await client.get('/proxy/empty');
// Should say it is gzipped...
expect(response, hasHeader('content-encoding', 'gzip'));
// Should have gzipped body
expect(response.body, isEmpty); expect(response.body, isEmpty);
}); });
test('normal', () async { test('normal', () async {
var response = await client.get('/bar'); var response = await client.get('/bar');
// Should say it is gzipped...
expect(response, hasHeader('content-encoding', 'gzip'));
// Should have normal body
expect(response, hasBody('normal')); expect(response, hasBody('normal'));
}); });
} }