com.twinsoft.convertigo.engine.servlets.ReverseProxyServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.twinsoft.convertigo.engine.servlets.ReverseProxyServlet.java

Source

/*
 * Copyright (c) 2001-2011 Convertigo SA.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 *
 * $URL$
 * $Author$
 * $Revision$
 * $Date$
 */

package com.twinsoft.convertigo.engine.servlets;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HeaderElement;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
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.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.lang.StringUtils;

import com.twinsoft.convertigo.beans.connectors.HttpConnector;
import com.twinsoft.convertigo.beans.connectors.ProxyHttpConnector;
import com.twinsoft.convertigo.beans.connectors.ProxyHttpConnector.Replacements;
import com.twinsoft.convertigo.beans.core.Connector;
import com.twinsoft.convertigo.beans.core.Project;
import com.twinsoft.convertigo.engine.AbstractBiller;
import com.twinsoft.convertigo.engine.CertificateManager;
import com.twinsoft.convertigo.engine.Context;
import com.twinsoft.convertigo.engine.Engine;
import com.twinsoft.convertigo.engine.EngineException;
import com.twinsoft.convertigo.engine.EnginePropertiesManager;
import com.twinsoft.convertigo.engine.EnginePropertiesManager.PropertyName;
import com.twinsoft.convertigo.engine.EngineStatistics;
import com.twinsoft.convertigo.engine.MySSLSocketFactory;
import com.twinsoft.convertigo.engine.enums.MimeType;
import com.twinsoft.convertigo.engine.util.GenericUtils;

public class ReverseProxyServlet extends HttpServlet {

    private static final long serialVersionUID = -6850744063612272691L;

    private static final Pattern reg_fields = Pattern.compile("(.*/rproxy/([^/]*)/([^/]*)/([^/]*)/)(.*)");
    private static final Pattern reg_base = Pattern.compile("\\$base");
    private static final Pattern reg_connector = Pattern.compile("\\$connector=(.*?)\\$");

    // protected static FileCacheManager proxyCacheManager;

    // static {
    // try {
    // proxyCacheManager = new FileCacheManager();
    // proxyCacheManager.init();
    // }
    // catch(Exception e) {
    // Engine.logEngine.error("Unexpected exception", e);
    // }
    // }

    /**
     * Key for redirect location header.
     */
    private static final String STRING_LOCATION_HEADER = "Location";

    /**
     * Key for content length header.
     */
    private static final String STRING_CONTENT_LENGTH_HEADER_NAME = "Content-Length";

    /**
     * Key for host header
     */
    private static final String STRING_HOST_HEADER_NAME = "Host";

    public String getName() {
        return "ReverseProxyServlet";
    }

    public String getDefaultContentType() {
        return MimeType.Html.value();
    }

    public String getServletInfo() {
        return "Convertigo ReverseProxyServlet";
    }

    public String getDocumentExtension() {
        return null;
    }

    private enum HttpMethodType {
        GET, POST
    };

