proxyRequest function
Future
proxyRequest(
- HttpConnect connect,
- dynamic url, {
- String? proxyName,
- FutureOr<
bool> shallRetry(- dynamic ex,
- StackTrace st
Proxies the request of url
to connect
.
Example:
Future proxyFoo(HttpConnect connect)
=> proxy(connect, getTargetUrl(connect));
url
must be a String or Uri.proxyName
is used in headers to identify this proxy. It should be a valid HTTP token or a hostname. It defaults to null -- novia
header will be added.shallRetry
a callback to decide whether to retry when proxyRequest receives an exception. Ignored if omitted.
Implementation
Future proxyRequest(HttpConnect connect, url, {String? proxyName,
FutureOr<bool> shallRetry(ex, StackTrace st)?}) async {
//COPRYRIGHT NOTICE:
//The code is ported from [shelf_proxy](https://github.com/dart-lang/shelf_proxy)
Uri uri;
if (url is String) {
uri = Uri.parse(url);
} else if (url is Uri) {
uri = url;
} else {
throw ArgumentError.value(url, 'url', 'url must be a String or Uri.');
}
final client = http.Client(),
serverRequest = connect.request,
serverResponse = connect.response;
http.StreamedResponse clientResponse;
for (List<int>? requestBody;;) {
try {
final clientRequest = http.StreamedRequest(serverRequest.method, uri);
clientRequest.followRedirects = false;
serverRequest.headers.forEach((name, values) {
for (final value in values)
if (Rsp.isHeaderValueValid(value))
_addHeader(clientRequest.headers, name, value);
else
_logger.warning('Ignored: invalid request header value: $name=${json.encode(value)}');
});
clientRequest.headers['Host'] = uri.authority;
// Add a Via header. See
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.45
_addHeader(clientRequest.headers, 'via',
'${serverRequest.protocolVersion} ${proxyName??"Stream"}');
if (requestBody == null) { //first time
_Listener<List<int>>? copyTo;
if (shallRetry != null) {
final body = requestBody = <int>[];
copyTo = (List<int> event) {
body.addAll(event);
clientRequest.sink.add(event);
};
}
await _copy(serverRequest, clientRequest.sink, copyTo: copyTo);
} else { //retries
clientRequest.sink.add(requestBody);
}
clientResponse = await client.send(clientRequest);
break; //done
} catch (ex, st) {
if (shallRetry == null || (await shallRetry(ex, st)) != true)
rethrow;
//retry
}
}
serverResponse.statusCode = clientResponse.statusCode;
clientResponse.headers.forEach((name, value) {
if (!Rsp.isHeaderValueValid(value)) {
var fixed = false;
if (name.toLowerCase() == 'content-disposition') {
value = value.replaceAllMapped(
RegExp(r'(name=")([^"]+)(")'),
(m) => '${m[1]}${Uri.encodeComponent(m[2]!)}${m[3]}');
fixed = Rsp.isHeaderValueValid(value);
}
if (!fixed) {
_logger.warning('Ignored: invalid response header value: $name=${json.encode(value)}');
return; //skip
}
}
serverResponse.headers.add(name, value, preserveHeaderCase: true);
});
// Add a Via header. See
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.45
if (proxyName != null)
serverResponse.headers.add('via', '1.1 $proxyName');
// Remove the transfer-encoding since the body has already been decoded by
// [client].
serverResponse.headers.removeAll('transfer-encoding');
// If the original response was gzipped, it will be decoded by [client]
// and we'll have no way of knowing its actual content-length.
if (clientResponse.headers['content-encoding'] == 'gzip') {
serverResponse.headers
..removeAll(HttpHeaders.contentEncodingHeader)
..removeAll(HttpHeaders.contentLengthHeader);
// Add a Warning header. See
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.2
serverResponse.headers.add('warning', '214 ${proxyName??'Stream'} "GZIP decoded"');
}
// Make sure the Location header is pointing to the proxy server rather
// than the destination server, if possible.
if (clientResponse.isRedirect) {
final rawLocation = clientResponse.headers['location'];
if (rawLocation != null) {
var location = uri.resolve(rawLocation).toString();
if (p.url.isWithin(uri.toString(), location)) {
serverResponse.headers.set('location',
'/' + p.url.relative(location, from: uri.toString()));
} else {
serverResponse.headers.set('location', location);
}
}
}
await _copy(clientResponse.stream, serverResponse);
}