Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;

import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT_ENCODING;
Expand Down Expand Up @@ -79,6 +80,7 @@
import static org.asynchttpclient.util.HttpUtils.hostHeader;
import static org.asynchttpclient.util.HttpUtils.originHeader;
import static org.asynchttpclient.util.HttpUtils.urlEncodeFormParams;
import static org.asynchttpclient.util.MiscUtils.closeSilently;
import static org.asynchttpclient.util.MiscUtils.isNonEmpty;
import static org.asynchttpclient.ws.WebSocketUtils.getWebSocketKey;

Expand Down Expand Up @@ -213,6 +215,32 @@ public void setProxyAuthorizationHeader(HttpHeaders headers, String proxyAuthori
}
}

private static void addProxyCustomHeaders(HttpHeaders headers, Request request, ProxyServer proxyServer) {
Function<Request, HttpHeaders> customHeaders = proxyServer.getCustomHeaders();
if (customHeaders == null) {
return;
}
HttpHeaders proxyHeaders = customHeaders.apply(request);
if (proxyHeaders == null) {
return;
}
for (String name : proxyHeaders.names()) {
// Framing is the message's, not the caller's: on a CONNECT, which has no body, Netty would
// write a chunk terminator into the tunnel. Upgrade goes with them.
if (CONTENT_LENGTH.contentEqualsIgnoreCase(name) || TRANSFER_ENCODING.contentEqualsIgnoreCase(name)
|| UPGRADE.contentEqualsIgnoreCase(name)) {
continue;
}
if (CONNECTION.contentEqualsIgnoreCase(name)) {
// List-valued, and replacing it would drop the close token keepAlive=false put there.
headers.add(name, proxyHeaders.getAll(name));
} else {
// Replace: a second Host line is a 400 from a conforming recipient (RFC 9112 section 3.2).
headers.set(name, proxyHeaders.getAll(name));
}
}
}

public NettyRequest newNettyRequest(Request request, boolean performConnectRequest, ProxyServer proxyServer, Realm realm, Realm proxyRealm) {
Uri uri = request.getUri();
HttpMethod method = performConnectRequest ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod());
Expand Down Expand Up @@ -331,7 +359,20 @@ public NettyRequest newNettyRequest(Request request, boolean performConnectReque
// A ws:// request is tunnelled through CONNECT the same way wss:// is (see
// NettyRequestSender.needConnect), so the upgrade request that follows also reaches the origin, not
// the proxy; exclude it from the plain-HTTP branch the same way wss:// already is.
// Custom headers are proxy-scoped too, so they travel under the same gate, and before the generated
// header so a realm still decides Proxy-Authorization.
if ((connect || (!uri.isSecured() && !uri.isWebSocket())) && proxyServer != null && proxyServer.getProxyType().isHttp()) {
try {
addProxyCustomHeaders(headers, request, proxyServer);
} catch (RuntimeException | Error e) {
// Caller code, and a CR or LF in what it returns throws here too. Nothing owns nettyRequest
// until this method returns, so a throw would strand the body it already holds.
nettyRequest.release();
if (body instanceof NettyBodyBody) {
closeSilently(((NettyBodyBody) body).getBody());
}
throw e;
}
setProxyAuthorizationHeader(headers, perRequestProxyAuthorizationHeader(request, proxyRealm));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,12 +451,6 @@ private <T> ListenableFuture<T> sendRequestWithNewChannel(Request request, Proxy
// some headers are only set when performing the first request
HttpRequest nettyRequest = future.getNettyRequest().getHttpRequest();
HttpHeaders headers = nettyRequest.headers();
if (proxy != null && proxy.getCustomHeaders() != null) {
HttpHeaders customHeaders = proxy.getCustomHeaders().apply(request);
if (customHeaders != null) {
headers.add(customHeaders);
}
}
Realm realm = future.getRealm();
Realm proxyRealm = future.getProxyRealm();
// On the tunnel path this is the CONNECT request, sent to the proxy in the clear before the TLS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ public Builder setProxyType(ProxyType proxyType) {
return this;
}

/**
* Headers for the proxy itself, sent only on a request the proxy reads: an absolute-form request or a
* CONNECT. A SOCKS proxy never parses the HTTP it carries, so it gets none.
*
* <p>An HTTP proxy forwards what it does not recognise, so to keep one of these off the origin, name
* it in a {@code Connection} header of the same set.
*
* <p>Called once per request built for the proxy, on an I/O thread.
*/
public Builder setCustomHeaders(Function<Request, HttpHeaders> customHeaders) {
this.customHeaders = customHeaders;
return this;
Expand Down
Loading
Loading