469414 - Proxied redirects expose upstream server name.
Introduced "preserveHost" init-parameter similar to what Apache and Nginx have.
This commit is contained in:
parent
bfe6c2638e
commit
9306477f5b
|
@ -33,6 +33,7 @@ import java.util.concurrent.TimeoutException;
|
|||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.UnavailableException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
@ -50,6 +51,32 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
||||
/**
|
||||
* <p>Abstract base class for proxy servlets.</p>
|
||||
* <p>Forwards requests to another server either as a standard web reverse
|
||||
* proxy or as a transparent reverse proxy (as defined by RFC 7230).</p>
|
||||
* <p>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)}.</p>
|
||||
* <p>The following init parameters may be used to configure the servlet:</p>
|
||||
* <ul>
|
||||
* <li>preserveHost - the host header specified by the client is forwarded to the server</li>
|
||||
* <li>hostHeader - forces the host header to a particular value</li>
|
||||
* <li>viaHost - the name to use in the Via header: Via: http/1.1 <viaHost></li>
|
||||
* <li>whiteList - comma-separated list of allowed proxy hosts</li>
|
||||
* <li>blackList - comma-separated list of forbidden proxy hosts</li>
|
||||
* </ul>
|
||||
* <p>In addition, see {@link #createHttpClient()} for init parameters
|
||||
* used to configure the {@link HttpClient} instance.</p>
|
||||
* <p>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.</p>
|
||||
* <p>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.</p>
|
||||
*/
|
||||
public abstract class AbstractProxyServlet extends HttpServlet
|
||||
{
|
||||
protected static final Set<String> HOP_HEADERS;
|
||||
|
@ -71,6 +98,7 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
private final Set<String> _whiteList = new HashSet<>();
|
||||
private final Set<String> _blackList = new HashSet<>();
|
||||
protected Logger _log;
|
||||
private boolean _preserveHost;
|
||||
private String _hostHeader;
|
||||
private String _viaHost;
|
||||
private HttpClient _client;
|
||||
|
@ -83,6 +111,8 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
|
||||
ServletConfig config = getServletConfig();
|
||||
|
||||
_preserveHost = Boolean.parseBoolean(config.getInitParameter("preserveHost"));
|
||||
|
||||
_hostHeader = config.getInitParameter("hostHeader");
|
||||
|
||||
_viaHost = config.getInitParameter("viaHost");
|
||||
|
@ -181,9 +211,8 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link HttpClient} instance, configured with init parameters of this servlet.
|
||||
* <p/>
|
||||
* The init parameters used to configure the {@link HttpClient} instance are:
|
||||
* <p>Creates a {@link HttpClient} instance, configured with init parameters of this servlet.</p>
|
||||
* <p>The init parameters used to configure the {@link HttpClient} instance are:</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr>
|
||||
|
@ -408,7 +437,7 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
String headerName = headerNames.nextElement();
|
||||
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
if (HttpHeader.HOST.is(headerName))
|
||||
if (HttpHeader.HOST.is(headerName) && !_preserveHost)
|
||||
continue;
|
||||
|
||||
// Remove hop-by-hop headers.
|
||||
|
|
|
@ -55,7 +55,15 @@ import org.eclipse.jetty.util.CountingCallback;
|
|||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
import org.eclipse.jetty.util.component.Destroyable;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
/**
|
||||
* <p>Servlet 3.1 asynchronous proxy servlet with capability
|
||||
* to intercept and modify request/response content.</p>
|
||||
* <p>Both the request processing and the I/O are asynchronous.</p>
|
||||
*
|
||||
* @see ProxyServlet
|
||||
* @see AsyncProxyServlet
|
||||
* @see ConnectHandler
|
||||
*/
|
||||
public class AsyncMiddleManServlet extends AbstractProxyServlet
|
||||
{
|
||||
private static final String CLIENT_TRANSFORMER = AsyncMiddleManServlet.class.getName() + ".clientTransformer";
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
|||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritePendingException;
|
||||
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -38,6 +39,14 @@ import org.eclipse.jetty.client.util.DeferredContentProvider;
|
|||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
|
||||
/**
|
||||
* <p>Servlet 3.1 asynchronous proxy servlet.</p>
|
||||
* <p>Both the request processing and the I/O are asynchronous.</p>
|
||||
*
|
||||
* @see ProxyServlet
|
||||
* @see AsyncMiddleManServlet
|
||||
* @see ConnectHandler
|
||||
*/
|
||||
public class AsyncProxyServlet extends ProxyServlet
|
||||
{
|
||||
private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener";
|
||||
|
|
|
@ -23,14 +23,13 @@ import java.io.InputStream;
|
|||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentProvider;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
|
@ -40,26 +39,11 @@ import org.eclipse.jetty.http.HttpVersion;
|
|||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
* Asynchronous ProxyServlet.
|
||||
* <p/>
|
||||
* Forwards requests to another server either as a standard web reverse proxy
|
||||
* (as defined by RFC2616) or as a transparent reverse proxy.
|
||||
* <p/>
|
||||
* To facilitate JMX monitoring, the {@link HttpClient} instance is set as context attribute,
|
||||
* prefixed with the servlet's name and exposed by the mechanism provided by
|
||||
* {@link ServletContext#setAttribute(String, Object)}.
|
||||
* <p/>
|
||||
* The following init parameters may be used to configure the servlet:
|
||||
* <ul>
|
||||
* <li>hostHeader - forces the host header to a particular value</li>
|
||||
* <li>viaHost - the name to use in the Via header: Via: http/1.1 <viaHost></li>
|
||||
* <li>whiteList - comma-separated list of allowed proxy hosts</li>
|
||||
* <li>blackList - comma-separated list of forbidden proxy hosts</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* In addition, see {@link #createHttpClient()} for init parameters used to configure
|
||||
* the {@link HttpClient} instance.
|
||||
* <p>Servlet 3.0 asynchronous proxy servlet.</p>
|
||||
* <p>The request processing is asynchronous, but the I/O is blocking.</p>
|
||||
*
|
||||
* @see AsyncProxyServlet
|
||||
* @see AsyncMiddleManServlet
|
||||
* @see ConnectHandler
|
||||
*/
|
||||
public class ProxyServlet extends AbstractProxyServlet
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
package org.eclipse.jetty.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -42,13 +45,27 @@ public class ReverseProxyTest
|
|||
{
|
||||
@Rule
|
||||
public final TestTracker tracker = new TestTracker();
|
||||
private HttpClient client;
|
||||
private Server proxy;
|
||||
private ServerConnector proxyConnector;
|
||||
private Server server;
|
||||
private ServerConnector serverConnector;
|
||||
private Server proxy;
|
||||
private ServerConnector proxyConnector;
|
||||
private HttpClient client;
|
||||
|
||||
private void startProxy() throws Exception
|
||||
private void startServer(HttpServlet servlet) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
|
||||
serverConnector = new ServerConnector(server);
|
||||
server.addConnector(serverConnector);
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
private void startProxy(Map<String, String> params) throws Exception
|
||||
{
|
||||
proxy = new Server();
|
||||
|
||||
|
@ -65,7 +82,7 @@ public class ReverseProxyTest
|
|||
protected String rewriteTarget(HttpServletRequest clientRequest)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(clientRequest.getScheme()).append("://localhost:");
|
||||
builder.append(clientRequest.getScheme()).append("://127.0.0.1:");
|
||||
builder.append(serverConnector.getLocalPort());
|
||||
builder.append(clientRequest.getRequestURI());
|
||||
String query = clientRequest.getQueryString();
|
||||
|
@ -74,25 +91,14 @@ public class ReverseProxyTest
|
|||
return builder.toString();
|
||||
}
|
||||
});
|
||||
if (params != null)
|
||||
proxyServletHolder.setInitParameters(params);
|
||||
|
||||
proxyContext.addServlet(proxyServletHolder, "/*");
|
||||
|
||||
proxy.start();
|
||||
}
|
||||
|
||||
private void startServer(HttpServlet servlet) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
|
||||
serverConnector = new ServerConnector(server);
|
||||
server.addConnector(serverConnector);
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
private void startClient() throws Exception
|
||||
{
|
||||
client = new HttpClient();
|
||||
|
@ -108,17 +114,37 @@ public class ReverseProxyTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testClientHostHeaderUpdatedWhenSentToServer() throws Exception
|
||||
public void testHostHeaderUpdatedWhenSentToServer() throws Exception
|
||||
{
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Assert.assertEquals(request.getServerPort(), serverConnector.getLocalPort());
|
||||
Assert.assertEquals("127.0.0.1", request.getServerName());
|
||||
Assert.assertEquals(serverConnector.getLocalPort(), request.getServerPort());
|
||||
}
|
||||
});
|
||||
startProxy();
|
||||
startProxy(null);
|
||||
startClient();
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()).send();
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostHeaderPreserved() throws Exception
|
||||
{
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Assert.assertEquals("localhost", request.getServerName());
|
||||
Assert.assertEquals(proxyConnector.getLocalPort(), request.getServerPort());
|
||||
}
|
||||
});
|
||||
startProxy(new HashMap<String, String>() {{ put("preserveHost", "true"); }});
|
||||
startClient();
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()).send();
|
||||
|
|
Loading…
Reference in New Issue