// 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. import 'dart:async'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_proxy/shelf_proxy.dart'; import 'package:test/test.dart'; /// The URI of the server the current proxy server is proxying to. late Uri targetUri; /// The URI of the current proxy server. late Uri proxyUri; void main() { group('forwarding', () { test('forwards request method', () async { await createProxy((request) { expect(request.method, equals('DELETE')); return shelf.Response.ok(':)'); }); await http.delete(proxyUri); }); test('forwards request headers', () async { await createProxy((request) { expect(request.headers, containsPair('foo', 'bar')); expect(request.headers, containsPair('accept', '*/*')); return shelf.Response.ok(':)'); }); await get(headers: {'foo': 'bar', 'accept': '*/*'}); }); test('forwards request body', () async { await createProxy((request) { expect(request.readAsString(), completion(equals('hello, server'))); return shelf.Response.ok(':)'); }); await http.post(proxyUri, body: 'hello, server'); }); test('forwards response status', () async { await createProxy((request) => shelf.Response(567)); final response = await get(); expect(response.statusCode, equals(567)); }); test('forwards response headers', () async { await createProxy((request) => shelf.Response.ok(':)', headers: {'foo': 'bar', 'accept': '*/*'})); final response = await get(); expect(response.headers, containsPair('foo', 'bar')); expect(response.headers, containsPair('accept', '*/*')); }); test('forwards response body', () async { await createProxy((request) => shelf.Response.ok('hello, client')); expect(await http.read(proxyUri), equals('hello, client')); }); test('adjusts the Host header for the target server', () async { await createProxy((request) { expect(request.headers, containsPair('host', targetUri.authority)); return shelf.Response.ok(':)'); }); await get(); }); }); group('via', () { test('adds a Via header to the request', () async { await createProxy((request) { expect(request.headers, containsPair('via', '1.1 shelf_proxy')); return shelf.Response.ok(':)'); }); await get(); }); test("adds to a request's existing Via header", () async { await createProxy((request) { expect(request.headers, containsPair('via', '1.0 something, 1.1 shelf_proxy')); return shelf.Response.ok(':)'); }); await get(headers: {'via': '1.0 something'}); }); test('adds a Via header to the response', () async { await createProxy((request) => shelf.Response.ok(':)')); final response = await get(); expect(response.headers, containsPair('via', '1.1 shelf_proxy')); }); test("adds to a response's existing Via header", () async { await createProxy((request) => shelf.Response.ok(':)', headers: {'via': '1.0 something'})); final response = await get(); expect(response.headers, containsPair('via', '1.0 something, 1.1 shelf_proxy')); }); }); group('redirects', () { test("doesn't modify a Location for a foreign server", () async { await createProxy( (request) => shelf.Response.found('http://dartlang.org')); final response = await get(); expect(response.headers, containsPair('location', 'http://dartlang.org')); }); test('relativizes a reachable root-relative Location', () async { await createProxy((request) => shelf.Response.found('/foo/bar'), targetPath: '/foo'); final response = await get(); expect(response.headers, containsPair('location', '/bar')); }); test('absolutizes an unreachable root-relative Location', () async { await createProxy((request) => shelf.Response.found('/baz'), targetPath: '/foo'); final response = await get(); expect(response.headers, containsPair('location', targetUri.resolve('/baz').toString())); }); }); test('removes a transfer-encoding header', () async { final handler = mockHandler((request) => http.Response('', 200, headers: {'transfer-encoding': 'chunked'})); final response = await handler(shelf.Request('GET', Uri.parse('http://localhost/'))); expect(response.headers, isNot(contains('transfer-encoding'))); }); test('removes content-length and content-encoding for a gzipped response', () async { final handler = mockHandler((request) => http.Response('', 200, headers: {'content-encoding': 'gzip', 'content-length': '1234'})); final response = await handler(shelf.Request('GET', Uri.parse('http://localhost/'))); expect(response.headers, isNot(contains('content-encoding'))); expect(response.headers, isNot(contains('content-length'))); expect(response.headers, containsPair('warning', '214 shelf_proxy "GZIP decoded"')); }); } /// 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 `/`. Future createProxy(shelf.Handler handler, {String? targetPath}) async { handler = expectAsync1(handler, reason: 'target server handler'); final targetServer = await shelf_io.serve(handler, 'localhost', 0); targetUri = Uri.parse('http://localhost:${targetServer.port}'); if (targetPath != null) targetUri = targetUri.resolve(targetPath); final proxyServerHandler = expectAsync1(proxyHandler(targetUri), reason: 'proxy server handler'); final proxyServer = await shelf_io.serve(proxyServerHandler, 'localhost', 0); proxyUri = Uri.parse('http://localhost:${proxyServer.port}'); addTearDown(() { proxyServer.close(force: true); targetServer.close(force: true); }); } /// Creates a [shelf.Handler] that's backed by a [MockClient] running /// [callback]. shelf.Handler mockHandler( FutureOr Function(http.Request) callback) { final client = MockClient((request) async => await callback(request)); return proxyHandler('http://dartlang.org', client: client); } /// Schedules a GET request with [headers] to the proxy server. Future get({Map? headers}) { final uri = proxyUri; final request = http.Request('GET', uri); if (headers != null) request.headers.addAll(headers); request.followRedirects = false; return request.send().then(http.Response.fromStream); }