proxyRequest function
Future
proxyRequest(
- HttpConnect connect,
- dynamic url, {
- String? proxyName,
- FutureOr<
bool> shallRetry(- Object ex,
- StackTrace st
- void onResponseHeaders(
- int statusCode,
- HttpHeaders headers
- void log(
- String errmsg
Proxies the request of url to connect.
Example:
Future proxyFoo(HttpConnect connect)
=> proxy(connect, getTargetUrl(connect));
urlmust be a String or Uri.proxyNameis used in headers to identify this proxy. It should be a valid HTTP token or a hostname. It defaults to null -- noviaheader will be added.shallRetrya callback to decide whether to retry when proxyRequest receives an exception. Ignored if omitted.onResponseHeadersif specified, it'll be called after the upstream response headers have been merged intoconnect's response and before the body is sent. The given HttpHeaders is the (still mutable) response headers, so the caller can inspect what the upstream returned and adjust it -- e.g., apply a default header only when the upstream didn't provide one.logIf specified, it'll be called if there is an ignorable error, e.g., header's value containing invalid characters
Implementation
Future proxyRequest(HttpConnect connect, url, {String? proxyName,
FutureOr<bool> shallRetry(Object ex, StackTrace st)?,
void onResponseHeaders(int statusCode, HttpHeaders headers)?,
void log(String errmsg)?}) 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;
try {
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
(log ?? _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
_CopyTo<List<int>>? copyTo;
if (shallRetry != null) {
final body = requestBody = <int>[];
copyTo = (List<int> event, void close()) {
body.addAll(event);
clientRequest.sink.add(event);
};
}
await copyToSink(serverRequest, clientRequest.sink,
copyTo: copyTo, closeSink: true);
} else { //retries
clientRequest.sink.add(requestBody);
await clientRequest.sink.close();
}
clientResponse = await client.send(clientRequest);
break; //done
} catch (ex, st) {
if (shallRetry == null || (await shallRetry(ex, st)) != true)
rethrow;
//retry
}
}
final code = 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) => 'name="${Uri.encodeComponent(m[1]!)}"');
fixed = Rsp.isHeaderValueValid(value);
}
if (!fixed) {
(log ?? _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);
}
}
}
// Let the caller inspect/adjust the response headers (already merged from
// the upstream response) before the body is sent.
onResponseHeaders?.call(code, serverResponse.headers);
await copyToSink(clientResponse.stream, serverResponse,
closeSink: true);
} finally {
client.close();
}
}