org.codeartisans.proxilet.Proxilet.java Source code

Java tutorial

Introduction

Here is the source code for org.codeartisans.proxilet.Proxilet.java

Source

/*
 * Copyright (c) 2007, Jason Edwards. All Rights Reserved.
 * Copyright (c) 2010, Laurent Morel. All Rights Reserved.
 * Copyright (c) 2010, Fabien Barbero. All Rights Reserved.
 * Copyright (c) 2010, David Emo. All Rights Reserved.
 * Copyright (c) 2010, Paul Merlin. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.codeartisans.proxilet;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
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.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Servlet that act as a reverse proxy.
 *
 * Based on ProxiServlet from Jason Edwards, available under the Apache Licence V2
 * http://edwardstx.net/wiki/Wiki.jsp?page=HttpProxyServlet
 * <p/>
 * Patched to skip "Transfer-Encoding: chunked" headers, and avoid double slashes in proxied URL's.
 * http://code.google.com/p/google-web-toolkit/issues/detail?id=3131#c40
 * <p/>
 * See also http://raibledesigns.com/rd/entry/how_to_do_cross_domain
 * and a comment about charset encoding for getWriter() below...
 */
public class Proxilet extends HttpServlet {

    private static final Logger LOGGER = LoggerFactory.getLogger(Proxilet.class);

    static {
        // TODO Put a boolean init parameter for this
        System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
        // So that any server connection is accepted : (port number is only the default one)
        Protocol easyhttps = new Protocol("https", (ProtocolSocketFactory) new EasySSLProtocolSocketFactory(), 443);
        Protocol.registerProtocol("https", easyhttps);
    }

    private static final Protocol SSL_PROTOCOL = new Protocol("https",
            (ProtocolSocketFactory) new EasySSLProtocolSocketFactory(), 443);

    private static final int FOUR_KB = 4196;

    private static final long serialVersionUID = 1L;

    private static final String HEADER_LOCATION = "Location";

    private static final String HEADER_CONTENT_TYPE = "Content-Type";

    private static final String HEADER_CONTENT_LENGTH = "Content-Length";

    private static final String HEADER_HOST = "Host";

    private static final File FILE_UPLOAD_TEMP_DIRECTORY = new File(System.getProperty("java.io.tmpdir"));
    // Target host params

    private String targetHost;

    private int targetPort = 80;

    private boolean targetSsl;

    private String targetCredentials; // format is user:password

    /**
     * The (optional) path on the proxy host to which we are proxying requests. Default value is "".
     */
    private String proxyPath = "";

    private String stringPrefixPath = "";

    private String stringMimeType = "text/x-gwt-rpc";

    private String stringSourcePath = "";

    private String stringDestinationPath = "";

    private String[] stringForwardTypes;

    /**
     * Setting that allows removing the initial path from client. Allows specifying /twitter/* as synonym for
     * twitter.com.
     */
    private boolean removePrefix = false;

    private int maxFileUploadSize = 5 * 1024 * 1024; // Defaults to 5MB

    private boolean followRedirects;