    /**
     * 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
     */
    public void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws IOException, ServletException {
        // Execute the proxy request
        this.doRequest(HttpMethodType.GET, 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
     */
    public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws IOException, ServletException {
        // Execute the proxy request
        this.doRequest(HttpMethodType.POST, httpServletRequest, httpServletResponse);
    }

    /**
     * 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
     * @throws EngineException
     */
    private void doRequest(HttpMethodType httpMethodType, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse) throws IOException, ServletException {
        try {
            Engine.logEngine.debug("(ReverseProxyServlet) Starting request handling");

            if (Boolean.parseBoolean(EnginePropertiesManager.getProperty(PropertyName.SSL_DEBUG))) {
                System.setProperty("javax.net.debug", "all");
                Engine.logEngine.trace("(ReverseProxyServlet) Enabling SSL debug mode");
            } else {
                System.setProperty("javax.net.debug", "");
                Engine.logEngine.debug("(ReverseProxyServlet) Disabling SSL debug mode");
            }

            String baseUrl;
            String projectName;
            String connectorName;
            String contextName;
            String extraPath;

            {
                String requestURI = httpServletRequest.getRequestURI();
                Engine.logEngine.trace("(ReverseProxyServlet) Requested URI : " + requestURI);
                Matcher m = reg_fields.matcher(requestURI);
                if (m.matches() && m.groupCount() >= 5) {
                    baseUrl = m.group(1);
                    projectName = m.group(2);
                    connectorName = m.group(3);
                    contextName = m.group(4);
                    extraPath = m.group(5);
                } else {
                    throw new MalformedURLException(
                            "The request doesn't contains needed fields : projectName, connectorName and contextName");
                }
            }

            String sessionID = httpServletRequest.getSession().getId();

            Engine.logEngine.debug("(ReverseProxyServlet) baseUrl : " + baseUrl + " ; projectName : " + projectName
                    + " ; connectorName : " + connectorName + " ; contextName : " + contextName + " ; extraPath : "
                    + extraPath + " ; sessionID : " + sessionID);

            Context context = Engine.theApp.contextManager.get(null, contextName, sessionID, null, projectName,
                    connectorName, null);

            Project project = Engine.theApp.databaseObjectsManager.getProjectByName(projectName);
            context.projectName = projectName;
            context.project = project;

            ProxyHttpConnector proxyHttpConnector = (ProxyHttpConnector) project.getConnectorByName(connectorName);
            context.connector = proxyHttpConnector;
            context.connectorName = proxyHttpConnector.getName();

            HostConfiguration hostConfiguration = proxyHttpConnector.hostConfiguration;

            // Proxy configuration
            String proxyServer = Engine.theApp.proxyManager.getProxyServer();
            String proxyUser = Engine.theApp.proxyManager.getProxyUser();
            String proxyPassword = Engine.theApp.proxyManager.getProxyPassword();
            int proxyPort = Engine.theApp.proxyManager.getProxyPort();

            if (!proxyServer.equals("")) {
                hostConfiguration.setProxy(proxyServer, proxyPort);
                Engine.logEngine.debug("(ReverseProxyServlet) Using proxy: " + proxyServer + ":" + proxyPort);
            } else {
                // Remove old proxy configuration
                hostConfiguration.setProxyHost(null);
            }

            String targetHost = proxyHttpConnector.getServer();
            Engine.logEngine.debug("(ReverseProxyServlet) Target host: " + targetHost);
            int targetPort = proxyHttpConnector.getPort();
            Engine.logEngine.debug("(ReverseProxyServlet) Target port: " + targetPort);

            // Configuration SSL
            Engine.logEngine.debug("(ReverseProxyServlet) Https: " + proxyHttpConnector.isHttps());
            CertificateManager certificateManager = proxyHttpConnector.certificateManager;
            boolean trustAllServerCertificates = proxyHttpConnector.isTrustAllServerCertificates();

            if (proxyHttpConnector.isHttps()) {
                Engine.logEngine.debug("(ReverseProxyServlet) Setting up SSL properties");
                certificateManager.collectStoreInformation(context);

                Engine.logEngine.debug(
                        "(ReverseProxyServlet) CertificateManager has changed: " + certificateManager.hasChanged);
                if (certificateManager.hasChanged || (!targetHost.equalsIgnoreCase(hostConfiguration.getHost()))
                        || (hostConfiguration.getPort() != targetPort)) {
                    Engine.logEngine
                            .debug("(ReverseProxyServlet) Using MySSLSocketFactory for creating the SSL socket");
                    Protocol myhttps = new Protocol("https",
                            MySSLSocketFactory.getSSLSocketFactory(certificateManager.keyStore,
                                    certificateManager.keyStorePassword, certificateManager.trustStore,
                                    certificateManager.trustStorePassword, trustAllServerCertificates),
                            targetPort);

                    hostConfiguration.setHost(targetHost, targetPort, myhttps);
                }

                Engine.logEngine.debug("(ReverseProxyServlet) Updated host configuration for SSL purposes");
            } else {
                hostConfiguration.setHost(targetHost, targetPort);
            }

            HttpMethod httpMethodProxyRequest;

            String targetPath = proxyHttpConnector.getBaseDir() + extraPath;

            // Handle the query string
            if (httpServletRequest.getQueryString() != null) {
                targetPath += "?" + httpServletRequest.getQueryString();
            }
            Engine.logEngine.debug("(ReverseProxyServlet) Target path: " + targetPath);

            Engine.logEngine.debug("(ReverseProxyServlet) Requested method: " + httpMethodType);

            if (httpMethodType == HttpMethodType.GET) {
                // Create a GET request
                httpMethodProxyRequest = new GetMethod();
            } else if (httpMethodType == HttpMethodType.POST) {
                // Create a standard POST request
                httpMethodProxyRequest = new PostMethod();
                ((PostMethod) httpMethodProxyRequest)
                        .setRequestEntity(new InputStreamRequestEntity(httpServletRequest.getInputStream()));
            } else {
                throw new IllegalArgumentException("Unknown HTTP method: " + httpMethodType);
            }

            String charset = httpMethodProxyRequest.getParams().getUriCharset();
            URI targetURI;
            try {
                targetURI = new URI(targetPath, true, charset);
            } catch (URIException e) {
                // Bugfix #1484
                String newTargetPath = "";
                for (String part : targetPath.split("&")) {
                    if (!newTargetPath.equals("")) {
                        newTargetPath += "&";
                    }
                    String[] pair = part.split("=");
                    try {
                        newTargetPath += URLDecoder.decode(pair[0], "UTF-8") + "="
                                + (pair.length > 1 ? URLEncoder.encode(URLDecoder.decode(pair[1], "UTF-8"), "UTF-8")
                                        : "");
                    } catch (UnsupportedEncodingException ee) {
                        newTargetPath = targetPath;
                    }
                }

                targetURI = new URI(newTargetPath, true, charset);
            }
            httpMethodProxyRequest.setURI(targetURI);

            // Tells the method to automatically handle authentication.
            httpMethodProxyRequest.setDoAuthentication(true);

            HttpState httpState = getHttpState(proxyHttpConnector, context);

            String basicUser = proxyHttpConnector.getAuthUser();
            String basicPassword = proxyHttpConnector.getAuthPassword();
            String givenBasicUser = proxyHttpConnector.getGivenAuthUser();
            String givenBasicPassword = proxyHttpConnector.getGivenAuthPassword();

            // Basic authentication configuration
            String realm = null;
            if (!basicUser.equals("") || (basicUser.equals("") && (givenBasicUser != null))) {
                String userName = ((givenBasicUser == null) ? basicUser : givenBasicUser);
                String userPassword = ((givenBasicPassword == null) ? basicPassword : givenBasicPassword);
                httpState.setCredentials(new AuthScope(targetHost, targetPort, realm),
                        new UsernamePasswordCredentials(userName, userPassword));
                Engine.logEngine.debug("(ReverseProxyServlet) Credentials: " + userName + ":******");
            }

            // Setting basic authentication for proxy
            if (!proxyServer.equals("") && !proxyUser.equals("")) {
                httpState.setProxyCredentials(new AuthScope(proxyServer, proxyPort),
                        new UsernamePasswordCredentials(proxyUser, proxyPassword));
                Engine.logEngine.debug("(ReverseProxyServlet) Proxy credentials: " + proxyUser + ":******");
            }

            // Forward the request headers
            setProxyRequestHeaders(httpServletRequest, httpMethodProxyRequest, proxyHttpConnector);

            // Use the CEMS HttpClient
            HttpClient httpClient = Engine.theApp.httpClient;
            httpMethodProxyRequest.setFollowRedirects(false);

            // Execute the request
            int intProxyResponseCode = httpClient.executeMethod(hostConfiguration, httpMethodProxyRequest,
                    httpState);

            // 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(STRING_LOCATION_HEADER).getValue();
                if (stringLocation == null) {
                    throw new ServletException("Received status code: " + stringStatusCode + " but no "
                            + STRING_LOCATION_HEADER + " header was found in the response");
                }
                // Modify the redirect to go to this proxy servlet rather that
                // the
                // proxied host
                String redirect = handleRedirect(stringLocation, baseUrl, proxyHttpConnector);
                httpServletResponse.sendRedirect(redirect);
                Engine.logEngine.debug("(ReverseProxyServlet) Send redirect (" + redirect + ")");
                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(STRING_CONTENT_LENGTH_HEADER_NAME, 0);
                httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                Engine.logEngine.debug("(ReverseProxyServlet) NOT MODIFIED (304)");
                return;
            }

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

            // Pass response headers back to the client
            Engine.logEngine.debug("(ReverseProxyServlet) Response headers back to the client:");
            Header[] headerArrayResponse = httpMethodProxyRequest.getResponseHeaders();
            for (Header header : headerArrayResponse) {
                String headerName = header.getName();
                String headerValue = header.getValue();
                if (!headerName.equalsIgnoreCase("Transfer-Encoding")
                        && !headerName.equalsIgnoreCase("Set-Cookie")) {
                    httpServletResponse.setHeader(headerName, headerValue);
                    Engine.logEngine.debug("   " + headerName + "=" + headerValue);
                }
            }

            String contentType = null;
            Header[] contentTypeHeaders = httpMethodProxyRequest.getResponseHeaders("Content-Type");
            for (Header contentTypeHeader : contentTypeHeaders) {
                contentType = contentTypeHeader.getValue();
                break;
            }

            String pageCharset = "UTF-8";
            if (contentType != null) {
                int iCharset = contentType.indexOf("charset=");
                if (iCharset != -1) {
                    pageCharset = contentType.substring(iCharset + "charset=".length()).trim();
                }
                Engine.logEngine.debug("(ReverseProxyServlet) Using charset: " + pageCharset);
            }

            InputStream siteIn = httpMethodProxyRequest.getResponseBodyAsStream();

            // Handle gzipped content
            Header[] contentEncodingHeaders = httpMethodProxyRequest.getResponseHeaders("Content-Encoding");
            boolean bGZip = false, bDeflate = false;
            for (Header contentEncodingHeader : contentEncodingHeaders) {
                HeaderElement[] els = contentEncodingHeader.getElements();
                for (int j = 0; j < els.length; j++) {
                    if ("gzip".equals(els[j].getName())) {
                        Engine.logBeans.debug("(ReverseProxyServlet) Decode GZip stream");
                        siteIn = new GZIPInputStream(siteIn);
                        bGZip = true;
                    } else if ("deflate".equals(els[j].getName())) {
                        Engine.logBeans.debug("(ReverseProxyServlet) Decode Deflate stream");
                        siteIn = new InflaterInputStream(siteIn, new Inflater(true));
                        bDeflate = true;
                    }
                }
            }

            byte[] bytesDataResult;

            ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);

            // String resourceUrl = projectName + targetPath;

            String t = context.statistics.start(EngineStatistics.APPLY_USER_REQUEST);

            try {
                // Read either from the cache, either from the remote server
                // InputStream is = proxyCacheManager.getResource(resourceUrl);
                // if (is != null) {
                // Engine.logEngine.debug("(ReverseProxyServlet) Getting data from cache");
                // siteIn = is;
                // }
                int c = siteIn.read();
                while (c > -1) {
                    baos.write(c);
                    c = siteIn.read();
                }
                // if (is != null) is.close();
            } finally {
                context.statistics.stop(t, true);
            }

            bytesDataResult = baos.toByteArray();
            baos.close();
            Engine.logEngine.debug("(ReverseProxyServlet) Data retrieved!");

            // if (isDynamicContent(httpServletRequest.getPathInfo(),
            // proxyHttpConnector.getDynamicContentFiles())) {
            Engine.logEngine.debug("(ReverseProxyServlet) Dynamic content");

            bytesDataResult = handleStringReplacements(baseUrl, contentType, pageCharset, proxyHttpConnector,
                    bytesDataResult);

            String billingClassName = context.getConnector().getBillingClassName();
            if (billingClassName != null) {
                try {
                    Engine.logContext.debug("Billing class name required: " + billingClassName);
                    AbstractBiller biller = (AbstractBiller) Class.forName(billingClassName).newInstance();
                    Engine.logContext.debug("Executing the biller");
                    biller.insertBilling(context);
                } catch (Throwable e) {
                    Engine.logContext.warn("Unable to execute the biller (the billing is thus ignored): ["
                            + e.getClass().getName() + "] " + e.getMessage());
                }
            }
            // }
            // else {
            // Engine.logEngine.debug("(ReverseProxyServlet) Static content: " +
            // contentType);
            //            
            // // Determine if the resource has already been cached or not
            // CacheEntry cacheEntry =
            // proxyCacheManager.getCacheEntry(resourceUrl);
            // if (cacheEntry instanceof FileCacheEntry) {
            // FileCacheEntry fileCacheEntry = (FileCacheEntry) cacheEntry;
            // File file = new File(fileCacheEntry.fileName);
            // if (!file.exists())
            // proxyCacheManager.removeCacheEntry(cacheEntry);
            // cacheEntry = null;
            // }
            // if (cacheEntry == null) {
            // bytesDataResult = handleStringReplacements(contentType,
            // proxyHttpConnector, bytesDataResult);
            //
            // if (intProxyResponseCode == 200) {
            // Engine.logEngine.debug("(ReverseProxyServlet) Resource stored: "
            // + resourceUrl);
            // cacheEntry = proxyCacheManager.storeResponse(resourceUrl,
            // bytesDataResult);
            // cacheEntry.contentLength = bytesDataResult.length;
            // cacheEntry.contentType = contentType;
            // Engine.logEngine.debug("(ReverseProxyServlet) Cache entry: " +
            // cacheEntry);
            // }
            // }
            // }

            // Send the content to the client
            if (Engine.logEngine.isDebugEnabled() && MimeType.Html.is(contentType)) {
                Engine.logEngine.debug("Data proxied:\n" + new String(bytesDataResult, pageCharset));
            }

            if (bGZip || bDeflate) {
                baos = new ByteArrayOutputStream();
                OutputStream compressedOutputStream = bGZip ? new GZIPOutputStream(baos)
                        : new DeflaterOutputStream(baos,
                                new Deflater(Deflater.DEFAULT_COMPRESSION | Deflater.DEFAULT_STRATEGY, true));
                compressedOutputStream.write(bytesDataResult);
                compressedOutputStream.close();
                bytesDataResult = baos.toByteArray();
                baos.close();
            }

            httpServletResponse.setContentLength(bytesDataResult.length);
            OutputStream outputStreamClientResponse = httpServletResponse.getOutputStream();
            outputStreamClientResponse.write(bytesDataResult);

            Engine.logEngine.debug("(ReverseProxyServlet) End of document retransmission");
        } catch (Exception e) {
            Engine.logEngine.error("Error while trying to proxy page", e);
            throw new ServletException("Error while trying to proxy page", e);
        }
    }

