com.pastdev.httpcomponents.servlet.ReverseProxyServlet Maven / Gradle / Ivy
package com.pastdev.httpcomponents.servlet;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.UUID;
import javax.naming.NamingException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.AbstractExecutionAwareRequest;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.HeaderGroup;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.pastdev.http.client.DefaultHttpClientFactory;
import com.pastdev.http.client.HttpClientFactory;
import com.pastdev.httpcomponents.configuration.Configuration;
import com.pastdev.httpcomponents.configuration.ConfigurationChain;
import com.pastdev.httpcomponents.configuration.InitParameterConfiguration;
import com.pastdev.httpcomponents.configuration.JndiConfiguration;
import com.pastdev.httpcomponents.util.ProxyUri;
public class ReverseProxyServlet extends HttpServlet {
private static final long serialVersionUID = 9091933627516767566L;
private static Logger logger = LoggerFactory.getLogger( ReverseProxyServlet.class );
/* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 */
public static final HeaderGroup HOB_BY_HOP_HEADERS;
public static final String JNDI_ROOT;
public static final String REQUEST_ATTRIBUTE_X_REQUEST_ID =
ReverseProxyServlet.class.getName() + ".X-Request-ID";
static {
String jndiRoot = System.getProperty( "httpcomponents.reverseproxy.jndiroot" );
if ( jndiRoot == null ) {
JNDI_ROOT = "java:/comp/env/httpcomponents/reverseproxy";
}
else {
JNDI_ROOT = "java:/comp/env/" + jndiRoot;
}
HOB_BY_HOP_HEADERS = new HeaderGroup();
String[] headers = new String[] {
"Connection", "Keep-Alive", "Proxy-Authenticate",
"Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding",
"Upgrade" };
for ( String header : headers ) {
HOB_BY_HOP_HEADERS.addHeader( new BasicHeader( header, null ) );
}
}
private Configuration configuration;
private boolean setXForwarded;
private boolean setXRequestId;
private ProxyUri proxyUri;
private ReverseProxyResponseHandler responseHandler =
new DefaultReverseProxyResponseHandler();
private ProxyRequestPreprocessor proxyRequestPreprocessor;
private void copyRequestHeaders( HttpServletRequest servletRequest, HttpRequest proxyRequest ) {
Enumeration headerNames = servletRequest.getHeaderNames();
while ( headerNames.hasMoreElements() ) {
String headerName = headerNames.nextElement();
if ( headerName.equalsIgnoreCase( HttpHeaders.CONTENT_LENGTH ) ) {
// Instead the content-length is effectively set via
// InputStreamEntity
continue;
}
if ( HOB_BY_HOP_HEADERS.containsHeader( headerName ) ) {
continue;
}
Enumeration headers = servletRequest.getHeaders( headerName );
while ( headers.hasMoreElements() ) {
String headerValue = headers.nextElement();
// In case the proxy host is running multiple virtual servers,
// rewrite the Host header to ensure that we get content from
// the correct virtual server
if ( headerName.equalsIgnoreCase( HttpHeaders.HOST ) ) {
headerValue = proxyUri.getHostName();
if ( proxyUri.getPort() != -1 ) {
headerValue += ":" + proxyUri.getPort();
}
}
proxyRequest.addHeader( headerName, headerValue );
}
}
}
@Override
public String getServletInfo() {
return "A composable proxy servlet";
}
@Override
public void init() throws ServletException {
if ( configuration == null ) {
try {
ServletConfig servletConfig = getServletConfig();
String jndiRoot = JNDI_ROOT + "/" +
URLEncoder.encode( servletConfig.getServletName(),
"UTF-8" );
logger.debug( "no configuration specified, loading from {}",
jndiRoot );
configuration = ConfigurationChain
.primaryConfiguration(
new JndiConfiguration( jndiRoot ) )
.fallbackTo(
new InitParameterConfiguration( servletConfig ) );
}
catch ( UnsupportedEncodingException | NamingException e ) {
throw new IllegalStateException( "unable to load JndiConfiguration" );
}
}
String targetUriString = configuration.get( Key.TARGET_URI, String.class );
if ( targetUriString == null ) {
throw new ServletException( Key.TARGET_URI.key() + " is required." );
}
try {
proxyUri = new ProxyUri( targetUriString );
}
catch ( URISyntaxException e ) {
throw new ServletException(
"Trying to process targetUri init parameter: "
+ e.getMessage(),
e );
}
// default true
Boolean setXForwarded = configuration.get(
Key.SET_X_FORWARDED, Boolean.class );
this.setXForwarded = setXForwarded == null
? true : setXForwarded;
// default false
Boolean setXRequestId = configuration.get(
Key.SET_X_REQUEST_ID, Boolean.class );
this.setXRequestId = setXRequestId == null
? false : setXRequestId;
Class> responseHandlerClass = configuration.get(
Key.RESPONSE_HANDLER_CLASS, Class.class );
if ( responseHandlerClass != null ) {
try {
responseHandler = (ReverseProxyResponseHandler)
responseHandlerClass.newInstance();
}
catch ( InstantiationException | IllegalAccessException | ClassCastException e ) {
throw new ServletException(
"Unable to construct responseHandler: "
+ e.getMessage(),
e );
}
}
Class> proxyRequestPreprocessorClass = configuration.get(
Key.PROXY_REQUEST_PREPROCESSOR_CLASS, Class.class );
if ( proxyRequestPreprocessorClass != null ) {
try {
proxyRequestPreprocessor = (ProxyRequestPreprocessor)
proxyRequestPreprocessorClass.newInstance();
}
catch ( InstantiationException | IllegalAccessException | ClassCastException e ) {
throw new ServletException(
"Unable to construct proxyRequestPreprocessor: "
+ e.getMessage(),
e );
}
}
}
private HttpClient newHttpClient( HttpServletRequest request ) {
HttpClientFactory httpClientFactory = (HttpClientFactory)
getServletContext().getAttribute(
HttpClientFactoryServletContextListener.HTTP_CLIENT_FACTORY );
if ( httpClientFactory == null ) {
httpClientFactory = new DefaultHttpClientFactory();
}
return httpClientFactory.create( configuration, null );
}
@Override
protected void service( HttpServletRequest servletRequest,
HttpServletResponse servletResponse )
throws ServletException, IOException {
logger.trace( "Received proxy request for {}", servletRequest );
String method = servletRequest.getMethod();
String proxyRequestUri;
try {
proxyRequestUri = proxyUri.rewriteRequestUri( servletRequest )
.toString();
}
catch ( URISyntaxException e ) {
throw new ServletException( "invalid request or proxy uri", e );
}
HttpRequest proxyRequest;
if ( servletRequest.getHeader( HttpHeaders.CONTENT_LENGTH ) != null ||
servletRequest.getHeader( HttpHeaders.TRANSFER_ENCODING ) != null ) {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
// either of these two headers signal that there is a message body.
HttpEntityEnclosingRequest entityEnclosingProxyRequest =
new BasicHttpEntityEnclosingRequest( method, proxyRequestUri );
entityEnclosingProxyRequest.setEntity(
new InputStreamEntity( servletRequest.getInputStream(),
servletRequest.getContentLength() ) );
proxyRequest = entityEnclosingProxyRequest;
}
else {
proxyRequest = new BasicHttpRequest( method, proxyRequestUri );
}
copyRequestHeaders( servletRequest, proxyRequest );
if ( setXForwarded ) {
setReverseProxyHeaders( servletRequest, proxyRequest );
}
if ( setXRequestId ) {
servletRequest.setAttribute( REQUEST_ATTRIBUTE_X_REQUEST_ID,
setRequestIdHeader( servletRequest, proxyRequest ) );
}
if ( proxyRequestPreprocessor != null ) {
proxyRequestPreprocessor.preProcess( servletRequest, proxyRequest );
}
HttpResponse proxyResponse = null;
try {
// Execute the request
ReverseProxyAudit.request( servletRequest, proxyRequest );
HttpClient client = newHttpClient( servletRequest );
proxyResponse = client.execute( proxyUri.getHost(), proxyRequest );
responseHandler.handle( proxyUri, servletRequest, servletResponse,
client, proxyResponse );
}
catch ( Exception e ) {
if ( proxyRequest instanceof AbstractExecutionAwareRequest ) {
((AbstractExecutionAwareRequest) proxyRequest).abort();
}
if ( e instanceof RuntimeException ) {
throw (RuntimeException) e;
}
else if ( e instanceof ServletException ) {
throw (ServletException) e;
}
else if ( e instanceof IOException ) {
throw (IOException) e;
}
else {
throw new RuntimeException( e );
}
}
finally {
if ( proxyResponse != null ) {
// ensure response is completely processed
EntityUtils.consumeQuietly( proxyResponse.getEntity() );
}
}
}
public void setConfiguration( Configuration configuration ) {
this.configuration = configuration;
}
private String setRequestIdHeader( HttpServletRequest servletRequest,
HttpRequest proxyRequest ) {
// https://devcenter.heroku.com/articles/http-request-id
String name = "X-Request-ID";
String value = servletRequest.getHeader( name );
if ( value == null ) {
value = UUID.randomUUID().toString();
proxyRequest.setHeader( name, value );
}
return value;
}
public void setReverseProxyResponseHandler(
ReverseProxyResponseHandler responseHandler ) {
this.responseHandler = responseHandler;
}
private void setReverseProxyHeaders( HttpServletRequest servletRequest,
HttpRequest proxyRequest ) {
// http://stackoverflow.com/q/19084340/516433
// http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#x-headers
setReverseProxyHeader( servletRequest, proxyRequest, "X-Forwarded-For",
servletRequest.getRemoteAddr() );
String host = servletRequest.getHeader( "Host" );
if ( host == null ) {
int port = servletRequest.getServerPort();
String scheme = servletRequest.getScheme();
if ( "http".equals( scheme ) && port != 80 ) {
host = servletRequest.getServerName() + ":" + port;
}
else if ( "https".equals( scheme ) && port != 443 ) {
host = servletRequest.getServerName() + ":" + port;
}
else {
host = servletRequest.getServerName();
}
}
setReverseProxyHeader( servletRequest, proxyRequest, "X-Forwarded-Host",
host );
setReverseProxyHeader( servletRequest, proxyRequest, "X-Forwarded-Server",
servletRequest.getServerName() );
setReverseProxyHeader( servletRequest, proxyRequest, "X-Forwarded-Proto",
servletRequest.getScheme() );
}
private void setReverseProxyHeader( HttpServletRequest servletRequest,
HttpRequest proxyRequest, String name, String value ) {
String existingHeader = servletRequest.getHeader( name );
if ( existingHeader != null ) {
value = existingHeader + ", " + value;
}
proxyRequest.setHeader( name, value );
}
public static enum Key implements com.pastdev.httpcomponents.configuration.Key {
PROXY_REQUEST_PREPROCESSOR_CLASS("proxyRequestPreprocessor"),
RESPONSE_HANDLER_CLASS("responseHandler"),
// http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#x-headers
SET_X_FORWARDED("setXForwarded"),
SET_X_REQUEST_ID("setXRequestId"),
TARGET_URI("targetUri");
private String key;
private Key( String key ) {
this.key = key;
}
@Override
public String key() {
return key;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy