/******************************************************************************* * Copyright (c) 2011 Subgraph. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Subgraph - initial API and implementation ******************************************************************************/ package com.subgraph.vega.internal.http.proxy; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; import org.apache.http.HttpMessage; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.ProtocolException; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.message.BasicHttpResponse; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpRequestHandler; import org.apache.http.util.EntityUtils; import com.subgraph.vega.api.http.requests.IHttpRequestEngine; import com.subgraph.vega.api.http.requests.IHttpResponse; import com.subgraph.vega.api.http.requests.RequestEngineException; import com.subgraph.vega.http.requests.custom.HttpEntityEnclosingMutableRequest; import com.subgraph.vega.http.requests.custom.HttpMutableRequest; public class ProxyRequestHandler implements HttpRequestHandler { /** * Hop-by-hop headers to be removed by this proxy. */ private final static String[] HOP_BY_HOP_HEADERS = { // Hop-by-hop headers specified in RFC2616 section 13.5.1. HTTP.CONN_DIRECTIVE, // "Connection" HTTP.CONN_KEEP_ALIVE, // "Keep-Alive" "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailers", HTTP.TRANSFER_ENCODING, // "Transfer-Encoding" "Upgrade", // Not part of the RFC but should not be forwarded; see http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html "Proxy-Connection", }; private final Logger logger; private final HttpProxy httpProxy; private final IHttpRequestEngine requestEngine; ProxyRequestHandler(HttpProxy httpProxy, Logger logger, IHttpRequestEngine requestEngine) { this.httpProxy = httpProxy; this.logger = logger; this.requestEngine = requestEngine; } @Override public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { final ProxyTransaction transaction = new ProxyTransaction(requestEngine, context); context.setAttribute(HttpProxy.PROXY_HTTP_TRANSACTION, transaction); try { if (handleRequest(transaction, request) == false) { response.setStatusCode(503); transaction.signalComplete(false); return; } HttpUriRequest uriRequest = transaction.getRequest(); BasicHttpContext ctx = new BasicHttpContext(); transaction.signalForward(); IHttpResponse r = requestEngine.sendRequest(uriRequest, ctx); if(r == null) { response.setStatusCode(503); transaction.signalComplete(false); return; } if (handleResponse(transaction, r) == false) { response.setStatusCode(503); transaction.signalComplete(true); return; } HttpResponse httpResponse = copyResponse(r.getRawResponse()); removeHeaders(httpResponse); response.setStatusLine(httpResponse.getStatusLine()); response.setHeaders(httpResponse.getAllHeaders()); response.setEntity(httpResponse.getEntity()); transaction.signalForward(); } catch (InterruptedException e) { logger.log(Level.WARNING, "Error processing request: " + e.getMessage(), e); response.setStatusCode(503); } catch (RequestEngineException e) { logger.log(Level.WARNING, "Error processing request: " + e.getMessage()); response.setStatusCode(502); } catch (ProtocolException e) { logger.log(Level.WARNING, "Error processing request: " + e.getMessage(), e); response.setStatusCode(400); } catch (Exception e) { logger.log(Level.WARNING, "Error processing request: " + e.getMessage(), e); response.setStatusCode(500); } finally { transaction.signalComplete(false); } } private HttpEntity copyEntity(HttpEntity entity) { try { if(entity == null) { return null; } final ByteArrayEntity newEntity = new ByteArrayEntity(EntityUtils.toByteArray(entity)); newEntity.setContentEncoding(entity.getContentEncoding()); newEntity.setContentType(entity.getContentType()); return newEntity; } catch (IOException e) { return null; } } private HttpUriRequest copyToUriRequest(HttpRequest request) throws ProtocolException { URI uri; try { uri = new URI(request.getRequestLine().getUri()); } catch (URISyntaxException e) { throw new ProtocolException("Invalid URI: " + request.getRequestLine().getUri(), e); } // ensuring we have scheme and host also prevents the proxy from connecting back to itself if (uri.getScheme() == null) { throw new ProtocolException("No scheme in proxy request URI"); } if (uri.getHost() == null) { throw new ProtocolException("No host in proxy request URI"); } final HttpUriRequest uriRequest; if (request instanceof HttpEntityEnclosingRequest) { HttpEntityEnclosingMutableRequest tmp = new HttpEntityEnclosingMutableRequest(request.getRequestLine().getMethod(), uri); tmp.setEntity(copyEntity(((HttpEntityEnclosingRequest) request).getEntity())); uriRequest = tmp; } else { uriRequest = new HttpMutableRequest(request.getRequestLine().getMethod(), uri); } uriRequest.setParams(request.getParams()); uriRequest.setHeaders(request.getAllHeaders()); return uriRequest; } private HttpResponse copyResponse(HttpResponse originalResponse) { HttpResponse r = new BasicHttpResponse(originalResponse.getStatusLine()); r.setHeaders(originalResponse.getAllHeaders()); r.setEntity(originalResponse.getEntity()); return r; } private void removeHeaders(HttpMessage message) { for(String hdr: HOP_BY_HOP_HEADERS) { message.removeHeaders(hdr); } } private boolean handleRequest(ProxyTransaction transaction, HttpRequest request) throws InterruptedException, ProtocolException { removeHeaders(request); transaction.setRequest(copyToUriRequest(request)); if (httpProxy.handleTransaction(transaction) == true) { return transaction.getForward(); } else { return true; } } private boolean handleResponse(ProxyTransaction transaction, IHttpResponse response) throws InterruptedException { transaction.setResponse(response); if (httpProxy.handleTransaction(transaction) == true) { return transaction.getForward(); } else { return true; } } }