    @Override
    public void init(ServletConfig servletConfig) {
        // Get the proxy host
        String stringProxyHostNew = servletConfig.getInitParameter("targetHost");
        if (stringProxyHostNew == null || stringProxyHostNew.length() == 0) {
            throw new IllegalArgumentException("Proxy host not set, please set init-param 'targetHost' in web.xml");
        }
        targetHost = stringProxyHostNew;
        // Get the proxy port if specified
        String stringProxyPortNew = servletConfig.getInitParameter("targetPort");
        if (stringProxyPortNew != null && stringProxyPortNew.length() > 0) {
            targetPort = Integer.parseInt(stringProxyPortNew);
        }

        // Get the credentials, if specified
        String strProxyCredentials = servletConfig.getInitParameter("targetCredentials");
        if (strProxyCredentials != null && strProxyCredentials.length() > 0) {
            targetCredentials = strProxyCredentials;
        }
        // Get the secure flag if specified
        String secure = servletConfig.getInitParameter("targetSsl");
        if (secure != null && secure.length() > 0) {
            targetSsl = "true".equalsIgnoreCase(secure);
        }
        // Get the proxy path if specified
        String stringProxyPathNew = servletConfig.getInitParameter("proxyPath");
        if (stringProxyPathNew != null && stringProxyPathNew.length() > 0) {
            proxyPath = stringProxyPathNew;
        }

        String strPrefixPath = servletConfig.getInitParameter("prefixPath");
        if (strPrefixPath != null && strPrefixPath.length() > 0) {
            stringPrefixPath = strPrefixPath;
        }

        String strMimeType = servletConfig.getInitParameter("mimeType");
        if (strMimeType != null && strMimeType.length() > 0) {
            stringMimeType = strMimeType;
        }

        String strForwardTypes = servletConfig.getInitParameter("forwardTypes");
        if (strForwardTypes != null && strForwardTypes.length() > 0) {
            stringForwardTypes = strForwardTypes.split(",");
        }

        String strRemovePrexif = servletConfig.getInitParameter("removePrefix");
        if (strRemovePrexif != null && strRemovePrexif.length() > 0) {
            removePrefix = Boolean.valueOf(strRemovePrexif);
        }

        // Get the maximum file upload size if specified
        String stringMaxFileUploadSize = servletConfig.getInitParameter("maxFileUploadSize");
        if (stringMaxFileUploadSize != null && stringMaxFileUploadSize.length() > 0) {
            maxFileUploadSize = Integer.parseInt(stringMaxFileUploadSize);
        }

        String strSourcePath = servletConfig.getInitParameter("sourcePath");
        if (strSourcePath != null && strSourcePath.length() > 0) {
            stringSourcePath = strSourcePath;
        }

        String strDestinationPath = servletConfig.getInitParameter("destinationPath");
        if (strDestinationPath != null && strDestinationPath.length() > 0) {
            stringDestinationPath = strDestinationPath;
        }

    }

    /**
     * Performs an HTTP GET request.
     *
     * @param httpServletRequest    The {@link HttpServletRequest} object passed in by the servlet engine representing the client request to be proxied
     * @param httpServletResponse   The {@link HttpServletResponse} object by which we can send a proxied response to the client
     */
    @Override
    public void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws IOException, ServletException {
        // Create a GET request
        String destinationUrl = this.getProxyURL(httpServletRequest);
        LOGGER.trace("GET {} => {}", httpServletRequest.getRequestURL(), destinationUrl);
        GetMethod getMethodProxyRequest = new GetMethod(destinationUrl);
        // Forward the request headers
        setProxyRequestHeaders(httpServletRequest, getMethodProxyRequest);
        // Execute the proxy request
        this.executeProxyRequest(getMethodProxyRequest, httpServletRequest, httpServletResponse);
    }

    /**
     * Performs an HTTP POST request.
     *
     * @param httpServletRequest    The {@link HttpServletRequest} object passed in by the servlet engine representing the client request to be proxied
     * @param httpServletResponse   The {@link HttpServletResponse} object by which we can send a proxied response to the client
     */
    @Override
    public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws IOException, ServletException {
        // Create a standard POST request
        String contentType = httpServletRequest.getContentType();
        String destinationUrl = this.getProxyURL(httpServletRequest);
        LOGGER.trace("POST {} => {} ({})" + httpServletRequest.getRequestURL(), destinationUrl, contentType);
        PostMethod postMethodProxyRequest = new PostMethod(destinationUrl);
        // Forward the request headers
        setProxyRequestHeaders(httpServletRequest, postMethodProxyRequest);
        // Check if this is a mulitpart (file upload) POST
        if (ServletFileUpload.isMultipartContent(httpServletRequest)) {
            this.handleMultipartPost(postMethodProxyRequest, httpServletRequest);
        } else {
            if (contentType == null || PostMethod.FORM_URL_ENCODED_CONTENT_TYPE.equals(contentType)) {
                this.handleStandardPost(postMethodProxyRequest, httpServletRequest);
            } else {
                this.handleContentPost(postMethodProxyRequest, httpServletRequest);
            }
        }
        // Execute the proxy request
        this.executeProxyRequest(postMethodProxyRequest, httpServletRequest, httpServletResponse);
    }