    private byte[] handleStringReplacements(String baseUrl, String contentType, String charset,
            ProxyHttpConnector proxyHttpConnector, byte[] data) throws IOException {

        Engine.logEngine.debug("(ReverseProxyServlet) String replacements for content-type: " + contentType);

        if (contentType == null) {
            Engine.logEngine.warn("(ReverseProxyServlet) Aborting string replacements because of null mimetype!");
        } else {
            Replacements replacements = proxyHttpConnector.getReplacementsForMimeType(contentType);

            if (!replacements.isEmpty()) {
                String sData = new String(data, charset);

                Engine.logEngine.trace("(ReverseProxyServlet) Data before string replacements:\n" + sData);

                Engine.logEngine.debug("(ReverseProxyServlet) Replacements in progress");

                String strSearched, strReplacing;
                for (int i = 0; i < replacements.strReplacing.length; i++) {
                    strSearched = replacements.strSearched[i];
                    Engine.logEngine.debug("(ReverseProxyServlet) Replacing: " + strSearched);

                    strReplacing = replacements.strReplacing[i];

                    Matcher m_connector = reg_connector.matcher(strReplacing);
                    if (m_connector.find() && m_connector.groupCount() >= 1) {
                        String newConnector = m_connector.group(1);
                        Engine.logEngine.trace("(ReverseProxyServlet) find connector : " + newConnector);

                        // Bugfix for #1798 regression about #1718
                        String newBaseUrl = switchConnector(baseUrl, newConnector) + '/';
                        Engine.logEngine.trace("(ReverseProxyServlet) new baseUrl : " + newBaseUrl);
                        strReplacing = m_connector.replaceAll(newBaseUrl);
                    } else {
                        strReplacing = reg_base.matcher(replacements.strReplacing[i]).replaceAll(baseUrl);
                    }

                    Engine.logEngine.debug("(ReverseProxyServlet) By: " + strReplacing);

                    sData = sData.replaceAll(strSearched, strReplacing);
                }

                Engine.logEngine.debug("(ReverseProxyServlet) Replacements done!");

                Engine.logEngine.trace("(ReverseProxyServlet) Data after string replacements:\n" + sData);

                data = sData.getBytes(charset);
            }
        }
        return data;
    }

