/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.container;

import com.ericsson.ssa.utils.ChunkedInputStream;
import com.ericsson.ssa.utils.ContentLengthInputStream;

import org.apache.catalina.Request;
import org.apache.catalina.Response;

import org.apache.coyote.tomcat5.CoyoteRequest;
import org.apache.coyote.tomcat5.CoyoteResponse;

import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.MimeHeaders;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.lang.reflect.Field;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.logging.Level;

// inserted by hockey (automatic)
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletRequest;


/**
 * This class implements proxy functionality for proxying Tomcat HTTP requests
 * to back-end servers. The proxy uses connection pooling of connections to the
 * back-end servers for better performance.
 *
 * @author epiesan
 * @since Apr 16, 2006
 * @reviewed ejoelbi 2006-oct-23
 * @reviewed qmaghes 2007-mars-14
 *
 */
public class HttpProxy {
    private static final Logger log = (Logger) Logger.getLogger("SipContainer");
    private final static String PROXY_REQUEST_BODY_NOTE = "ProxyRequestBody";
    private final static String EAS_HEADER_FRONTEND_IS_SECURE = "eas_schema";
    private final static String EAS_HEADER_FRONTEND_LOCAL_ADDRESS = "eas_localaddress";
    private final static String EAS_HEADER_FRONTEND_LOCAL_PORT = "eas_localport";
    private static HashMap<HttpHost, HttpConnectionPool> pools = new HashMap<HttpHost, HttpConnectionPool>();
    private static HttpProxySettings settings;
    public static final ArrayList<String> droppedRequestHeaders = new ArrayList<String>();
    public static final ArrayList<String> droppedResponseHeaders = new ArrayList<String>();

    /**
     * The parser regex pattern to split status line into parts.
     */
    public static final Pattern statusLinePattern;

    static {
        // Initializing dropped request headers
        droppedRequestHeaders.add("connection");
        droppedRequestHeaders.add("content-length");

        // Initializing dropped response headers
        droppedResponseHeaders.add("connection");
        droppedResponseHeaders.add("transfer-encoding");
        droppedResponseHeaders.add("content-length");
        droppedResponseHeaders.add("content-type");
        droppedResponseHeaders.add("server");
        droppedResponseHeaders.add("date");

        // Initializing parser patterns.
        statusLinePattern = Pattern.compile("HTTP/\\d+\\.\\d+ (\\d{3}) (.*)");
    }

    private HttpProxy() {
        // Prevent instantiation.
    }

    public static void setProxySettings(HttpProxySettings settings) {
        HttpProxy.settings = settings;
    }

    /**
     * Proxy the request to a host using the default number of retries.
     *
     * @param request
     *        The request.
     * @param response
     *        The response.
     * @param host
     *        The host to proxy the request to.
     * @throws IOException
     *         Thrown if the response could not be sent.
     */
    public static void proxy(Request request, Response response, HttpHost host)
        throws IOException {
        proxy(request, response, host, settings.getRetries());
    }

    /**
     * Proxy the request to a host using the specified number of retries.
     *
     * @param request
     *        The request.
     * @param response
     *        The response.
     * @param host
     *        The host to proxy the request to.
     * @param retries
     *        The number of proxy attempts.
     * @throws IOException
     *         Thrown if the response could not be sent.
     */
    public static void proxy(Request request, Response response, HttpHost host,
        int retries) throws IOException {
        // Obtain a connection from the connection pool for this host.
        HttpConnection connection = getConnection(host);

        // GLASSFISH Request/Response does not extend HttpServletRequest/Respose
        // Therefore we need to cast to CoyoteRequest/Response
        CoyoteRequest cRequest = (CoyoteRequest) request;

        if (connection == null) {
            // No connection could be obtained, so respond with 503 Service
            // Unavailable
            if (log.isLoggable(Level.FINE)) {
                log.log(Level.FINE,
                    "No connection could be obtained within the " +
                    "configured time limit when proxying the request '" +
                    cRequest.getRequestURL() + "' to '" + host + "'.");
            }

            ((CoyoteResponse) response).sendError(CoyoteResponse.SC_SERVICE_UNAVAILABLE);

            return;
        }

        if (log.isLoggable(Level.FINE)) {
            log.log(Level.FINE, "The proxy obtained a connection: " +
                connection);
        }

        try {
            // Proxy the request and retry if necessary.
            if (log.isLoggable(Level.FINE)) {
                log.log(Level.FINE,
                    "Proxying the request '" + cRequest.getMethod() + " " +
                    cRequest.getRequestURI() + "' to '" + host +
                    "' over the connection '" + connection + "'.");
            }

            do {
                try {
                    proxyInternal(connection, request, response, host);

                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE,
                            "Successfully proxied the request '" +
                            cRequest.getMethod() + " " +
                            cRequest.getRequestURI() + "' to '" + host +
                            "' over the connection '" + connection + "'.");
                    }

                    return;
                } catch (IOException e) {
                    // Try again
                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE,
                            "Failed to proxy the request'" +
                            cRequest.getRequestURI() + "' to '" + host +
                            "' over the connection '" + connection +
                            "'. Retries left: " + retries, e);
                    }

