blob: bf37c54e6c23866c66ecdf5e6a9054ce6326a159 [file] [log] [blame]
// Copyright 2019 The Flutter Authors. 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:convert';
import 'dart:io';
import 'package:cocoon_service/cocoon_service.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
void main() {
HttpServer destServer;
setUpAll(() async {
destServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
destServer.defaultResponseHeaders.clear();
destServer.serverHeader = null;
destServer.listen((HttpRequest request) async {
final HttpResponse response = request.response;
response.headers.add('X-Single-Header', 'SingleValue');
response.headers.add('X-Multi-Header', 'Value1');
response.headers.add('X-Multi-Header', 'Value2');
switch (request.uri.path) {
case '/ok':
response.statusCode = HttpStatus.ok;
break;
case '/notFound':
response.statusCode = HttpStatus.notFound;
break;
case '/redirect':
response.headers.add(HttpHeaders.locationHeader, '/foo');
response.statusCode = HttpStatus.movedTemporarily;
break;
case '/rawBody':
response.write('hello world');
await response.flush();
await response.close();
return;
case '/gzipped':
final String body =
List<String>.generate(1024, (int index) => '$index').join();
response.headers.add(HttpHeaders.contentEncodingHeader, 'gzip');
response.add(gzip.encode(utf8.encode(body)));
await response.flush();
await response.close();
return;
default:
response.statusCode = HttpStatus.methodNotAllowed;
}
bool isIgnoredHeader(String key, String value) {
return key.toLowerCase() ==
HttpHeaders.contentLengthHeader.toLowerCase();
}
final Map<String, String> headers = <String, String>{};
request.headers.forEach(
(String name, List<String> values) => headers[name] = values.single);
response.write(json.encode(<String, dynamic>{
'headers': headers..removeWhere(isIgnoredHeader),
'path': request.uri.path,
'query': request.uri.query,
'fragment': request.uri.fragment,
'body': await utf8.decoder.bind(request).join(),
}));
await response.flush();
await response.close();
});
});
tearDownAll(() async {
await destServer.close();
});
group('ProxyRequestHandler', () {
ProxyRequestHandler handler;
HttpServer proxyServer;
HttpClient client;
Uri url;
HttpClientRequest request;
HttpClientResponse response;
setUp(() async {
handler = ProxyRequestHandler(
config: MockConfig(),
scheme: 'http',
host: 'localhost',
port: destServer.port,
);
proxyServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
proxyServer.listen(handler.service);
client = HttpClient()..userAgent = null;
url = Uri(scheme: 'http', host: 'localhost', port: proxyServer.port);
});
tearDown(() async {
await proxyServer.close();
});
test('forwards basic request', () async {
request = await client.getUrl(url.replace(path: '/ok'));
request.headers.clear();
response = await request.close();
final Map<String, dynamic> data = await utf8.decoder
.bind(response)
.transform(json.decoder)
.single as Map<String, dynamic>;
expect(data['headers'], <String, dynamic>{
HttpHeaders.hostHeader: 'localhost:${destServer.port}',
});
expect(data['path'], '/ok');
expect(data['query'], isEmpty);
expect(data['fragment'], isEmpty);
expect(data['body'], isEmpty);
});
test('forwards HTTP request headers', () async {
request = await client.getUrl(url.replace(path: '/ok'));
request.headers.clear();
request.headers.add('foo', 'bar');
request.headers.add('baz', 'qux');
request.headers.add('multi', 'value1');
request.headers.add('multi', 'value2');
response = await request.close();
final Map<String, dynamic> data = await utf8.decoder
.bind(response)
.transform(json.decoder)
.single as Map<String, dynamic>;
expect(data['headers'], <String, dynamic>{
'foo': 'bar',
'baz': 'qux',
'multi': 'value1, value2',
HttpHeaders.hostHeader: 'localhost:${destServer.port}',
});
});
test('forwards HTTP request body', () async {
request = await client.getUrl(url.replace(path: '/ok'));
request.contentLength = 'request body'.length;
request.write('request body');
await request.flush();
response = await request.close();
final Map<String, dynamic> data = await utf8.decoder
.bind(response)
.transform(json.decoder)
.single as Map<String, dynamic>;
expect(data['body'], 'request body');
});
test('forwards HTTP request query parameters', () async {
request = await client.getUrl(
url.replace(path: '/ok', query: 'foo=bar&baz=qux%26quz%3Dquw'));
response = await request.close();
final Map<String, dynamic> data = await utf8.decoder
.bind(response)
.transform(json.decoder)
.single as Map<String, dynamic>;
expect(data['query'], 'foo=bar&baz=qux%26quz%3Dquw');
});
test('URL fragment is dropped', () async {
request = await client
.getUrl(url.replace(path: '/ok', fragment: 'foo&bar=baz'));
response = await request.close();
final Map<String, dynamic> data = await utf8.decoder
.bind(response)
.transform(json.decoder)
.single as Map<String, dynamic>;
expect(data['fragment'], isEmpty);
});
test('forwards HTTP response status code', () async {
request = await client.getUrl(url.replace(path: '/ok'));
response = await request.close();
expect(response.statusCode, HttpStatus.ok);
request = await client.getUrl(url.replace(path: '/notFound'));
response = await request.close();
expect(response.statusCode, HttpStatus.notFound);
request = await client.getUrl(url.replace(path: '/redirect'));
request.followRedirects = false;
response = await request.close();
expect(response.statusCode, HttpStatus.movedTemporarily);
expect(response.headers.value(HttpHeaders.locationHeader), '/foo');
});
test('forwards HTTP response headers', () async {
request = await client.getUrl(url.replace(path: '/ok'));
response = await request.close();
expect(response.headers.value('X-Single-Header'), 'SingleValue');
expect(response.headers.value('X-Multi-Header'), 'Value1, Value2');
});
test('forwards HTTP response body', () async {
request = await client.getUrl(url.replace(path: '/rawBody'));
response = await request.close();
expect(await utf8.decoder.bind(response).join(), 'hello world');
});
test('Handles gzipped responses from the dest server', () async {
final String expectedBody =
List<String>.generate(1024, (int index) => '$index').join();
request = await client.getUrl(url.replace(path: '/gzipped'));
response = await request.close();
expect(await utf8.decoder.bind(response).join(), expectedBody);
});
});
}
// ignore: must_be_immutable
class MockConfig extends Mock implements Config {}