blob: fe76ddc6f08fae121407517576f73d2c652af3b2 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library shelf_proxy;
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:scheduled_test/scheduled_test.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_proxy/shelf_proxy.dart';
/// The URI of the server the current proxy server is proxying to.
Uri targetUri;
/// The URI of the current proxy server.
Uri proxyUri;
void main() {
group("forwarding", () {
test("forwards request method", () {
createProxy((request) {
expect(request.method, equals('DELETE'));
return new shelf.Response.ok(':)');
});
schedule(() => http.delete(proxyUri));
});
test("forwards request headers", () {
createProxy((request) {
expect(request.headers, containsPair('foo', 'bar'));
expect(request.headers, containsPair('accept', '*/*'));
return new shelf.Response.ok(':)');
});
get(headers: {
'foo': 'bar',
'accept': '*/*'
});
});
test("forwards request body", () {
createProxy((request) {
expect(request.readAsString(), completion(equals('hello, server')));
return new shelf.Response.ok(':)');
});
schedule(() => http.post(proxyUri, body: 'hello, server'));
});
test("forwards response status", () {
createProxy((request) {
return new shelf.Response(567);
});
expect(get().then((response) {
expect(response.statusCode, equals(567));
}), completes);
});
test("forwards response headers", () {
createProxy((request) {
return new shelf.Response.ok(':)', headers: {
'foo': 'bar',
'accept': '*/*'
});
});
expect(get().then((response) {
expect(response.headers, containsPair('foo', 'bar'));
expect(response.headers, containsPair('accept', '*/*'));
}), completes);
});
test("forwards response body", () {
createProxy((request) {
return new shelf.Response.ok('hello, client');
});
expect(schedule(() => http.read(proxyUri)),
completion(equals('hello, client')));
});
test("adjusts the Host header for the target server", () {
createProxy((request) {
expect(request.headers, containsPair('host', targetUri.authority));
return new shelf.Response.ok(':)');
});
get();
});
});
group("via", () {
test("adds a Via header to the request", () {
createProxy((request) {
expect(request.headers, containsPair('via', '1.1 shelf_proxy'));
return new shelf.Response.ok(':)');
});
get();
});
test("adds to a request's existing Via header", () {
createProxy((request) {
expect(request.headers,
containsPair('via', '1.0 something, 1.1 shelf_proxy'));
return new shelf.Response.ok(':)');
});
get(headers: {'via': '1.0 something'});
});
test("adds a Via header to the response", () {
createProxy((request) => new shelf.Response.ok(':)'));
expect(get().then((response) {
expect(response.headers, containsPair('via', '1.1 shelf_proxy'));
}), completes);
});
test("adds to a response's existing Via header", () {
createProxy((request) {
return new shelf.Response.ok(':)', headers: {'via': '1.0 something'});
});
expect(get().then((response) {
expect(response.headers,
containsPair('via', '1.0 something, 1.1 shelf_proxy'));
}), completes);
});
test("adds to a response's existing Via header", () {
createProxy((request) {
return new shelf.Response.ok(':)', headers: {'via': '1.0 something'});
});
expect(get().then((response) {
expect(response.headers,
containsPair('via', '1.0 something, 1.1 shelf_proxy'));
}), completes);
});
});
group("redirects", () {
test("doesn't modify a Location for a foreign server", () {
createProxy((request) {
return new shelf.Response.found('http://dartlang.org');
});
expect(get().then((response) {
expect(response.headers,
containsPair('location', 'http://dartlang.org'));
}), completes);
});
test("relativizes a reachable root-relative Location", () {
createProxy((request) {
return new shelf.Response.found('/foo/bar');
}, targetPath: '/foo');
expect(get().then((response) {
expect(response.headers, containsPair('location', '/bar'));
}), completes);
});
test("absolutizes an unreachable root-relative Location", () {
createProxy((request) {
return new shelf.Response.found('/baz');
}, targetPath: '/foo');
expect(get().then((response) {
expect(response.headers,
containsPair('location', targetUri.resolve('/baz').toString()));
}), completes);
});
});
test("removes a transfer-encoding header", () {
var handler = mockHandler((request) {
return new http.Response('', 200, headers: {
'transfer-encoding': 'chunked'
});
});
expect(handler(new shelf.Request('GET', Uri.parse('http://localhost/')))
.then((response) {
expect(response.headers, isNot(contains("transfer-encoding")));
}), completes);
});
test("removes content-length and content-encoding for a gzipped response",
() {
var handler = mockHandler((request) {
return new http.Response('', 200, headers: {
'content-encoding': 'gzip',
'content-length': '1234'
});
});
expect(handler(new shelf.Request('GET', Uri.parse('http://localhost/')))
.then((response) {
expect(response.headers, isNot(contains("content-encoding")));
expect(response.headers, isNot(contains("content-length")));
expect(response.headers,
containsPair('warning', '214 shelf_proxy "GZIP decoded"'));
}), completes);
});
}
/// Creates a proxy server proxying to a server running [handler].
///
/// [targetPath] is the root-relative path on the target server to proxy to. It
/// defaults to `/`.
void createProxy(shelf.Handler handler, {String targetPath}) {
handler = wrapAsync(handler, 'target server handler');
schedule(() {
return shelf_io.serve(handler, 'localhost', 0).then((targetServer) {
targetUri = Uri.parse('http://localhost:${targetServer.port}');
if (targetPath != null) targetUri = targetUri.resolve(targetPath);
var proxyServerHandler = wrapAsync(
proxyHandler(targetUri), 'proxy server handler');
return shelf_io.serve(proxyServerHandler, 'localhost', 0)
.then((proxyServer) {
proxyUri = Uri.parse('http://localhost:${proxyServer.port}');
currentSchedule.onComplete.schedule(() {
proxyServer.close(force: true);
targetServer.close(force: true);
}, 'tear down servers');
});
});
}, 'spin up servers');
}
/// Creates a [shelf.Handler] that's backed by a [MockClient] running
/// [callback].
shelf.Handler mockHandler(callback(http.Request request)) {
var client = new MockClient((request) {
return new Future.sync(() => callback(request));
});
return proxyHandler('http://dartlang.org', client: client);
}
/// Schedules a GET request with [headers] to the proxy server.
Future<http.Response> get({Map<String, String> headers}) {
return schedule(() {
var uri = proxyUri;
var request = new http.Request('GET', uri);
if (headers != null) request.headers.addAll(headers);
request.followRedirects = false;
return request.send().then(http.Response.fromStream);
}, 'GET proxy server');
}