    /**
     * Retrieves 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
     */
    private void setProxyRequestHeaders(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest,
            ProxyHttpConnector proxyHttpConnector) {
        Collection<String> removableHeaders = proxyHttpConnector.getRemovableHeadersSet();
        // Get an Enumeration of all of the header names sent by the client
        Enumeration<String> enumerationOfHeaderNames = GenericUtils.cast(httpServletRequest.getHeaderNames());
        while (enumerationOfHeaderNames.hasMoreElements()) {
            String stringHeaderName = (String) enumerationOfHeaderNames.nextElement();
            if (stringHeaderName.equalsIgnoreCase(STRING_CONTENT_LENGTH_HEADER_NAME)
                    || stringHeaderName.equalsIgnoreCase("Cookie")
                    || removableHeaders.contains(stringHeaderName.toLowerCase())) {
                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 = GenericUtils
                    .cast(httpServletRequest.getHeaders(stringHeaderName));
            while (enumerationOfHeaderValues.hasMoreElements()) {
                String stringHeaderValue = (String) 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(STRING_HOST_HEADER_NAME)) {
                    stringHeaderValue = getProxyHostAndPort(proxyHttpConnector);
                } else if (stringHeaderName.equalsIgnoreCase("Referer")) {
                    stringHeaderValue = stringHeaderValue.replaceFirst("://[^/]*/[^/]*/",
                            "://" + getProxyHostAndPort(proxyHttpConnector) + proxyHttpConnector.getBaseDir()
                                    + (proxyHttpConnector.getBaseDir().endsWith("/") ? "" : "/"));
                }
                Engine.logEngine.debug(
                        "(ReverseProxyServlet) Forwarding header: " + stringHeaderName + "=" + stringHeaderValue);
                Header header = new Header(stringHeaderName, stringHeaderValue);
                // Set the same header on the proxy request
                httpMethodProxyRequest.setRequestHeader(header);
            }
        }
    }

