
org.eclipse.jetty.proxy.ProxyHandler Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.client.AsyncRequestContent;
import org.eclipse.jetty.client.ContentSourceRequestContent;
import org.eclipse.jetty.client.ContinueProtocolHandler;
import org.eclipse.jetty.client.EarlyHintsProtocolHandler;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.ProcessingProtocolHandler;
import org.eclipse.jetty.client.ProtocolHandlers;
import org.eclipse.jetty.client.Result;
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpCookieStore;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link Handler} that can be used to implement a {@link Forward forward
* proxy ("proxy")} or a {@link Reverse reverse proxy ("gateway")} as defined by
* RFC 7230.
* This class uses {@link HttpClient} to send requests from the proxy to the server.
* The {@code HttpClient} instance is either
* {@link #setHttpClient(HttpClient) set explicitly}, or created implicitly.
* To customize the implicit {@code HttpClient} instance, applications can
* override {@link #newHttpClient()} and {@link #configureHttpClient(HttpClient)}.
*
* @see Forward
* @see Reverse
*/
public abstract class ProxyHandler extends Handler.Abstract
{
private static final Logger LOG = LoggerFactory.getLogger(ProxyHandler.class);
private static final String CLIENT_TO_PROXY_REQUEST_ATTRIBUTE = ProxyHandler.class.getName() + ".clientToProxyRequest";
private static final String PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE = ProxyHandler.class.getName() + ".proxyToClientResponse";
private static final String PROXY_TO_SERVER_CONTINUE_ATTRIBUTE = ProxyHandler.class.getName() + ".proxyToServerContinue";
private static final EnumSet HOP_HEADERS = EnumSet.of(
HttpHeader.CONNECTION,
HttpHeader.KEEP_ALIVE,
HttpHeader.PROXY_AUTHORIZATION,
HttpHeader.PROXY_AUTHENTICATE,
HttpHeader.PROXY_CONNECTION,
HttpHeader.TRANSFER_ENCODING,
HttpHeader.TE,
HttpHeader.TRAILER,
HttpHeader.UPGRADE
);
private HttpClient httpClient;
private String proxyToServerHost;
private String viaHost;
public HttpClient getHttpClient()
{
return httpClient;
}
public void setHttpClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
/**
* Get the proxy-to-server {@code Host} header value.
* @return the proxy-to-server {@code Host} header value
*/
public String getProxyToServerHost()
{
return proxyToServerHost;
}
/**
* Sets the value to use for the {@code Host} header in proxy-to-server requests.
* If {@code null}, the client-to-proxy value is used.
*
* @param host the proxy-to-server {@code Host} header value
*/
public void setProxyToServerHost(String host)
{
this.proxyToServerHost = host;
}
/**
* Get the value to use for the {@code Via} header.
* @return the value to use for the {@code Via} header
*/
public String getViaHost()
{
return viaHost;
}
/**
* Sets the value to use for the {@code Via} header in proxy-to-server requests.
* If {@code null}, the local host name is used.
*
* @param viaHost the value to use for the {@code Via} header
*/
public void setViaHost(String viaHost)
{
this.viaHost = viaHost;
}
private static String viaHost()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException x)
{
return "localhost";
}
}
@Override
protected void doStart() throws Exception
{
if (httpClient == null)
setHttpClient(createHttpClient());
addBean(httpClient, true);
if (viaHost == null)
setViaHost(viaHost());
super.doStart();
}
private HttpClient createHttpClient()
{
HttpClient httpClient = newHttpClient();
configureHttpClient(httpClient);
LifeCycle.start(httpClient);
httpClient.getContentDecoderFactories().clear();
ProtocolHandlers protocolHandlers = httpClient.getProtocolHandlers();
protocolHandlers.clear();
protocolHandlers.put(new ProxyContinueProtocolHandler());
protocolHandlers.put(new ProxyProcessingProtocolHandler());
protocolHandlers.put(new ProxyEarlyHintsProtocolHandler());
return httpClient;
}
/**
* Creates a new {@link HttpClient} instance, by default with a thread
* pool named {@code proxy-client} and with the
* {@link HttpClientTransportDynamic dynamic transport} configured only
* with HTTP/1.1.
*
* @return a new {@code HttpClient} instance
*/
protected HttpClient newHttpClient()
{
ClientConnector clientConnector = new ClientConnector();
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
proxyClientThreads.setName("proxy-client");
clientConnector.setExecutor(proxyClientThreads);
return new HttpClient(new HttpClientTransportDynamic(clientConnector));
}
/**
* Configures the {@link HttpClient} instance before it is started.
*
* @param httpClient the {@code HttpClient} instance to configure
*/
protected void configureHttpClient(HttpClient httpClient)
{
httpClient.setFollowRedirects(false);
httpClient.setHttpCookieStore(new HttpCookieStore.Empty());
}
protected static String requestId(Request clientToProxyRequest)
{
return String.valueOf(System.identityHashCode(clientToProxyRequest));
}
@Override
public boolean handle(Request clientToProxyRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
if (LOG.isDebugEnabled())
LOG.debug("""
{} C2P received request
{}
{}""",
requestId(clientToProxyRequest),
clientToProxyRequest,
clientToProxyRequest.getHeaders());
HttpURI rewritten = rewriteHttpURI(clientToProxyRequest);
if (LOG.isDebugEnabled())
LOG.debug("{} URI rewrite {} => {}", requestId(clientToProxyRequest), clientToProxyRequest.getHttpURI(), rewritten);
var proxyToServerRequest = newProxyToServerRequest(clientToProxyRequest, rewritten);
proxyToServerRequest.attribute(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE, clientToProxyRequest)
.attribute(PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE, proxyToClientResponse);
copyRequestHeaders(clientToProxyRequest, proxyToServerRequest);
addProxyHeaders(clientToProxyRequest, proxyToServerRequest);
if (hasContent(clientToProxyRequest))
{
if (expects100Continue(clientToProxyRequest))
{
// Delay reading the content until the server sends 100 Continue.
AsyncRequestContent delayedProxyToServerRequestContent = new AsyncRequestContent();
proxyToServerRequest.body(delayedProxyToServerRequestContent);
Runnable action = () ->
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2S continuing request", requestId(clientToProxyRequest));
var proxyToServerRequestContent = newProxyToServerRequestContent(clientToProxyRequest, proxyToClientResponse, proxyToServerRequest);
Content.copy(proxyToServerRequestContent, delayedProxyToServerRequestContent,
Callback.from(delayedProxyToServerRequestContent::close, delayedProxyToServerRequestContent::fail));
};
proxyToServerRequest.attribute(PROXY_TO_SERVER_CONTINUE_ATTRIBUTE, action);
}
else
{
var proxyToServerRequestContent = newProxyToServerRequestContent(clientToProxyRequest, proxyToClientResponse, proxyToServerRequest);
proxyToServerRequest.body(proxyToServerRequestContent);
}
}
sendProxyToServerRequest(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback);
return true;
}
/**
* Rewrites the client-to-proxy request URI to the proxy-to-server request URI.
*
* @param clientToProxyRequest the client-to-proxy request
* @return an {@code HttpURI} for the proxy-to-server request
*/
protected abstract HttpURI rewriteHttpURI(Request clientToProxyRequest);
protected org.eclipse.jetty.client.Request newProxyToServerRequest(Request clientToProxyRequest, HttpURI newHttpURI)
{
return getHttpClient().newRequest(newHttpURI.toURI())
.method(clientToProxyRequest.getMethod());
}
protected void copyRequestHeaders(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest)
{
Set headersToRemove = findConnectionHeaders(clientToProxyRequest);
for (HttpField clientToProxyRequestField : clientToProxyRequest.getHeaders())
{
HttpHeader clientToProxyRequestHeader = clientToProxyRequestField.getHeader();
if (HttpHeader.HOST == clientToProxyRequestHeader)
{
String host = getProxyToServerHost();
if (host != null)
{
proxyToServerRequest.headers(headers -> headers.put(HttpHeader.HOST, host));
continue;
}
}
if (HOP_HEADERS.contains(clientToProxyRequestHeader))
continue;
if (headersToRemove != null && headersToRemove.contains(clientToProxyRequestField.getLowerCaseName()))
continue;
proxyToServerRequest.headers(headers -> headers.add(clientToProxyRequestField));
}
}
private Set findConnectionHeaders(Request clientToProxyRequest)
{
// Any header listed by the Connection header must be removed:
// http://tools.ietf.org/html/rfc7230#section-6.1.
Set hopHeaders = null;
List connectionHeaders = clientToProxyRequest.getHeaders().getValuesList(HttpHeader.CONNECTION);
for (String value : connectionHeaders)
{
String[] values = value.split(",");
for (String name : values)
{
name = name.trim().toLowerCase(Locale.ENGLISH);
if (hopHeaders == null)
hopHeaders = new HashSet<>();
hopHeaders.add(name);
}
}
return hopHeaders;
}
protected void addProxyHeaders(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest)
{
addViaHeader(clientToProxyRequest, proxyToServerRequest);
addForwardedHeader(clientToProxyRequest, proxyToServerRequest);
}
protected void addViaHeader(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest)
{
String protocol = clientToProxyRequest.getConnectionMetaData().getProtocol();
String[] parts = protocol.split("/", 2);
// Retain only the version if the protocol is HTTP.
String protocolPart = parts.length == 2 && "HTTP".equalsIgnoreCase(parts[0]) ? parts[1] : protocol;
String viaHeaderValue = protocolPart + " " + getViaHost();
proxyToServerRequest.headers(headers -> headers.computeField(HttpHeader.VIA, (header, viaFields) ->
{
if (viaFields == null || viaFields.isEmpty())
return new HttpField(header, viaHeaderValue);
String separator = ", ";
String newValue = viaFields.stream()
.flatMap(field -> Stream.of(field.getValues()))
.filter(value -> !StringUtil.isBlank(value))
.collect(Collectors.joining(separator));
if (!newValue.isEmpty())
newValue += separator;
newValue += viaHeaderValue;
return new HttpField(HttpHeader.VIA, newValue);
}));
}
protected void addForwardedHeader(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest)
{
String byAttr = Request.getLocalAddr(clientToProxyRequest);
String forAttr = Request.getRemoteAddr(clientToProxyRequest);
String hostAttr = clientToProxyRequest.getHeaders().get(HttpHeader.HOST);
String scheme = clientToProxyRequest.getHttpURI().getScheme();
// Even if the request came through a secure channel, look at the original scheme if present.
// For example, a client with a forward proxy may want to communicate in clear-text with the
// server (so the scheme is http), but securely with the forward proxy (so isSecure() is true).
String protoAttr = scheme == null ? (clientToProxyRequest.isSecure() ? "https" : "http") : scheme;
String forwardedValue = "by=%s;for=%s;host=%s;proto=%s".formatted(
HttpField.PARAMETER_TOKENIZER.quote(byAttr),
HttpField.PARAMETER_TOKENIZER.quote(forAttr),
HttpField.PARAMETER_TOKENIZER.quote(hostAttr),
protoAttr
);
proxyToServerRequest.headers(headers -> headers.computeField(HttpHeader.FORWARDED, (header, fields) ->
{
String newValue;
if (fields == null || fields.isEmpty())
{
newValue = forwardedValue;
}
else
{
String separator = ", ";
newValue = fields.stream()
.flatMap(field -> field.getValueList().stream())
.collect(Collectors.joining(separator));
newValue += separator + forwardedValue;
}
return new HttpField(HttpHeader.FORWARDED, newValue);
}));
}
private boolean hasContent(Request clientToProxyRequest)
{
long contentLength = clientToProxyRequest.getLength();
if (contentLength == 0)
return false;
if (contentLength > 0)
return true;
return clientToProxyRequest.getHeaders().get(HttpHeader.TRANSFER_ENCODING) != null;
}
private boolean expects100Continue(Request clientToProxyRequest)
{
return HttpHeaderValue.CONTINUE.is(clientToProxyRequest.getHeaders().get(HttpHeader.EXPECT));
}
protected org.eclipse.jetty.client.Request.Content newProxyToServerRequestContent(Request clientToProxyRequest, Response proxyToClientResponse, org.eclipse.jetty.client.Request proxyToServerRequest)
{
return new ProxyRequestContent(clientToProxyRequest);
}
protected void sendProxyToServerRequest(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
if (LOG.isDebugEnabled())
{
LOG.debug("""
{} P2S sending request
{}
{}""",
requestId(clientToProxyRequest),
proxyToServerRequest,
proxyToServerRequest.getHeaders());
}
proxyToServerRequest.send(newServerToProxyResponseListener(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback));
}
protected org.eclipse.jetty.client.Response.CompleteListener newServerToProxyResponseListener(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
return new ProxyResponseListener(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback);
}
protected HttpField filterServerToProxyResponseField(HttpField serverToProxyResponseField)
{
return serverToProxyResponseField;
}
protected void onServerToProxyResponseFailure(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, org.eclipse.jetty.client.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback, Throwable failure)
{
int status = HttpStatus.BAD_GATEWAY_502;
if (failure instanceof TimeoutException)
status = HttpStatus.GATEWAY_TIMEOUT_504;
Callback callback = new ProxyToClientResponseFailureCallback(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback);
Response.writeError(clientToProxyRequest, proxyToClientResponse, callback, status);
}
protected Runnable onServerToProxyResponse100Continue(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C 100 continue response", requestId(clientToProxyRequest));
return (Runnable)proxyToServerRequest.getAttributes().get(PROXY_TO_SERVER_CONTINUE_ATTRIBUTE);
}
protected void onServerToProxyResponse102Processing(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders, Response proxyToClientResponse)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C 102 interim response {}", requestId(clientToProxyRequest), serverToProxyResponseHeaders);
proxyToClientResponse.writeInterim(HttpStatus.PROCESSING_102, serverToProxyResponseHeaders);
}
protected void onServerToProxyResponse103EarlyHints(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders, Response proxyToClientResponse)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C 103 interim response {}", requestId(clientToProxyRequest), serverToProxyResponseHeaders);
proxyToClientResponse.writeInterim(HttpStatus.EARLY_HINTS_103, serverToProxyResponseHeaders);
}
protected void onProxyToClientResponseComplete(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, org.eclipse.jetty.client.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback)
{
proxyToClientCallback.succeeded();
}
protected void onProxyToClientResponseFailure(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, org.eclipse.jetty.client.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback, Throwable failure)
{
// There is no point trying to write an error,
// we already know we cannot write to the client.
proxyToClientCallback.failed(failure);
}
/**
* A {@code ProxyHandler} that can be used to implement a forward proxy server.
* Forward proxies are configured in client applications that use
* {@link HttpClient} in this way:
* {@code
* httpClient.getProxyConfiguration().addProxy(new HttpProxy(proxyHost, proxyPort));
* }
*
* @see org.eclipse.jetty.client.ProxyConfiguration
* @see org.eclipse.jetty.client.HttpProxy
* @see Reverse
*/
public static class Forward extends ProxyHandler
{
/**
* {@inheritDoc}
* Applications that use this class should return the client-to-proxy
* request URI, since clients will send the absolute URI of the server.
*
* @param clientToProxyRequest the client-to-proxy request
* @return the client-to-proxy request URI
*/
@Override
protected HttpURI rewriteHttpURI(Request clientToProxyRequest)
{
return clientToProxyRequest.getHttpURI();
}
}
/**
* A {@code ProxyHandler} that can be used to implement a reverse proxy.
* A reverse proxy must rewrite the client-to-proxy request URI into the
* proxy-to-server request URI.
* This can be done by providing a rewrite function to the constructor,
* and/or override {@link #rewriteHttpURI(Request)}.
*
* @see Forward
*/
public static class Reverse extends ProxyHandler
{
private final Function httpURIRewriter;
/**
* Convenience constructor that provides a rewrite function
* using {@link String#replaceAll(String, String)}.
* As a simple example, given the URI pattern of:
* {@code (https?)://([a-z]+):([0-9]+)/([^/]+)/(.*)}
* and given a replacement string of:
* {@code $1://$2:9000/proxy/$5}
* an incoming {@code HttpURI} of:
* {@code http://host:8080/ctx/path}
* will be rewritten as:
* {@code http://host:9000/proxy/path}
*
* @param uriPattern the regex pattern to use to match the incoming URI
* @param uriReplacement the replacement string to use to rewrite the incoming URI
*/
public Reverse(String uriPattern, String uriReplacement)
{
this(request ->
{
String uri = request.getHttpURI().toString();
return HttpURI.build(uri.replaceAll(uriPattern, uriReplacement));
});
}
/**
* Creates a new instance with the given {@code HttpURI} rewrite function.
* The rewrite functions rewrites the client-to-proxy request URI into the
* proxy-to-server request URI.
*
* @param httpURIRewriter a function that returns the URI of the server
*/
public Reverse(Function httpURIRewriter)
{
this.httpURIRewriter = Objects.requireNonNull(httpURIRewriter);
}
public Function getHttpURIRewriter()
{
return httpURIRewriter;
}
/**
* {@inheritDoc}
* Applications that use this class typically provide a rewrite
* function to the constructor.
* The rewrite function rewrites the client-to-proxy request URI,
* for example {@code http://example.com/app/path}, to the proxy-to-server
* request URI, for example {@code http://backend1:8080/legacy/path}.
*
* @param clientToProxyRequest the client-to-proxy request
* @return the proxy-to-server request URI.
*/
@Override
protected HttpURI rewriteHttpURI(Request clientToProxyRequest)
{
return getHttpURIRewriter().apply(clientToProxyRequest);
}
}
protected static class ProxyRequestContent extends ContentSourceRequestContent
{
public ProxyRequestContent(Request clientToProxyRequest)
{
super(clientToProxyRequest, clientToProxyRequest.getHeaders().get(HttpHeader.CONTENT_TYPE));
}
@Override
public Request getContentSource()
{
return (Request)super.getContentSource();
}
@Override
public Content.Chunk read()
{
Content.Chunk chunk = super.read();
if (LOG.isDebugEnabled())
LOG.debug("{} C2P read content {}", requestId(getContentSource()), chunk);
return chunk;
}
}
protected class ProxyResponseListener extends Callback.Completable implements org.eclipse.jetty.client.Response.Listener
{
private final Request clientToProxyRequest;
private final org.eclipse.jetty.client.Request proxyToServerRequest;
private final Response proxyToClientResponse;
private final Callback proxyToClientCallback;
public ProxyResponseListener(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
{
this.clientToProxyRequest = clientToProxyRequest;
this.proxyToServerRequest = proxyToServerRequest;
this.proxyToClientResponse = proxyToClientResponse;
this.proxyToClientCallback = proxyToClientCallback;
}
@Override
public void onBegin(org.eclipse.jetty.client.Response serverToProxyResponse)
{
proxyToClientResponse.setStatus(serverToProxyResponse.getStatus());
}
@Override
public void onHeaders(org.eclipse.jetty.client.Response serverToProxyResponse)
{
if (LOG.isDebugEnabled())
{
LOG.debug("""
{} S2P received response
{}
{}""",
requestId(clientToProxyRequest),
serverToProxyResponse,
serverToProxyResponse.getHeaders());
}
for (HttpField serverToProxyResponseField : serverToProxyResponse.getHeaders())
{
if (HOP_HEADERS.contains(serverToProxyResponseField.getHeader()))
continue;
HttpField newField = filterServerToProxyResponseField(serverToProxyResponseField);
if (newField == null)
continue;
proxyToClientResponse.getHeaders().add(newField);
}
if (LOG.isDebugEnabled())
{
LOG.debug("""
{} P2C sending response
{}
{}""",
requestId(clientToProxyRequest),
proxyToClientResponse,
proxyToClientResponse.getHeaders());
}
}
@Override
public void onContent(org.eclipse.jetty.client.Response serverToProxyResponse, Content.Chunk serverToProxyChunk, Runnable serverToProxyDemander)
{
ByteBuffer serverToProxyContent = serverToProxyChunk.getByteBuffer();
if (LOG.isDebugEnabled())
LOG.debug("{} S2P received content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
serverToProxyChunk.retain();
Callback callback = new Callback()
{
@Override
public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C succeeded to write content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
serverToProxyChunk.release();
serverToProxyDemander.run();
}
@Override
public void failed(Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C failed to write content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent), failure);
serverToProxyChunk.release();
// Cannot write towards the client, abort towards the server.
serverToProxyResponse.abort(failure);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
};
proxyToClientResponse.write(false, serverToProxyContent, callback);
}
@Override
public void onSuccess(org.eclipse.jetty.client.Response serverToProxyResponse)
{
proxyToClientResponse.write(true, BufferUtil.EMPTY_BUFFER, this);
}
@Override
public void onComplete(Result result)
{
if (result.isSucceeded())
{
// Wait for the last write to complete.
whenComplete((r, failure) ->
{
if (failure == null)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response complete {}", requestId(clientToProxyRequest), proxyToClientResponse);
onProxyToClientResponseComplete(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback);
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response failure {}", requestId(clientToProxyRequest), proxyToClientResponse, failure);
onProxyToClientResponseFailure(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback, failure);
}
});
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("{} S2P failure {}", requestId(clientToProxyRequest), result.getResponse(), result.getFailure());
onServerToProxyResponseFailure(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback, result.getFailure());
}
}
}
private class ProxyToClientResponseFailureCallback implements Callback
{
private final Request clientToProxyRequest;
private final org.eclipse.jetty.client.Request proxyToServerRequest;
private final org.eclipse.jetty.client.Response serverToProxyResponse;
private final Response proxyToClientResponse;
private final Callback proxyToClientCallback;
private ProxyToClientResponseFailureCallback(Request clientToProxyRequest, org.eclipse.jetty.client.Request proxyToServerRequest, org.eclipse.jetty.client.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback)
{
this.clientToProxyRequest = clientToProxyRequest;
this.proxyToServerRequest = proxyToServerRequest;
this.serverToProxyResponse = serverToProxyResponse;
this.proxyToClientResponse = proxyToClientResponse;
this.proxyToClientCallback = proxyToClientCallback;
}
@Override
public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response complete {}", requestId(clientToProxyRequest), proxyToClientResponse);
onProxyToClientResponseComplete(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback);
}
@Override
public void failed(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("{} P2C response failure {}", requestId(clientToProxyRequest), proxyToClientResponse, x);
onProxyToClientResponseFailure(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback, x);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
}
private class ProxyContinueProtocolHandler extends ContinueProtocolHandler
{
@Override
protected Runnable onContinue(org.eclipse.jetty.client.Request proxyToServerRequest)
{
var clientToProxyRequest = (Request)proxyToServerRequest.getAttributes().get(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE);
if (LOG.isDebugEnabled())
LOG.debug("{} S2P received 100 Continue", requestId(clientToProxyRequest));
return onServerToProxyResponse100Continue(clientToProxyRequest, proxyToServerRequest);
}
}
private class ProxyProcessingProtocolHandler extends ProcessingProtocolHandler
{
@Override
protected void onProcessing(org.eclipse.jetty.client.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders)
{
super.onProcessing(proxyToServerRequest, serverToProxyResponseHeaders);
var clientToProxyRequest = (Request)proxyToServerRequest.getAttributes().get(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE);
if (LOG.isDebugEnabled())
LOG.debug("{} S2P received 102 Processing", requestId(clientToProxyRequest));
var proxyToClientResponse = (Response)proxyToServerRequest.getAttributes().get(PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE);
onServerToProxyResponse102Processing(clientToProxyRequest, proxyToServerRequest, serverToProxyResponseHeaders, proxyToClientResponse);
}
}
private class ProxyEarlyHintsProtocolHandler extends EarlyHintsProtocolHandler
{
@Override
protected void onEarlyHints(org.eclipse.jetty.client.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders)
{
super.onEarlyHints(proxyToServerRequest, serverToProxyResponseHeaders);
var clientToProxyRequest = (Request)proxyToServerRequest.getAttributes().get(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE);
if (LOG.isDebugEnabled())
LOG.debug("{} S2P received 103 Early Hints", requestId(clientToProxyRequest));
var proxyToClientResponse = (Response)proxyToServerRequest.getAttributes().get(PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE);
onServerToProxyResponse103EarlyHints(clientToProxyRequest, proxyToServerRequest, serverToProxyResponseHeaders, proxyToClientResponse);
}
}
}