org.eclipse.jetty.ee10.proxy.AbstractProxyServlet 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.ee10.proxy;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.UnavailableException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.ContinueProtocolHandler;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.ProtocolHandlers;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.client.Response;
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.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract base class for proxy servlets.
* Forwards requests to another server either as a standard web reverse
* proxy or as a transparent reverse proxy (as defined by RFC 7230).
* To facilitate JMX monitoring, the {@link HttpClient} instance is set
* as ServletContext attribute, prefixed with this servlet's name and
* exposed by the mechanism provided by
* {@link ServletContext#setAttribute(String, Object)}.
* The following init parameters may be used to configure the servlet:
*
* - preserveHost - the host header specified by the client is forwarded to the server
* - hostHeader - forces the host header to a particular value
* - viaHost - the name to use in the Via header: Via: http/1.1 <viaHost>
* - whiteList - comma-separated list of allowed proxy hosts
* - blackList - comma-separated list of forbidden proxy hosts
*
* In addition, see {@link #createHttpClient()} for init parameters
* used to configure the {@link HttpClient} instance.
* NOTE: By default the Host header sent to the server by this proxy
* servlet is the server's host name. However, this breaks redirects.
* Set {@code preserveHost} to {@code true} to make redirects working,
* although this may break server's virtual host selection.
* The default behavior of not preserving the Host header mimics
* the default behavior of Apache httpd and Nginx, which both have
* a way to be configured to preserve the Host header.
*/
public abstract class AbstractProxyServlet extends HttpServlet
{
protected static final String CLIENT_REQUEST_ATTRIBUTE = "org.eclipse.jetty.proxy.clientRequest";
protected static final Set HOP_HEADERS = Set.of(
"connection",
"keep-alive",
"proxy-authorization",
"proxy-authenticate",
"proxy-connection",
"transfer-encoding",
"te",
"trailer",
"upgrade"
);
private final Set _whiteList = new HashSet<>();
private final Set _blackList = new HashSet<>();
protected Logger _log;
private boolean _preserveHost;
private String _hostHeader;
private String _viaHost;
private HttpClient _client;
private long _timeout;
@Override
public void init() throws ServletException
{
_log = createLogger();
ServletConfig config = getServletConfig();
_preserveHost = Boolean.parseBoolean(config.getInitParameter("preserveHost"));
_hostHeader = config.getInitParameter("hostHeader");
_viaHost = config.getInitParameter("viaHost");
if (_viaHost == null)
_viaHost = viaHost();
try
{
_client = createHttpClient();
// Put the HttpClient in the context to leverage ContextHandler.MANAGED_ATTRIBUTES
getServletContext().setAttribute(config.getServletName() + ".HttpClient", _client);
String whiteList = config.getInitParameter("whiteList");
if (whiteList != null)
getWhiteListHosts().addAll(parseList(whiteList));
String blackList = config.getInitParameter("blackList");
if (blackList != null)
getBlackListHosts().addAll(parseList(blackList));
}
catch (Exception e)
{
throw new ServletException(e);
}
}
@Override
public void destroy()
{
try
{
_client.stop();
}
catch (Exception x)
{
if (_log.isDebugEnabled())
_log.debug("Failed to stop client", x);
}
}
public String getHostHeader()
{
return _hostHeader;
}
public String getViaHost()
{
return _viaHost;
}
private static String viaHost()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException x)
{
return "localhost";
}
}
public long getTimeout()
{
return _timeout;
}
public void setTimeout(long timeout)
{
this._timeout = timeout;
}
public Set getWhiteListHosts()
{
return _whiteList;
}
public Set getBlackListHosts()
{
return _blackList;
}
/**
* @return a logger instance with a name derived from this servlet's name.
*/
protected Logger createLogger()
{
String servletName = getServletConfig().getServletName();
servletName = StringUtil.replace(servletName, '-', '.');
if ((getClass().getPackage() != null) && !servletName.startsWith(getClass().getPackage().getName()))
{
servletName = getClass().getName() + "." + servletName;
}
return LoggerFactory.getLogger(servletName);
}
/**
* Creates a {@link HttpClient} instance, configured with init parameters of this servlet.
* The init parameters used to configure the {@link HttpClient} instance are:
*
* Init Parameters
*
*
* init-param
* default
* description
*
*
*
*
* maxThreads
* 256
* The max number of threads of HttpClient's Executor. If not set, or set to the value of "-", then the
* Jetty server thread pool will be used.
*
*
* maxConnections
* 32768
* The max number of connections per destination, see {@link HttpClient#setMaxConnectionsPerDestination(int)}
*
*
* idleTimeout
* 30000
* The idle timeout in milliseconds, see {@link HttpClient#setIdleTimeout(long)}
*
*
* timeout
* 60000
* The total timeout in milliseconds, see {@link Request#timeout(long, java.util.concurrent.TimeUnit)}
*
*
* requestBufferSize
* HttpClient's default
* The request buffer size, see {@link HttpClient#setRequestBufferSize(int)}
*
*
* responseBufferSize
* HttpClient's default
* The response buffer size, see {@link HttpClient#setResponseBufferSize(int)}
*
*
* selectors
* cores / 2
* The number of NIO selectors used by {@link HttpClient}
*
*
*
*
* @return a {@link HttpClient} configured from the {@link #getServletConfig() servlet configuration}
* @throws ServletException if the {@link HttpClient} cannot be created
* @see #newHttpClient()
*/
protected HttpClient createHttpClient() throws ServletException
{
ServletConfig config = getServletConfig();
HttpClient client = newHttpClient();
// Redirects must be proxied as is, not followed.
client.setFollowRedirects(false);
// Must not store cookies, otherwise cookies of different clients will mix.
client.setHttpCookieStore(new HttpCookieStore.Empty());
Executor executor;
String value = config.getInitParameter("maxThreads");
if (value == null || "-".equals(value))
{
executor = (Executor)getServletContext().getAttribute("org.eclipse.jetty.server.Executor");
if (executor == null)
throw new IllegalStateException("No server executor for proxy");
}
else
{
QueuedThreadPool qtp = new QueuedThreadPool(Integer.parseInt(value));
String servletName = config.getServletName();
int dot = servletName.lastIndexOf('.');
if (dot >= 0)
servletName = servletName.substring(dot + 1);
qtp.setName(servletName);
executor = qtp;
}
client.setExecutor(executor);
value = config.getInitParameter("maxConnections");
if (value == null)
value = "256";
client.setMaxConnectionsPerDestination(Integer.parseInt(value));
value = config.getInitParameter("idleTimeout");
if (value == null)
value = "30000";
client.setIdleTimeout(Long.parseLong(value));
value = config.getInitParameter("timeout");
if (value == null)
value = "60000";
_timeout = Long.parseLong(value);
value = config.getInitParameter("requestBufferSize");
if (value != null)
client.setRequestBufferSize(Integer.parseInt(value));
value = config.getInitParameter("responseBufferSize");
if (value != null)
client.setResponseBufferSize(Integer.parseInt(value));
try
{
client.start();
// Content must not be decoded, otherwise the client gets confused.
client.getContentDecoderFactories().clear();
// Pass traffic to the client, only intercept what's necessary.
ProtocolHandlers protocolHandlers = client.getProtocolHandlers();
protocolHandlers.clear();
protocolHandlers.put(new ProxyContinueProtocolHandler());
return client;
}
catch (Exception x)
{
throw new ServletException(x);
}
}
/**
* The servlet init parameter 'selectors' can be set for the number of
* selector threads to be used by the HttpClient.
*
* @return a new HttpClient instance
*/
protected HttpClient newHttpClient()
{
int selectors = 1;
String value = getServletConfig().getInitParameter("selectors");
if (value != null)
selectors = Integer.parseInt(value);
ClientConnector clientConnector = newClientConnector();
clientConnector.setSelectors(selectors);
return newHttpClient(clientConnector);
}
protected HttpClient newHttpClient(ClientConnector clientConnector)
{
return new HttpClient(new HttpClientTransportDynamic(clientConnector));
}
protected ClientConnector newClientConnector()
{
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(new SslContextFactory.Client());
return clientConnector;
}
protected HttpClient getHttpClient()
{
return _client;
}
private Set parseList(String list)
{
Set result = new HashSet<>();
String[] hosts = list.split(",");
for (String host : hosts)
{
host = host.trim();
if (host.length() == 0)
continue;
result.add(host);
}
return result;
}
/**
* Checks the given {@code host} and {@code port} against whitelist and blacklist.
*
* @param host the host to check
* @param port the port to check
* @return true if it is allowed to be proxy to the given host and port
*/
public boolean validateDestination(String host, int port)
{
String hostPort = host + ":" + port;
if (!_whiteList.isEmpty())
{
if (!_whiteList.contains(hostPort))
{
if (_log.isDebugEnabled())
_log.debug("Host {}:{} not whitelisted", host, port);
return false;
}
}
if (!_blackList.isEmpty())
{
if (_blackList.contains(hostPort))
{
if (_log.isDebugEnabled())
_log.debug("Host {}:{} blacklisted", host, port);
return false;
}
}
return true;
}
protected String rewriteTarget(HttpServletRequest clientRequest)
{
if (!validateDestination(clientRequest.getServerName(), clientRequest.getServerPort()))
return null;
// If the proxy is secure, we will likely get a proxied URI
// with the "https" scheme, but the upstream server needs
// to be called with the "http" scheme (the ConnectHandler
// is used to call upstream servers with the "https" scheme).
StringBuffer target = clientRequest.getRequestURL();
// Change "https" to "http".
if (HttpScheme.HTTPS.is(target.substring(0, 5)))
target.replace(4, 5, "");
String query = clientRequest.getQueryString();
if (query != null)
target.append("?").append(query);
return target.toString();
}
/**
* Callback method invoked when the URI rewrite performed
* in {@link #rewriteTarget(HttpServletRequest)} returns null
* indicating that no rewrite can be performed.
* It is possible to use blocking API in this method,
* like {@link HttpServletResponse#sendError(int)}.
*
* @param clientRequest the client request
* @param proxyResponse the client response
*/
protected void onProxyRewriteFailed(HttpServletRequest clientRequest, HttpServletResponse proxyResponse)
{
sendProxyResponseError(clientRequest, proxyResponse, HttpStatus.FORBIDDEN_403);
}
protected boolean hasContent(HttpServletRequest clientRequest)
{
long contentLength = clientRequest.getContentLengthLong();
if (contentLength == 0)
return false;
if (contentLength > 0)
return true;
return clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
}
protected boolean expects100Continue(HttpServletRequest request)
{
return HttpHeaderValue.CONTINUE.is(request.getHeader(HttpHeader.EXPECT.asString()));
}
protected Request newProxyRequest(HttpServletRequest request, String rewrittenTarget)
{
// Do not copy the HTTP version, since the client-to-proxy
// version may be different from the proxy-to-server version.
return getHttpClient().newRequest(rewrittenTarget)
.method(request.getMethod())
.attribute(CLIENT_REQUEST_ATTRIBUTE, request);
}
protected void copyRequestHeaders(HttpServletRequest clientRequest, Request proxyRequest)
{
// First clear possibly existing headers, as we are going to copy those from the client request.
HttpFields.Mutable newHeaders = HttpFields.build();
Set headersToRemove = findConnectionHeaders(clientRequest);
for (Enumeration headerNames = clientRequest.getHeaderNames(); headerNames.hasMoreElements(); )
{
String headerName = headerNames.nextElement();
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
if (HttpHeader.HOST.is(headerName) && !_preserveHost)
continue;
// Remove hop-by-hop headers.
if (HOP_HEADERS.contains(lowerHeaderName))
continue;
if (headersToRemove != null && headersToRemove.contains(lowerHeaderName))
continue;
for (Enumeration headerValues = clientRequest.getHeaders(headerName); headerValues.hasMoreElements(); )
{
String headerValue = headerValues.nextElement();
if (headerValue != null)
newHeaders.add(headerName, headerValue);
}
}
// Force the Host header if configured
if (_hostHeader != null)
newHeaders.add(HttpHeader.HOST, _hostHeader);
proxyRequest.headers(headers -> headers.clear().add(newHeaders));
}
protected Set findConnectionHeaders(HttpServletRequest clientRequest)
{
// Any header listed by the Connection header must be removed:
// http://tools.ietf.org/html/rfc7230#section-6.1.
Set hopHeaders = null;
Enumeration connectionHeaders = clientRequest.getHeaders(HttpHeader.CONNECTION.asString());
while (connectionHeaders.hasMoreElements())
{
String value = connectionHeaders.nextElement();
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(HttpServletRequest clientRequest, Request proxyRequest)
{
addViaHeader(proxyRequest);
addXForwardedHeaders(clientRequest, proxyRequest);
}
/**
* Adds the HTTP {@code Via} header to the proxied request.
*
* @param proxyRequest the request being proxied
* @see #addViaHeader(HttpServletRequest, Request)
*/
protected void addViaHeader(Request proxyRequest)
{
HttpServletRequest clientRequest = (HttpServletRequest)proxyRequest.getAttributes().get(CLIENT_REQUEST_ATTRIBUTE);
addViaHeader(clientRequest, proxyRequest);
}
/**
* Adds the HTTP {@code Via} header to the proxied request, taking into account data present in the client request.
* This method considers the protocol of the client request when forming the proxied request. If it
* is HTTP, then the protocol name will not be included in the {@code Via} header that is sent by the proxy, and only
* the protocol version will be sent. If it is not, the entire protocol (name and version) will be included.
* If the client request includes a {@code Via} header, the result will be appended to that to form a chain.
*
* @param clientRequest the client request
* @param proxyRequest the request being proxied
* @see RFC 7230 section 5.7.1
*/
protected void addViaHeader(HttpServletRequest clientRequest, Request proxyRequest)
{
String protocol = clientRequest.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();
proxyRequest.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.length() > 0)
newValue += separator;
newValue += viaHeaderValue;
return new HttpField(HttpHeader.VIA, newValue);
}));
}
protected void addXForwardedHeaders(HttpServletRequest clientRequest, Request proxyRequest)
{
proxyRequest.headers(headers -> headers.add(HttpHeader.X_FORWARDED_FOR, clientRequest.getRemoteAddr()));
proxyRequest.headers(headers -> headers.add(HttpHeader.X_FORWARDED_PROTO, clientRequest.getScheme()));
String hostHeader = clientRequest.getHeader(HttpHeader.HOST.asString());
if (hostHeader != null)
proxyRequest.headers(headers -> headers.add(HttpHeader.X_FORWARDED_HOST, hostHeader));
String localName = clientRequest.getLocalName();
if (localName != null)
proxyRequest.headers(headers -> headers.add(HttpHeader.X_FORWARDED_SERVER, localName));
}
protected void sendProxyRequest(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest)
{
if (_log.isDebugEnabled())
{
StringBuilder builder = new StringBuilder(clientRequest.getMethod());
builder.append(" ").append(clientRequest.getRequestURI());
String query = clientRequest.getQueryString();
if (query != null)
builder.append("?").append(query);
builder.append(" ").append(clientRequest.getProtocol()).append(System.lineSeparator());
for (Enumeration headerNames = clientRequest.getHeaderNames(); headerNames.hasMoreElements(); )
{
String headerName = headerNames.nextElement();
builder.append(headerName).append(": ");
for (Enumeration headerValues = clientRequest.getHeaders(headerName); headerValues.hasMoreElements(); )
{
String headerValue = headerValues.nextElement();
if (headerValue != null)
builder.append(headerValue);
if (headerValues.hasMoreElements())
builder.append(",");
}
builder.append(System.lineSeparator());
}
builder.append(System.lineSeparator());
_log.debug("{} proxying to upstream:{}{}{}{}{}",
getRequestId(clientRequest),
System.lineSeparator(),
builder,
proxyRequest,
System.lineSeparator(),
proxyRequest.getHeaders().toString().trim());
}
proxyRequest.send(newProxyResponseListener(clientRequest, proxyResponse));
}
protected abstract Response.CompleteListener newProxyResponseListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse);
protected void onClientRequestFailure(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, Throwable failure)
{
proxyRequest.abort(failure).whenComplete((aborted, x) ->
{
if (aborted)
return;
int status = clientRequestStatus(failure);
sendProxyResponseError(clientRequest, proxyResponse, status);
});
}
protected int clientRequestStatus(Throwable failure)
{
return failure instanceof TimeoutException
? HttpStatus.REQUEST_TIMEOUT_408
: HttpStatus.INTERNAL_SERVER_ERROR_500;
}
protected void onServerResponseHeaders(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
{
for (HttpField field : serverResponse.getHeaders())
{
String headerName = field.getName();
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
if (HOP_HEADERS.contains(lowerHeaderName))
continue;
String newHeaderValue = filterServerResponseHeader(clientRequest, serverResponse, headerName, field.getValue());
if (newHeaderValue == null)
continue;
proxyResponse.addHeader(headerName, newHeaderValue);
}
if (_log.isDebugEnabled())
{
StringBuilder builder = new StringBuilder(System.lineSeparator());
builder.append(clientRequest.getProtocol()).append(" ").append(proxyResponse.getStatus())
.append(" ").append(serverResponse.getReason()).append(System.lineSeparator());
for (String headerName : proxyResponse.getHeaderNames())
{
builder.append(headerName).append(": ");
for (Iterator headerValues = proxyResponse.getHeaders(headerName).iterator(); headerValues.hasNext(); )
{
String headerValue = headerValues.next();
if (headerValue != null)
builder.append(headerValue);
if (headerValues.hasNext())
builder.append(",");
}
builder.append(System.lineSeparator());
}
_log.debug("{} proxying to downstream:{}{}",
getRequestId(clientRequest),
System.lineSeparator(),
builder);
}
}
protected String filterServerResponseHeader(HttpServletRequest clientRequest, Response serverResponse, String headerName, String headerValue)
{
return headerValue;
}
protected void onProxyResponseSuccess(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
{
if (_log.isDebugEnabled())
_log.debug("{} proxying successful", getRequestId(clientRequest));
AsyncContext asyncContext = clientRequest.getAsyncContext();
asyncContext.complete();
}
protected void onProxyResponseFailure(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse, Throwable failure)
{
if (_log.isDebugEnabled())
_log.debug(getRequestId(clientRequest) + " proxying failed", failure);
int status = proxyResponseStatus(failure);
int serverStatus = serverResponse == null ? status : serverResponse.getStatus();
if (expects100Continue(clientRequest) && serverStatus >= HttpStatus.OK_200)
status = serverStatus;
sendProxyResponseError(clientRequest, proxyResponse, status);
}
protected int proxyResponseStatus(Throwable failure)
{
return failure instanceof TimeoutException
? HttpStatus.GATEWAY_TIMEOUT_504
: HttpStatus.BAD_GATEWAY_502;
}
protected int getRequestId(HttpServletRequest clientRequest)
{
return System.identityHashCode(clientRequest);
}
protected void sendProxyResponseError(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, int status)
{
try
{
if (!proxyResponse.isCommitted())
{
proxyResponse.resetBuffer();
proxyResponse.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
}
proxyResponse.sendError(status);
}
catch (Exception e)
{
_log.trace("IGNORED", e);
try
{
proxyResponse.sendError(-1);
}
catch (Exception e2)
{
_log.trace("IGNORED", e2);
}
}
finally
{
if (clientRequest.isAsyncStarted())
clientRequest.getAsyncContext().complete();
}
}
protected Runnable onContinue(HttpServletRequest clientRequest, Request proxyRequest)
{
return null;
}
/**
* Utility class that implement transparent proxy functionalities.
* Configuration parameters:
*
* - {@code proxyTo} - a mandatory URI like {@code http://host:80/context} to which the request is proxied.
* - {@code prefix} - an optional URI prefix that is stripped from the start of the forwarded URI.
*
* For example, if a request is received at "/foo/bar", the {@code proxyTo} parameter is
* {@code http://host:80/context} and the {@code prefix} parameter is "/foo", then the request would
* be proxied to {@code http://host:80/context/bar}.
*/
protected static class TransparentDelegate
{
private final AbstractProxyServlet proxyServlet;
private String _proxyTo;
private String _prefix;
protected TransparentDelegate(AbstractProxyServlet proxyServlet)
{
this.proxyServlet = proxyServlet;
}
protected void init(ServletConfig config) throws ServletException
{
_proxyTo = config.getInitParameter("proxyTo");
if (_proxyTo == null)
throw new UnavailableException("Init parameter 'proxyTo' is required.");
String prefix = config.getInitParameter("prefix");
if (prefix != null)
{
if (!prefix.startsWith("/"))
throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
_prefix = prefix;
}
// Adjust prefix value to account for context path
String contextPath = config.getServletContext().getContextPath();
_prefix = _prefix == null ? contextPath : (contextPath + _prefix);
if (proxyServlet._log.isDebugEnabled())
proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
}
protected String rewriteTarget(HttpServletRequest request)
{
String path = request.getRequestURI();
if (!path.startsWith(_prefix))
return null;
StringBuilder uri = new StringBuilder(_proxyTo);
if (_proxyTo.endsWith("/"))
uri.setLength(uri.length() - 1);
String rest = path.substring(_prefix.length());
if (!rest.isEmpty())
{
if (!rest.startsWith("/"))
uri.append("/");
uri.append(rest);
}
String query = request.getQueryString();
if (query != null)
{
// Is there at least one path segment ?
String separator = "://";
if (uri.indexOf("/", uri.indexOf(separator) + separator.length()) < 0)
uri.append("/");
uri.append("?").append(query);
}
URI rewrittenURI = URI.create(uri.toString()).normalize();
if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
return null;
return rewrittenURI.toString();
}
}
class ProxyContinueProtocolHandler extends ContinueProtocolHandler
{
@Override
protected Runnable onContinue(Request request)
{
HttpServletRequest clientRequest = (HttpServletRequest)request.getAttributes().get(CLIENT_REQUEST_ATTRIBUTE);
return AbstractProxyServlet.this.onContinue(clientRequest, request);
}
}
}