    private String getProxyHostAndPort(ProxyHttpConnector proxyHttpConnector) {
        int targetPort = proxyHttpConnector.getPort();
        String targetServer = proxyHttpConnector.getServer();
        if ((targetPort == 80) || (targetPort == 443)) {
            return targetServer;
        } else {
            return targetServer + ":" + targetPort;
        }
    }

    // private boolean isDynamicContent(String path, String dynamicContentFiles)
    // {
    // StringTokenizer stringTokenizer = new
    // StringTokenizer(dynamicContentFiles, " ");
    // String filePattern;
    // while (stringTokenizer.hasMoreTokens()) {
    // filePattern = stringTokenizer.nextToken();
    // if (path.endsWith(filePattern)) return true;
    // }
    //
    // return false;
    // }

    private HttpState getHttpState(HttpConnector connector, Context context) {
        if (context.httpState == null) {
            Engine.logEngine
                    .debug("(ReverseProxyServlet) Creating new HttpState for context id " + context.contextID);
            context.httpState = new HttpState();
        } else {
            Engine.logEngine.debug("(ReverseProxyServlet) Using HttpState of context id " + context.contextID);
        }
        return context.httpState;
    }

    private String handleRedirect(String stringLocation, String baseUrl,
            ProxyHttpConnector currentProxyHttpConnector) {
        Engine.logEngine
                .debug("ReverseProxyServlet:handleRedirect() requested redirect location: " + stringLocation);
        Engine.logEngine.debug("ReverseProxyServlet:handleRedirect() reverse proxy base url: " + baseUrl);

        for (Connector connector : currentProxyHttpConnector.getProject().getConnectorsList()) {
            if (connector instanceof ProxyHttpConnector) {
                String redirectLocation = stringLocation;

                Engine.logEngine
                        .debug("ReverseProxyServlet:handleRedirect() analyzing connector " + connector.getName());

                ProxyHttpConnector proxyHttpConnector = (ProxyHttpConnector) connector;
                String baseDir = proxyHttpConnector.getBaseDir();

                // remove the trailing slash for host only connector, see
                // comment of #1798
                if ("/".equals(baseDir)) {
                    baseDir = "";
                }
                Engine.logEngine.debug("ReverseProxyServlet:handleRedirect()   connector baseDir=" + baseDir);

                String proxyDomainAndPort = "http" + (proxyHttpConnector.isHttps() ? "s" : "") + "://"
                        + getProxyHostAndPort(proxyHttpConnector);
                String proxyBaseUrl = proxyDomainAndPort + baseDir;
                Engine.logEngine.debug("ReverseProxyServlet:handleRedirect()   proxyBaseUrl=" + proxyBaseUrl);

                // Bugfix for #1891
                if (redirectLocation.startsWith("/")) {
                    redirectLocation = proxyDomainAndPort + redirectLocation;
                    Engine.logEngine.debug(
                            "ReverseProxyServlet:handleRedirect()   asbolute path redirect => redirectLocation="
                                    + redirectLocation);
                }

                // Bugfix for #1798 regression about #1718
                if (!redirectLocation.matches(proxyBaseUrl + "((/.*)|$)")) {
                    // Bugfix #1718: it probably means the standard port number
                    // (80 or 443) has been included
                    proxyBaseUrl = "http" + (proxyHttpConnector.isHttps() ? "s" : "") + "://"
                            + proxyHttpConnector.getServer() + ":" + proxyHttpConnector.getPort() + baseDir;
                    Engine.logEngine
                            .debug("ReverseProxyServlet:handleRedirect()   update proxyBaseUrl=" + proxyBaseUrl);
                }

                // Bugfix for #1798 regression about #1718
                if (redirectLocation.matches(proxyBaseUrl + "((/.*)|$)")) {
                    String newBaseUrl = switchConnector(baseUrl, connector.getName());
                    Engine.logEngine.debug("ReverseProxyServlet:handleRedirect()   newBaseUrl=" + newBaseUrl);
                    redirectLocation = redirectLocation.replace(proxyBaseUrl, newBaseUrl);
                    Engine.logEngine.debug(
                            "ReverseProxyServlet:handleRedirect() final redirect location: " + redirectLocation);
                    return redirectLocation;
                }
            }
        }

        // Default case: no connector match
        return stringLocation;
    }

    private String switchConnector(String baseUrl, String connectorName) {
        String[] splittedBase = StringUtils.split(baseUrl, '/');
        for (int i = 0; i < splittedBase.length; i++) {
            if (splittedBase[i].equals("rproxy")) {
                splittedBase[i + 2] = connectorName; // /convertigo/rproxy(i)/project(i+1)/connector(i+2)/context/
                break;
            }
        }
        // Bugfix for #1798 regression about #1718
        return '/' + StringUtils.join(splittedBase, '/');
    }
}