                    // Make sure this connection is closed!
                    connection.close();

                    retries--;
                } catch (Exception e) {
                    // Make sure this connection is closed!
                    connection.close();

                    log.log(Level.SEVERE,
                        "Failed to proxy the request'" +
                        cRequest.getRequestURI() + "' to '" + host +
                        "' over the connection '" + connection +
                        "'. Will not retry!", e);

                    // Do not retry!
                    break;
                }
            } while (retries >= 0);

            // Something went seriously wrong!
            sendInternalError(request, response, host);

            // Make sure this connection is closed!
            connection.close();
        } finally {
            // Always release the connection when done.
            if (connection != null) {
                connection.release();
            }
        }
    }

    /**
     * Update the request object if the request has been proxied.<br>
     * The following methods on the <code>ServletRequest</code> are updated
     * with the values recived at the frontend.<br>
     *
     * <ul>
     * <li> {@link ServletRequest#isSecure()}</li>
     * <li> {@link ServletRequest#getLocalAddr()}</li>
     * <li> {@link ServletRequest#getLocalPort()}</li>
     * <li> {@link ServletRequest#getScheme()}</li>
     * </ul>
     *
     * For the servlet that recives the request it should <strong>not</strong>
     * be seen that the request has been proxied internally in EAS. <br>
     * If the request has not been proxied this method will do nothing.
     *
     * @param request
     *           The request.
     */
    public static void updateRequest(Request request) {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteRequest cRequest = (CoyoteRequest) request;

        String exists = cRequest.getHeader(EAS_HEADER_FRONTEND_IS_SECURE);

        if (exists == null) {
            return;
        }

        org.apache.coyote.Request coyoteRequest = cRequest.getCoyoteRequest();
        MimeHeaders mimeHeaders = coyoteRequest.getMimeHeaders();

        String isSecure = cRequest.getHeader(EAS_HEADER_FRONTEND_IS_SECURE);
        String localAddress = cRequest.getHeader(EAS_HEADER_FRONTEND_LOCAL_ADDRESS);
        String localPort = cRequest.getHeader(EAS_HEADER_FRONTEND_LOCAL_PORT);

        mimeHeaders.removeHeader(EAS_HEADER_FRONTEND_IS_SECURE);
        mimeHeaders.removeHeader(EAS_HEADER_FRONTEND_LOCAL_ADDRESS);
        mimeHeaders.removeHeader(EAS_HEADER_FRONTEND_LOCAL_PORT);

        if ("true".equals(isSecure)) {
            request.setSecure(true);
            coyoteRequest.scheme()
                         .setBytes("https".getBytes(), 0, "https".length());
        }

        setFieldValue(request, Integer.parseInt(localPort), "localPort");
        setFieldValue(request, localAddress, "localAddr");
    }

    /**
     * Set a field's value
     *
     * @param object
     *           The filed's object
     * @param value
     *           The new value
     * @param fieldName
     *           The name of the field.
     */
    private static void setFieldValue(Object object, Object value,
        String fieldName) {
        try {
            Field f = getPrivateField(object, fieldName);
            f.set(object, value);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Gets a field from an object.
     *
     * @param o
     *           The object that has the field.
     * @param fieldName
     *           The name of the field.
     * @return An accessible field
     */
    private static Field getPrivateField(Object o, String fieldName) {
        final Field[] fields = o.getClass().getDeclaredFields();

        for (int i = 0; i < fields.length; ++i) {
            if (fieldName.equals(fields[i].getName())) {
                fields[i].setAccessible(true);

                return fields[i];
            }
        }

        throw new RuntimeException("Unable to lookup field=" + fieldName +
            " in object=" + o);
    }

    /**
     * Shuts down the proxy and all its connection pools. This method should be
     * called to clean up all connection pools since these are static resources.
     */
    public static void shutdown() {
        synchronized (pools) {
            for (HttpConnectionPool pool : pools.values()) {
                try {
                    pool.shutdown();
                } catch (IllegalStateException e) {
                    log.log(Level.WARNING,
                        "Problem when shutting down proxy " +
                        "connection pool: " + e.getMessage());
                }
            }
        }
    }

    /**
     * Performs the actual proxying operation.
     *
     * @param connection
     *        The connection to proxy the request over.
     * @param request
     *        The request.
     * @param response
     *        Te response.
     * @param host
     *        The host to proxy the request to.
     * @throws IOException
     *         Thrown if any IO exception ocurrs.
     * @throws HttpProxyRequestException
     *            Thrown if the proxy can't proxy this request due to an
     *            erroneous request.
     */
    private static void proxyInternal(HttpConnection connection,
        Request request, Response response, HttpHost host)
        throws IOException {
        // Make sure the connection is open.
        openConnection(connection);

        // Proxy the request to the back-end server.
        proxyRequest(connection, request);

        // Proxy the response back to the client.
        int maxLoop = 3;

        do {
            proxyResponse(connection, response);
            maxLoop--;
        } while ((maxLoop > 0) &&
                (((CoyoteResponse) response).getStatus() == CoyoteResponse.SC_CONTINUE));
    }

    /**
     * Obtains a connection from the connection pool for this host. This method
     * will also create a new pool for each new host that it encounters. This
     * method is blocking if no connection is available in the connection pool.
     *
     * @param host
     *        The host we want a connection for.
     * @return A connection to te specified host.
     */
    private static HttpConnection getConnection(HttpHost host) {
        // Obtain a reference to the connection pool.
        HttpConnectionPool pool = getConnectionPool(host);

        // Obtain a connection from the pool.
        return pool.getConnection(settings.getConnectionTimeout());
    }

    /**
     * Return a reference to the connection pool for this specific host. If no
     * connection pool has been created for this host yet, this method will also
     * create it and store it for later retrieval.
     *
     * @param host
     *        The host we want a connection pool reference for.
     * @return The connection pool for this specific host.
     */
    private static HttpConnectionPool getConnectionPool(HttpHost host) {
        HttpConnectionPool pool;

        synchronized (pools) {
            pool = pools.get(host);

            // Create a new pool if necessary.
            if (pool == null) {
                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE,
                        "Creating a new connection pool with " +
                        settings.getPoolSize() + " connections to host '" +
                        host + "'.");
                }

                // Create the new connection pool.
                pool = new HttpConnectionPool(host, settings.getPoolSize(),
                        settings.getSocketTimeout());

                // Store the connection pool in the pool of pools.
                pools.put(host, pool);
            }
        }

        return pool;
    }

    /**
     * Make sure that the passed connection is opened. If the connection has gone
     * stale, we reopen it.
     *
     * @param connection
     *        The connection we want opened.
     * @throws IOException
     *         If an IO exception occurred wen opening the connection.
     */
    private static void openConnection(HttpConnection connection)
        throws IOException {
        if (!connection.isOpen()) {
            connection.open();

            if (log.isLoggable(Level.FINE)) {
                log.log(Level.FINE, "The connection was opened: " + connection);
            }
        } else {
            if (connection.isStale()) {
                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE,
                        "The connection to '" + connection.getHost() +
                        "' appears to be stale. Reconnecting!");
                }

                connection.close();
                connection.open();

                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE,
                        "The connection was reopened: " + connection);
                }
            }
        }
    }

    /**
     * Proxy the request to the back-end server.
     *
     * @param connection
     *        The xonnection to the back-end server.
     * @param request
     *        The request to proxy.
     * @throws IOException
     *         Thrown if any IO problem occurs.
     */
    private static void proxyRequest(HttpConnection connection, Request request)
        throws IOException {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteRequest cRequest = (CoyoteRequest) request;

        // CoyoteResponse cResponse = (CoyoteResponse) response;
        String contentType = null;
        int contentLength = 0;

        // Emit the request line
        connection.printLine(createRequestLine(request));

        ArrayList<String> connectionToken = checkConnectionsToken(cRequest.getHeader(
                    "Connection"));

        // Emit some of the headers from the original request.
        Enumeration hdrNames = cRequest.getHeaderNames();

        while (hdrNames.hasMoreElements()) {
            String headerName = (String) hdrNames.nextElement();
            String headerValue = cRequest.getHeader(headerName);

            // Check for message content
            if ("Content-Type".equalsIgnoreCase(headerName)) {
                contentType = headerValue;
            }

            // Check for content length
            if ("Content-Length".equalsIgnoreCase(headerName)) {
                contentLength = Integer.parseInt(headerValue);
            }

            // If header is in the not-to-copy list, the header is
            // not copied to the proxied request.
            if (droppedRequestHeaders.contains(headerName.toLowerCase())) {
                continue;
            }

            // If the header is a connection token "extended not-to-copy" list.
            if ((connectionToken != null) &&
                    connectionToken.contains(headerName.toLowerCase())) {
                continue;
            }

            // Emit this header.
            connection.printLine(headerName + ": " + headerValue);
        }

        // Add EAS specific headers.
        addEasHeaders(connection, request);

        // If this is a retry, get the message body from the request note.
        MessageContent mc = (MessageContent) request.getNote(PROXY_REQUEST_BODY_NOTE);

        if (mc == null) {
            // Read the entire request body.
            mc = new MessageContent(getRequestInputStream(request, contentType,
                        contentLength));

            // This is the first try, so store the message body as a request note.
            // We need to store the request body somewhere for the retry mechanism
            // to
            // work since we will already have consumed the input during the first
            // try.
            request.setNote(PROXY_REQUEST_BODY_NOTE, mc);
        }

        // Only emit content length header if there is any content.
        if (mc.getContentLength() > 0) {
            connection.printLine("Content-Length: " + mc.getContentLength());

            // Complete headers with a CRLF.
            connection.writeLine();

            // Write the request body, if any, to the remote host.
            if (mc.getContentLength() > 0) {
                connection.write(mc.getContentAsBytes(), 0,
                    mc.getContentLength());
            }
        } else {
            // Complete headers with a CRLF.
            connection.writeLine();
        }

        // Make sure the request is sent on the socket.
        connection.flushOutputStream();
    }

    /**
     * Proxies the response back to the client.
     *
     * @param connection
     *        The connection to read the response from.
     * @param response
     *        The client response.
     * @throws IOException
     *         Thrown if any IO problem occurs.
     */
    private static void proxyResponse(HttpConnection connection,
        Response response) throws IOException {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteResponse cResponse = (CoyoteResponse) response;

        boolean chunked = false;
        boolean forcedClose = false;
        int contentLength = -1;

        try {
            // Transfer the status code and message to the response.
            int code = setStatusCode(connection, response);

            if (code == CoyoteResponse.SC_CONTINUE) {
                cResponse.sendAcknowledgement();

                return;
            }

            // Parse headers
            MimeHeaders headers = cResponse.getCoyoteResponse().getMimeHeaders();

            while (true) {
                // Read a header line.
                String header = connection.readLine();

                if ((header == null) || (header.trim().length() < 1)) {
                    break;
                }

                // Parse Headers
                int colon = header.indexOf(":");

                if (colon < 0) {
                    continue;
                }

                String hdr = header.substring(0, colon).trim();
                String val = new StringBuffer(header.substring(colon + 1).trim()).toString();

                // "Content-Length" and "Content-Type" should not be copied with
                // setValue
                // Those needs to be added by their own API's
                if ("Content-Length".equalsIgnoreCase(hdr)) {
                    contentLength = Integer.parseInt(val);
                    response.getResponse().setContentLength(contentLength);
                }

                if ("Content-Type".equalsIgnoreCase(hdr)) {
                    response.getResponse().setContentType(val);
                }

                if ("Transfer-Encoding".equalsIgnoreCase(hdr)) {
                    if ("chunked".equalsIgnoreCase(val)) {
                        chunked = true;
                    }
                }

                if ("Connection".equalsIgnoreCase(hdr)) {
                    if ("close".equalsIgnoreCase(val)) {
                        if (log.isLoggable(Level.FINE)) {
                            log.log(Level.FINE,
                                "Back-end wants to close this connection");
                        }

                        // This connection will not be reused after this response.
                        forcedClose = true;
                    }
                }

                // If header is in the not-to-copy list, the header is
                // not copied back to the original response
                if (droppedResponseHeaders.contains(hdr.toLowerCase())) {
                    continue;
                }

                if ((hdr != null) && (val != null)) {
                    MessageBytes mb = headers.addValue(hdr);
                    mb.setString(val);
                }
            }

            if (isResponseWithBody(code)) {
                OutputStream os = response.getResponse().getOutputStream();
                InputStream is = connection.getInputStream();

                if (chunked) {
                    is = new ChunkedInputStream(is);
                } else {
                    is = new ContentLengthInputStream(is, contentLength);
                }

                byte[] buffer = new byte[8192];
                int length;

                while ((length = is.read(buffer)) > 0) {
                    os.write(buffer, 0, length);
                }

                os.flush();
            }
        } finally {
            if (forcedClose) {
                connection.close();
            }
        }
    }

    /**
     * Retrieve an InputStream to read the request body from.
     *
     * @param request
     *        The request.
     * @param contentType
     *        The content type if any is specified.
     * @param contentLength
     *        The content length (if any) specified in the request.
     * @return An InputStream for the request body.
     * @throws IOException
     *         If any IO problem ocurred.
     */
    private static InputStream getRequestInputStream(Request request,
        String contentType, int contentLength) throws IOException {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteRequest cRequest = (CoyoteRequest) request;

        InputStream in;

        // If this is a POST request with posted parameters we
        // read the message body directly from the request since
        // this data may have already been consumed by Tomcat.
        if ((contentType != null) &&
                contentType.toLowerCase()
                               .startsWith("application/x-www-form-urlencoded") &&
                cRequest.getMethod().equalsIgnoreCase("POST")) {
            // Wrapping the already read POST data in a bounded InputStream.
            HttpFormData data = new HttpFormData(request);
            in = new ContentLengthInputStream(new ByteArrayInputStream(
                        data.getData()), data.getLength());
        } else {
            // Using the standard InputStream of the request.
            in = new ContentLengthInputStream(cRequest.getInputStream(),
                    contentLength);
        }

        return in;
    }

    /**
     * Read the status code from the status line of the response.
     *
     * @param connection
     *        The connection the response will be read from.
     * @param response
     *        The response to copy the information to.
     * @return The status code read from the status line.
     * @throws IOException
     *         If no response code could be read.
     */
    private static int setStatusCode(HttpConnection connection,
        Response response) throws IOException {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteResponse cResponse = (CoyoteResponse) response;

        String statusLine = readStatusLine(connection);

        try {
            Matcher matcher = statusLinePattern.matcher(statusLine);

            if (matcher.matches()) {
                int code = Integer.parseInt(matcher.group(1));
                String message = matcher.group(2);

                // Set the response code in the response to return.
                cResponse.getResponse().setStatus(code);
                cResponse.getCoyoteResponse().setMessage(message);

                return code;
            }
        } catch (NumberFormatException e) {
            // Fall through.
        }

        throw new IOException("Malformed response");
    }

    /**
     * Reads the status line from the response (the first line of the response).
     *
     * @param connection
     *        The connection to read the response from.
     * @return The status line.
     * @throws IOException
     *         If an IO exception occurs when reading the response from the
     *         connection.
     */
    private static String readStatusLine(HttpConnection connection)
        throws IOException {
        String statusLine;

        do {
            statusLine = connection.readLine();

            if (statusLine == null) {
                throw new IOException("No response could be read.");
            }
        } while (statusLine.trim().length() == 0);

        return statusLine;
    }

    /**
     * Determines if this response may have a body or not.
     *
     * @param statusCode
     *        The status code.
     * @return True if this response type may have a body.
     */
    private static boolean isResponseWithBody(int statusCode) {
        if (((statusCode >= 100) && (statusCode <= 199)) ||
                (statusCode == 204) || (statusCode == 304)) {
            return false;
        }

        return true;
    }

    /**
     * Create the request line out of the original request sent to the proxy.
     *
     * @param request
     *        The original request.
     * @return The request line.
     */
    private static String createRequestLine(Request request) {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteRequest cRequest = (CoyoteRequest) request;

        StringBuilder requestLine = new StringBuilder(cRequest.getMethod());
        requestLine.append(" ").append(cRequest.getRequestURI());

        if (cRequest.getQueryString() != null) {
            requestLine.append("?");
            requestLine.append(cRequest.getQueryString());
        }

        requestLine.append(" HTTP/1.1");

        return requestLine.toString();
    }

    /**
     * Add EAS specific headers.
     *
     * @param connection
     *        The connection we want the headers written to.
     * @param request
     *        The original request.
     * @throws IOException
     *         Thrown if the added headers could not be written to the
     *         connection.
     */
    private static void addEasHeaders(HttpConnection connection, Request request)
        throws IOException {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteRequest cRequest = (CoyoteRequest) request;

        // The front-end will add a HTTP header with information required for
        // single signon
        // e.g. x-eas-usercentric-ipcontext:
        // clientip=1.2.3.4,clientport=5000,serverip=5.6.7.8,serverport=9080
        String contextValue = "clientip=" + cRequest.getRemoteAddr() +
            ",clientport=" + cRequest.getRemotePort() + ",serverip=" +
            cRequest.getLocalAddr() + ",serverport=" + cRequest.getLocalPort();

        connection.printLine("x-eas-usercentric-ipcontext: " + contextValue);

        // Send information to backend TrafficProcessor.
        connection.printLine(EAS_HEADER_FRONTEND_IS_SECURE + ": " +
            cRequest.isSecure());
        connection.printLine(EAS_HEADER_FRONTEND_LOCAL_ADDRESS + ": " +
            cRequest.getLocalAddr());
        connection.printLine(EAS_HEADER_FRONTEND_LOCAL_PORT + ": " +
            cRequest.getLocalPort());
    }

    /**
     * Sends a 500 Server Internal Error to the client.
     *
     * @param request
     *        The request.
     * @param response
     *        The response.
     * @param host
     *        The host the request was proxied to.
     * @throws IOException
     *         Thrown if the error response could not be sent to the client.
     */
    private static void sendInternalError(Request request, Response response,
        HttpHost host) throws IOException {
        // GLASSFISH Request/Response does not extend HttpRequest/Response so
        // we need to cast to Coyote Request/Response
        CoyoteRequest cRequest = (CoyoteRequest) request;
        CoyoteResponse cResponse = (CoyoteResponse) response;

        // If the reponse is still not committed...
        if (!cResponse.isCommitted()) {
            // Failed to proxy this request, inform client.
            if (log.isLoggable(Level.FINE)) {
                log.log(Level.FINE,
                    "The attempt to proxy '" + cRequest.getRequestURL() +
                    "' to '" + host + "' failed. " +
                    "Responding with 500 Internal Server Error.");
            }

            cResponse.sendError(CoyoteResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * The method will parse the Connection header value and return an ArrayList
     * contains all the token in the header.
     *
     * @param connectionValue
     *        A string of the Connection header value(s).
     * @return An ArrayList with the all Connection tokens.
     */
    protected static ArrayList<String> checkConnectionsToken(
        String connectionValue) {
        if (connectionValue == null) { // if the connectionValue does not contains anything.

            return null;
        }

        if (!connectionValue.contains(",")) { // It will contain one token.

            ArrayList<String> al = new ArrayList<String>();
            al.add(connectionValue);

            return al;
        }

        ArrayList<String> connectionsTokens = new ArrayList<String>();

        if (connectionValue.contains(":") &&
                connectionValue.toLowerCase().startsWith("connection:")) {
            int colon = connectionValue.indexOf(":");
            connectionValue = connectionValue.substring(colon + 1,
                    connectionValue.length());
        }

        if (connectionValue.endsWith(";")) {
            connectionValue = connectionValue.substring(0,
                    connectionValue.length() - 1);
        }

        // Parse the connection tokens
        StringTokenizer stringTokenizer = new StringTokenizer(connectionValue,
                ",");

        while (stringTokenizer.hasMoreTokens()) {
            String token = stringTokenizer.nextToken();
            connectionsTokens.add(token.trim().toLowerCase());
        }

        return connectionsTokens;
    }
}