    /**
     * Sets up the given {@link PostMethod} to send the same multipart POST data as was sent in the given
     * {@link HttpServletRequest}.
     *
     * @param postMethodProxyRequest    The {@link PostMethod} that we are configuring to send a multipart POST request
     * @param httpServletRequest    The {@link HttpServletRequest} that contains the mutlipart POST data to be sent via the {@link PostMethod}
     */
    @SuppressWarnings("unchecked")
    private void handleMultipartPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest)
            throws ServletException {
        // Create a factory for disk-based file items
        DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
        // Set factory constraints
        diskFileItemFactory.setSizeThreshold(maxFileUploadSize);
        diskFileItemFactory.setRepository(FILE_UPLOAD_TEMP_DIRECTORY);
        // Create a new file upload handler
        ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
        // Parse the request
        try {
            // Get the multipart items as a list
            List<FileItem> listFileItems = (List<FileItem>) servletFileUpload.parseRequest(httpServletRequest);
            // Create a list to hold all of the parts
            List<Part> listParts = new ArrayList<Part>();
            // Iterate the multipart items list
            for (FileItem fileItemCurrent : listFileItems) {
                // If the current item is a form field, then create a string part
                if (fileItemCurrent.isFormField()) {
                    StringPart stringPart = new StringPart(fileItemCurrent.getFieldName(), // The field name
                            fileItemCurrent.getString() // The field value
                    );
                    // Add the part to the list
                    listParts.add(stringPart);
                } else {
                    // The item is a file upload, so we create a FilePart
                    FilePart filePart = new FilePart(fileItemCurrent.getFieldName(), // The field name
                            new ByteArrayPartSource(fileItemCurrent.getName(), // The uploaded file name
                                    fileItemCurrent.get() // The uploaded file contents
                            ));
                    // Add the part to the list
                    listParts.add(filePart);
                }
            }
            MultipartRequestEntity multipartRequestEntity = new MultipartRequestEntity(
                    listParts.toArray(new Part[] {}), postMethodProxyRequest.getParams());
            postMethodProxyRequest.setRequestEntity(multipartRequestEntity);
            // The current content-type header (received from the client) IS of
            // type "multipart/form-data", but the content-type header also
            // contains the chunk boundary string of the chunks. Currently, this
            // header is using the boundary of the client request, since we
            // blindly copied all headers from the client request to the proxy
            // request. However, we are creating a new request with a new chunk
            // boundary string, so it is necessary that we re-set the
            // content-type string to reflect the new chunk boundary string
            postMethodProxyRequest.setRequestHeader(HEADER_CONTENT_TYPE, multipartRequestEntity.getContentType());
        } catch (FileUploadException fileUploadException) {
            throw new ServletException(fileUploadException);
        }
    }

    /**
     * Sets up the given {@link PostMethod} to send the same standard POST data as was sent in the given
     * {@link HttpServletRequest}.
     *
     * @param postMethodProxyRequest    The {@link PostMethod} that we are configuring to send a standard POST request
     * @param httpServletRequest        The {@link HttpServletRequest} that contains the POST data to be sent via the {@link PostMethod}
     */
    @SuppressWarnings("unchecked")
    private void handleStandardPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest) {
        // Get the client POST data as a Map
        Map<String, String[]> mapPostParameters = (Map<String, String[]>) httpServletRequest.getParameterMap();
        // Create a List to hold the NameValuePairs to be passed to the PostMethod
        List<NameValuePair> listNameValuePairs = new ArrayList<NameValuePair>();
        // Iterate the parameter names
        for (String stringParameterName : mapPostParameters.keySet()) {
            // Iterate the values for each parameter name
            String[] stringArrayParameterValues = mapPostParameters.get(stringParameterName);
            for (String stringParamterValue : stringArrayParameterValues) {
                // Create a NameValuePair and store in list
                NameValuePair nameValuePair = new NameValuePair(stringParameterName, stringParamterValue);
                listNameValuePairs.add(nameValuePair);
            }
        }
        // Set the proxy request POST data
        postMethodProxyRequest.setRequestBody(listNameValuePairs.toArray(new NameValuePair[] {}));
    }

    /**
     * Sets up the given {@link PostMethod} to send the same content POST data (JSON, XML, etc.) as was sent in the
     * given {@link HttpServletRequest}.
     *
     * @param postMethodProxyRequest    The {@link PostMethod} that we are configuring to send a standard POST request
     * @param httpServletRequest        The {@link HttpServletRequest} that contains the POST data to be sent via the {@link PostMethod}
     */
    private void handleContentPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest)
            throws IOException, ServletException {
        StringBuilder content = new StringBuilder();
        BufferedReader reader = httpServletRequest.getReader();
        for (;;) {
            String line = reader.readLine();
            if (line == null) {
                break;
            }
            content.append(line);
        }

        String contentType = httpServletRequest.getContentType();
        String postContent = content.toString();

        // Hack to trickle main server gwt rpc servlet
        // this avoids warnings like the following :
        // "ERROR: The module path requested, /testmodule/, is not in the same web application as this servlet"
        // or
        // "WARNING: Failed to get the SerializationPolicy '29F4EA1240F157649C12466F01F46F60' for module 'http://localhost:8888/testmodule/'"
        //
        // Actually it avoids a NullPointerException in server logging :
        // See http://code.google.com/p/google-web-toolkit/issues/detail?id=3624
        if (contentType.startsWith(this.stringMimeType)) {
            String clientHost = httpServletRequest.getLocalName();
            if (clientHost.equals("127.0.0.1") || clientHost.equals("0:0:0:0:0:0:0:1")) {
                clientHost = "localhost";
            }

            int clientPort = httpServletRequest.getLocalPort();
            String clientUrl = clientHost + ((clientPort != 80) ? ":" + clientPort : "");
            String serverUrl = targetHost + ((targetPort != 80) ? ":" + targetPort : "") + stringPrefixPath;

            // Replace more completely if destination server is https :
            if (targetSsl) {
                clientUrl = "http://" + clientUrl;
                serverUrl = "https://" + serverUrl;
            }
            postContent = postContent.replace(clientUrl, serverUrl);
        }

        String encoding = httpServletRequest.getCharacterEncoding();
        LOGGER.trace("POST Content Type: {} Encoding: {} Content: {}",
                new Object[] { contentType, encoding, postContent });
        StringRequestEntity entity;
        try {
            entity = new StringRequestEntity(postContent, contentType, encoding);
        } catch (UnsupportedEncodingException e) {
            throw new ServletException(e);
        }
        // Set the proxy request POST data
        postMethodProxyRequest.setRequestEntity(entity);
    }

    private HttpClient createClientWithLogin() {
        // Create a default HttpClient
        HttpClient httpClient = new HttpClient();
        Protocol.registerProtocol("https", SSL_PROTOCOL);

        // if login/password authentication is required :
        if (targetCredentials != null) {
            httpClient.getParams().setAuthenticationPreemptive(true);
            String[] creds = targetCredentials.split(":");
            Credentials defaultcreds = new UsernamePasswordCredentials(creds[0], creds[1]);
            httpClient.getState().setCredentials(new AuthScope(targetHost, targetPort, AuthScope.ANY_REALM),
                    defaultcreds);
        }
        return httpClient;
    }

    /**
     * Executes the {@link HttpMethod} passed in and sends the proxy response back to the client via the given
     * {@link HttpServletResponse}.
     *
     * @param httpMethodProxyRequest    An object representing the proxy request to be made
     * @param httpServletResponse       An object by which we can send the proxied response back to the client
     * @throws IOException              Can be thrown by the {@link HttpClient}.executeMethod
     * @throws ServletException         Can be thrown to indicate that another error has occurred
     */
    private void executeProxyRequest(HttpMethod httpMethodProxyRequest, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse) throws IOException, ServletException {
        // Create a default HttpClient
        HttpClient httpClient;
        httpClient = createClientWithLogin();
        httpMethodProxyRequest.setFollowRedirects(false);

        // Execute the request
        int intProxyResponseCode = httpClient.executeMethod(httpMethodProxyRequest);
        //        String response = httpMethodProxyRequest.getResponseBodyAsString();

        // Check if the proxy response is a redirect
        // The following code is adapted from org.tigris.noodle.filters.CheckForRedirect
        // Hooray for open source software
        if (intProxyResponseCode >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */
                && intProxyResponseCode < HttpServletResponse.SC_NOT_MODIFIED /* 304 */ ) {
            String stringStatusCode = Integer.toString(intProxyResponseCode);
            String stringLocation = httpMethodProxyRequest.getResponseHeader(HEADER_LOCATION).getValue();
            if (stringLocation == null) {
                throw new ServletException("Received status code: " + stringStatusCode + " but no "
                        + HEADER_LOCATION + " header was found in the response");
            }
            // Modify the redirect to go to this proxy servlet rather that the proxied host
            String stringMyHostName = httpServletRequest.getServerName();
            if (httpServletRequest.getServerPort() != 80) {
                stringMyHostName += ":" + httpServletRequest.getServerPort();
            }
            stringMyHostName += httpServletRequest.getContextPath();
            if (followRedirects) {
                httpServletResponse
                        .sendRedirect(stringLocation.replace(getProxyHostAndPort() + proxyPath, stringMyHostName));
                return;
            }
        } else if (intProxyResponseCode == HttpServletResponse.SC_NOT_MODIFIED) {
            // 304 needs special handling. See:
            // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304
            // We get a 304 whenever passed an 'If-Modified-Since'
            // header and the data on disk has not changed; server
            // responds w/ a 304 saying I'm not going to send the
            // body because the file has not changed.
            httpServletResponse.setIntHeader(HEADER_CONTENT_LENGTH, 0);
            httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        // Pass the response code back to the client
        httpServletResponse.setStatus(intProxyResponseCode);

        // Pass response headers back to the client
        Header[] headerArrayResponse = httpMethodProxyRequest.getResponseHeaders();
        for (Header header : headerArrayResponse) {
            if (header.getName().equals("Transfer-Encoding") && header.getValue().equals("chunked")
                    || header.getName().equals("Content-Encoding") && header.getValue().equals("gzip")) {
                // proxy servlet does not support chunked encoding
            } else {
                httpServletResponse.setHeader(header.getName(), header.getValue());
            }
        }

        List<Header> responseHeaders = Arrays.asList(headerArrayResponse);

        // FIXME We should handle both String and bytes response in the same way:
        String response = null;
        byte[] bodyBytes = null;

        if (isBodyParameterGzipped(responseHeaders)) {
            LOGGER.trace("GZipped: true");
            if (!followRedirects && intProxyResponseCode == HttpServletResponse.SC_MOVED_TEMPORARILY) {
                response = httpMethodProxyRequest.getResponseHeader(HEADER_LOCATION).getValue();
                httpServletResponse.setStatus(HttpServletResponse.SC_OK);
                intProxyResponseCode = HttpServletResponse.SC_OK;
                httpServletResponse.setHeader(HEADER_LOCATION, response);
                httpServletResponse.setContentLength(response.length());
            } else {
                bodyBytes = ungzip(httpMethodProxyRequest.getResponseBody());
                httpServletResponse.setContentLength(bodyBytes.length);
            }
        }

        if (httpServletResponse.getContentType() != null && httpServletResponse.getContentType().contains("text")) {
            LOGGER.trace("Received status code: {} Response: {}", intProxyResponseCode, response);
        } else {
            LOGGER.trace("Received status code: {} [Response is not textual]", intProxyResponseCode);
        }

        // Send the content to the client
        if (response != null) {
            httpServletResponse.getWriter().write(response);
        } else if (bodyBytes != null) {
            httpServletResponse.getOutputStream().write(bodyBytes);
        } else {
            IOUtils.copy(httpMethodProxyRequest.getResponseBodyAsStream(), httpServletResponse.getOutputStream());
        }
    }

    /**
     * The response body will be assumed to be gzipped if the GZIP header has been set.
     *
     * @param responseHeaders   of response headers
     * @return                  true if the body is gzipped
     */
    private boolean isBodyParameterGzipped(List<Header> responseHeaders) {
        for (Header header : responseHeaders) {
            if (header.getValue().equals("gzip")) {
                return true;
            }
        }
        return false;
    }

    /**
     * A highly performant ungzip implementation.
     *
     * Do not refactor this without taking new timings. See ElementTest in ehcache for timings
     *
     * @param gzipped       the gzipped content
     * @return              an ungzipped byte[]
     * @throws IOException  when something bad happens
     */
    private byte[] ungzip(final byte[] gzipped) throws IOException {
        final GZIPInputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(gzipped));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(gzipped.length);
        final byte[] buffer = new byte[FOUR_KB];
        int bytesRead = 0;
        while (bytesRead != -1) {
            bytesRead = inputStream.read(buffer, 0, FOUR_KB);
            if (bytesRead != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
            }
        }
        byte[] ungzipped = byteArrayOutputStream.toByteArray();
        inputStream.close();
        byteArrayOutputStream.close();
        return ungzipped;
    }

    /**
     * Retreives all of the headers from the servlet request and sets them on the proxy request.
     *
     * @param httpServletRequest        The request object representing the client's request to the servlet engine
     * @param httpMethodProxyRequest    The request that we are about to send to the proxy host
     */
    @SuppressWarnings("unchecked")
    private void setProxyRequestHeaders(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
        // Get an Enumeration of all of the header names sent by the client
        Enumeration<String> enumerationOfHeaderNames = httpServletRequest.getHeaderNames();
        while (enumerationOfHeaderNames.hasMoreElements()) {
            String stringHeaderName = enumerationOfHeaderNames.nextElement();
            if (stringHeaderName.equalsIgnoreCase(HEADER_CONTENT_LENGTH)) {
                continue;
            }
            // As per the Java Servlet API 2.5 documentation:
            // Some headers, such as Accept-Language can be sent by clients
            // as several headers each with a different value rather than
            // sending the header as a comma separated list.
            // Thus, we get an Enumeration of the header values sent by the client
            Enumeration<String> enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName);
            while (enumerationOfHeaderValues.hasMoreElements()) {
                String stringHeaderValue = enumerationOfHeaderValues.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 (stringHeaderName.equalsIgnoreCase(HEADER_HOST)) {
                    stringHeaderValue = getProxyHostAndPort();
                }
                Header header = new Header(stringHeaderName, stringHeaderValue);
                // Set the same header on the proxy request
                httpMethodProxyRequest.setRequestHeader(header);
            }
        }
    }

    // Accessors
    private String getProxyURL(HttpServletRequest httpServletRequest) {
        // Set the protocol to HTTP
        String protocol = (targetSsl) ? "https://" : "http://";
        String stringProxyURL = protocol + this.getProxyHostAndPort();
        //System.out.println("proxyURL 1="+stringProxyURL);

        String uri = httpServletRequest.getRequestURI();

        if (!removePrefix) {
            if (uri.contains(this.stringSourcePath)) {
                stringProxyURL += stringPrefixPath + uri.replace(this.stringSourcePath, this.stringDestinationPath);
            } else {
                stringProxyURL += stringPrefixPath + uri;
            }
        }
        //System.out.println("proxyURL 2="+stringProxyURL);

        // LOLO : a double le dernier lment du path ?!
        //        stringProxyURL += "/";
        //
        //        // Handle the path given to the servlet
        //        String pathInfo = httpServletRequest.getPathInfo();
        //System.out.println("pathInfo="+pathInfo);
        //        if (pathInfo != null && pathInfo.startsWith("/")) {
        //            if (stringProxyURL != null && stringProxyURL.endsWith("/")) {
        //                // avoid double '/'
        //                stringProxyURL += pathInfo.substring(1);
        //System.out.println("proxyURL 3="+stringProxyURL);
        //            }
        //        } else {
        //            if (pathInfo != null) {
        //                stringProxyURL += httpServletRequest.getPathInfo();
        //System.out.println("proxyURL 4="+stringProxyURL);
        //            } else {
        //                stringProxyURL = stringProxyURL.substring(0, stringProxyURL.length() - 1);
        //System.out.println("proxyURL 5="+stringProxyURL);
        //            }
        //        }
        // Handle the query string
        if (httpServletRequest.getQueryString() != null) {
            stringProxyURL += "?" + httpServletRequest.getQueryString();
            //System.out.println("proxyURL 6="+stringProxyURL);
        }
        return stringProxyURL;
    }

    private String getProxyHostAndPort() {
        if (targetPort == 80) {
            return targetHost;
        } else {
            return targetHost + ":" + targetPort;
        }
    }

}