com.ibm.sbt.service.basic.ProxyService.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.sbt.service.basic.ProxyService.java

Source

/*
 *  Copyright IBM Corp. 2012
 * 
 * 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 com.ibm.sbt.service.basic;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

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

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie;

import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.ByteStreamCache;
import com.ibm.commons.util.io.ReaderInputStream;
import com.ibm.commons.util.io.StreamUtil;
import com.ibm.commons.util.io.TraceOutputStream;
import com.ibm.commons.util.io.WriterOutputStream;
import com.ibm.commons.util.io.base64.Base64;
import com.ibm.commons.util.io.json.JsonGenerator;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.commons.util.io.json.JsonJavaObject;
import com.ibm.commons.util.profiler.Profiler;
import com.ibm.commons.util.profiler.ProfilerAggregator;
import com.ibm.commons.util.profiler.ProfilerType;
import com.ibm.sbt.service.debug.DebugProxyHook;
import com.ibm.sbt.service.debug.DebugServiceHookFactory;
import com.ibm.sbt.service.debug.DebugServiceHookFactory.Type;

/**
 * Basic proxy.
 * <p>
 * This is the base class handling proxy requests. It has to be instanciated and called
 * from a servlet, or from a proxy handler from the library.
 * </p>
 * @author Dan Dumont
 * @author Philippe Riand
 */
public class ProxyService {

    /// TODO: use a group here...
    private static boolean TRACE = false;

    private static final ProfilerType profilerRequest = new ProfilerType("Proxy request, "); //$NON-NLS-1$

    /**
     * The wrapper for cookie domain and path.  These are stored in the cookie value
     * when the cookie is passed to the browser.  So that the proxy domain and path
     * can be used when the cookie is passed back to the browser.
     */
    private static final String PASSTHRUID = "sbtsdkck";

    private DebugProxyHook debugHook;

    public ProxyService() {
    }

    public DebugProxyHook getDebugHook() {
        return debugHook;
    }

    public int getSocketReadTimeout() {
        return 120; // default, in seconds
    }

    protected DefaultHttpClient getClient(HttpServletRequest request, int timeout) {
        // Should we manage a connection pool here?
        //BasicHttpParams params = new BasicHttpParams();
        //params.setRedirecting(params, false);
        //DefaultHttpClient httpClient = new DefaultHttpClient(params);
        Object timedObject = ProxyProfiler.getTimedObject();
        DefaultHttpClient httpClient = new DefaultHttpClient();
        ProxyProfiler.profileTimedRequest(timedObject, "httpclient creation");
        return httpClient;

    }

    //
    // Security options - defensive mode by default
    //
    protected void checkRequestAllowed(HttpServletRequest request) throws ServletException {
        Object timedObject = ProxyProfiler.getTimedObject();
        String method = request.getMethod();
        if (!isMethodAllowed(method)) {
            throw new ServletException(StringUtil.format("Invalid request method {0}", method));
        }
        ProxyProfiler.profileTimedRequest(timedObject, "checkRequestAllowed");
    }

    protected boolean isMethodAllowed(String method) throws ServletException {
        return false;
    }

    protected boolean forwardHeaders(HttpRequestBase method, HttpServletRequest request) {
        return true;
    }

    protected boolean isHeaderAllowed(String headerName) throws ServletException {
        // content-length header will be automatically added by the HTTP client
        // host header causes request failures when it does not match the proxy host see x-forwaded-for comment above
        // Origin is added by chrome / safari in post/put/delete same-origin requests.
        if (StringUtil.equalsIgnoreCase(headerName, "content-length")
                || StringUtil.equalsIgnoreCase(headerName, "host")
                || StringUtil.equalsIgnoreCase(headerName, "origin")) {
            return false;
        }
        return true;
    }

    protected boolean forwardCookies(HttpRequestBase method, HttpServletRequest request) {
        return true;
    }

    protected boolean isCookieAllowed(String cookieName) throws ServletException {
        //if(cookieName.equalsIgnoreCase("JSESSIONID")) {
        //    return false;
        //}
        return true;
    }

    protected boolean isMimeTypeAllowed(String cookieName) throws ServletException {
        return true;
    }

    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Object timedObject = ProxyProfiler.getTimedObject();
        if (Profiler.isEnabled()) {
            StringBuffer b = request.getRequestURL();
            String url = b.toString();
            ProfilerAggregator agg = Profiler.startProfileBlock(profilerRequest,
                    request.getMethod().toUpperCase() + " " + url);
            long ts = Profiler.getCurrentTime();
            try {
                serviceProxy(request, response);
            } finally {
                Profiler.endProfileBlock(agg, ts);
            }
        } else {
            serviceProxy(request, response);
        }
        ProxyProfiler.profileTimedRequest(timedObject, "service request");
    }

    protected void serviceProxy(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.debugHook = (DebugProxyHook) DebugServiceHookFactory.get().get(Type.PROXY, request, response);
        if (debugHook != null) {
            request = debugHook.getRequestWrapper();
            response = debugHook.getResponseWrapper();
        }
        try {
            try {
                initProxy(request, response);
                try {
                    checkRequestAllowed(request);
                    String smethod = request.getMethod();
                    DefaultHttpClient client = getClient(request, getSocketReadTimeout());
                    URI url = getRequestURI(request);
                    HttpRequestBase method = createMethod(smethod, url, request);
                    if (prepareForwardingMethod(method, request, client)) {
                        HttpResponse clientResponse = executeMethod(client, method);
                        prepareResponse(method, request, response, clientResponse, true);
                    }
                    response.flushBuffer();
                } catch (Exception e) {
                    writeErrorResponse("Unexpected Exception", new String[] { "exception" },
                            new String[] { e.toString() }, response, request);
                } finally {
                    termProxy(request, response);
                }
            } catch (Exception e) {
                writeErrorResponse("Unexpected Exception", new String[] { "exception" },
                        new String[] { e.toString() }, response, request);
            }
        } finally {
            if (debugHook != null) {
                debugHook.terminate();
                debugHook = null;
            }
        }
    }

    private HttpRequestBase prepareMethodWithUpdatedContent(HttpRequestBase method, HttpServletRequest request)
            throws ServletException {
        // PHIL, 2/28/2013
        // We should use the length when available, for HTTP 1.1
        // -> it optimizes with HTTP 1.1
        // -> it prevents the HTTP client to use Transfert-Encoding: chunked, which doesn't
        //    seem to work will all HTTP servers (ex: Domino)
        // A browser should anyway provide the length.
        long length = -1;
        String slength = request.getHeader("Content-Length");
        if (StringUtil.isNotEmpty(slength)) {
            length = Long.parseLong(slength);
        }
        try {
            // When no length is specified, the HTTP client core forces Transfert-Encoding: chunked
            // The code bellow shows a workaround, although we should avoid that
            if (false) {
                if (length < 0) {
                    ByteStreamCache bs = new ByteStreamCache();
                    bs.copyFrom(request.getInputStream());
                    HttpEntity payloadEntity = new InputStreamEntity(bs.getInputStream(), bs.getLength());
                    ((HttpEntityEnclosingRequest) method).setEntity(payloadEntity);
                    return method;
                }
            }
            // Regular code
            HttpEntity payloadEntity = new InputStreamEntity(request.getInputStream(), length);
            ((HttpEntityEnclosingRequest) method).setEntity(payloadEntity);
        } catch (Exception e) {
            throw new ServletException("Error while parsing the payload");
        }
        return method;
    }

    protected void initProxy(HttpServletRequest request, HttpServletResponse response) throws ServletException {
    }

    protected void termProxy(HttpServletRequest request, HttpServletResponse response) throws ServletException {
    }

    protected HttpRequestBase createMethod(String smethod, URI uri, HttpServletRequest request)
            throws ServletException {
        Object timedObject = ProxyProfiler.getTimedObject();
        if (getDebugHook() != null) {
            getDebugHook().getDumpRequest().setMethod(smethod);
            getDebugHook().getDumpRequest().setUrl(uri.toString());
        }
        if (smethod.equalsIgnoreCase("get")) {
            HttpGet method = new HttpGet(uri);
            ProxyProfiler.profileTimedRequest(timedObject, "create HttpGet");
            return method;
        } else if (smethod.equalsIgnoreCase("put")) {
            HttpPut method = new HttpPut(uri);
            method = (HttpPut) prepareMethodWithUpdatedContent(method, request);
            ProxyProfiler.profileTimedRequest(timedObject, "create HttpPut");
            return method;
        } else if (smethod.equalsIgnoreCase("post")) {
            HttpPost method = new HttpPost(uri);
            method = (HttpPost) prepareMethodWithUpdatedContent(method, request);
            ProxyProfiler.profileTimedRequest(timedObject, "create HttpPost");
            return method;
        } else if (smethod.equalsIgnoreCase("delete")) {
            HttpDelete method = new HttpDelete(uri);
            ProxyProfiler.profileTimedRequest(timedObject, "create HttpDelete");
            return method;
        } else if (smethod.equalsIgnoreCase("head")) {
            HttpHead method = new HttpHead(uri);
            ProxyProfiler.profileTimedRequest(timedObject, "create HttpHead");
            return method;
        } else if (smethod.equalsIgnoreCase("options")) {
            HttpOptions method = new HttpOptions(uri);
            ProxyProfiler.profileTimedRequest(timedObject, "create HttpOptions");
            return method;
        } else {
            ProxyProfiler.profileTimedRequest(timedObject, "failed creating method");
            throw new ServletException("Illegal method, should be GET, PUT, POST, DELETE or HEAD");
        }
    }

    protected boolean prepareForwardingMethod(HttpRequestBase method, HttpServletRequest request,
            DefaultHttpClient client) throws ServletException {
        boolean retval = (!forwardCookies(method, request) || prepareForwardingCookies(method, request, client))
                && (!forwardHeaders(method, request) || prepareForwardingHeaders(method, request));
        return retval;
    }

    protected boolean prepareForwardingCookies(HttpRequestBase method, HttpServletRequest request,
            DefaultHttpClient httpClient) throws ServletException {
        Object timedObject = ProxyProfiler.getTimedObject();
        Cookie[] cookies = request.getCookies();
        BasicCookieStore cs = new BasicCookieStore();
        httpClient.setCookieStore(cs);
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie != null) {
                    String cookiename = cookie.getName();
                    if (StringUtil.isNotEmpty(cookiename)) {
                        String cookieval = cookie.getValue();
                        if (cookiename.startsWith(PASSTHRUID)) {
                            cookiename = cookiename.substring(PASSTHRUID.length());
                            if (isCookieAllowed(cookiename)) {
                                String[] parts = decodeCookieNameAndPath(cookiename);
                                if (parts != null && parts.length == 3) {
                                    cookiename = parts[0];
                                    String path = parts[1];
                                    String domain = parts[2];

                                    // Got stored domain now see if it matches destination
                                    BasicClientCookie methodcookie = new BasicClientCookie(cookiename, cookieval);
                                    methodcookie.setDomain(domain);
                                    methodcookie.setPath(path);
                                    cs.addCookie(methodcookie);
                                    if (getDebugHook() != null) {
                                        getDebugHook().getDumpRequest().addCookie(methodcookie.getName(),
                                                methodcookie.toString());
                                    }
                                }
                            }
                        } else if (isCookieAllowed(cookiename)) {
                            BasicClientCookie methodcookie = new BasicClientCookie(cookiename, cookieval);
                            String domain = cookie.getDomain();
                            if (domain == null) {
                                try {
                                    domain = method.getURI().getHost();
                                    domain = domain.substring(domain.indexOf('.'));
                                } catch (Exception e) {
                                    domain = "";
                                }
                            }
                            methodcookie.setDomain(domain);
                            String path = cookie.getPath();
                            if (path == null) {
                                path = "/";
                            }
                            methodcookie.setPath(path);
                            cs.addCookie(methodcookie);
                            if (getDebugHook() != null) {
                                getDebugHook().getDumpRequest().addCookie(methodcookie.getName(),
                                        methodcookie.toString());
                            }
                        }
                    }
                }
            }
        }
        ProxyProfiler.profileTimedRequest(timedObject, "perpareForwardingCookie");
        return true;
    }

    @SuppressWarnings("unchecked")
    protected boolean prepareForwardingHeaders(HttpRequestBase method, HttpServletRequest request)
            throws ServletException {
        Object timedObject = ProxyProfiler.getTimedObject();
        // Forward any headers that should be forwarded. Except cookies.
        StringBuffer xForwardedForHeader = new StringBuffer();
        for (Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements();) {
            String headerName = e.nextElement();
            // Ignore cookie - treat them separately
            if (headerName.equalsIgnoreCase("cookie")) { // $NON-NLS-1$
                continue;
            }
            String headerValue = request.getHeader(headerName);
            // This is to be investigated - Should the X-Forwarded-For being passed? Ryan.
            //            if(headerName.equalsIgnoreCase("X-Forwarded-For") || headerName.equalsIgnoreCase("host")) {
            //                addXForwardedForHeader(method, headerValue, xForwardedForHeader);
            //                continue;
            //            }
            // Ensure that the header is allowed
            if (isHeaderAllowed(headerName)) {
                method.addHeader(headerName, headerValue);
                if (getDebugHook() != null) {
                    getDebugHook().getDumpRequest().addHeader(headerName, headerValue);
                }
            }
        }
        String xForward = xForwardedForHeader.toString();
        if (StringUtil.isNotEmpty(xForward)) {
            method.addHeader("X-Forwarded-For", xForward);
            if (getDebugHook() != null) {
                getDebugHook().getDumpRequest().addHeader("X-Forwarded-For", xForward);
            }
        }
        ProxyProfiler.profileTimedRequest(timedObject, "prepareForwardingHeaders");
        return true;
    }

    /**
     * Adds a host to the x-Forwarded-For header.  This method is called a host or
     * x-Forwarded-For header is encountered on the proxied request.
     * @param method The request being made by the proxy.
     * @param headerValue The header value from the proxied request.
     * @param xForwardedForHeader The current valye of the x-Forward-For header that will be
     * added to the request made by the proxy.
     */
    protected void addXForwardedForHeader(HttpRequestBase method, String headerValue,
            StringBuffer xForwardedForHeader) {
        String[] forwards = headerValue.trim().split(",");
        for (String forward : forwards) {
            String host = forward.trim();
            if (!host.equals("")) {
                if (xForwardedForHeader.length() == 0) {
                    xForwardedForHeader.append(host);
                } else {
                    xForwardedForHeader.append("," + host);
                }
            }
        }
    }

    public HttpResponse executeMethod(HttpClient client, HttpRequestBase method) throws ServletException {
        try {
            Object timedObject = ProxyProfiler.getTimedObject();
            HttpResponse response = client.execute(method);
            ProxyProfiler.profileTimedRequest(timedObject, "remote call");
            return response;
        } catch (IOException ex) {
            throw new ServletException(ex);
        }
    }

    public void prepareResponse(HttpRequestBase method, HttpServletRequest request, HttpServletResponse response,
            HttpResponse clientResponse, boolean isCopy) throws ServletException {
        Object timedObject = ProxyProfiler.getTimedObject();
        try {
            int statusCode = clientResponse.getStatusLine().getStatusCode();
            if (statusCode == 401 || statusCode == 403) {
                clientResponse.setHeader("WWW-Authenticate", "");
            }
            response.setStatus(statusCode);
            if (getDebugHook() != null) {
                getDebugHook().getDumpResponse().setStatus(statusCode);
            }

            // Passed back all heads, but process cookies differently.
            Header[] headers = clientResponse.getAllHeaders();
            for (Header header : headers) {
                String headername = header.getName();

                if (headername.equalsIgnoreCase("Set-Cookie")) { // $NON-NLS-1$
                    if (forwardCookies(method, request)) {
                        // If cookie, have to rewrite domain/path for browser.
                        String setcookieval = header.getValue();

                        if (setcookieval != null) {
                            String thisserver = request.getServerName();

                            String thisdomain;
                            if (thisserver.indexOf('.') == -1) {
                                thisdomain = "";
                            } else {
                                thisdomain = thisserver.substring(thisserver.indexOf('.'));
                            }
                            String domain = null;

                            // path info = /protocol/server/path-on-server
                            //Matcher m = cookiePathPattern.matcher(request.getPathInfo());
                            String thispath = request.getContextPath() + request.getServletPath();
                            String path = null;

                            String[][] cookparams = getCookieStrings(setcookieval);

                            for (int j = 1; j < cookparams.length; j++) {
                                if ("domain".equalsIgnoreCase(cookparams[j][0])) { // $NON-NLS-1$
                                    domain = cookparams[j][1];
                                    cookparams[j][1] = null;
                                } else if ("path".equalsIgnoreCase(cookparams[j][0])) { // $NON-NLS-1$
                                    path = cookparams[j][1];
                                    cookparams[j][1] = null;
                                }
                            }

                            if (domain == null) {
                                domain = method.getURI().getHost();
                            }

                            // Set cookie name
                            String encoded = encodeCookieNameAndPath(cookparams[0][0], path, domain);
                            if (encoded != null) {
                                String newcookiename = PASSTHRUID + encoded;

                                StringBuilder newset = new StringBuilder(newcookiename);
                                newset.append('=');
                                newset.append(cookparams[0][1]);

                                for (int j = 1; j < cookparams.length; j++) {
                                    String settingname = cookparams[j][0];
                                    String settingvalue = cookparams[j][1];
                                    if (settingvalue != null) {
                                        newset.append("; ").append(settingname); // $NON-NLS-1$
                                        newset.append('=').append(settingvalue); // $NON-NLS-1$
                                    }
                                }

                                newset.append("; domain=").append(thisdomain); // $NON-NLS-1$
                                newset.append("; path=").append(thispath); // $NON-NLS-1$

                                String newsetcookieval = newset.toString();
                                // this implementation of HttpServletRequest seems to have issues... setHeader works as I would
                                // expect addHeader to.
                                response.setHeader(headername, newsetcookieval);
                                if (getDebugHook() != null) {
                                    getDebugHook().getDumpResponse().addCookie(headername, newsetcookieval);
                                }
                            }
                        }
                    }
                } else if (!headername.equalsIgnoreCase("Transfer-Encoding")) { // $NON-NLS-1$
                    String headerval = header.getValue();

                    if (headername.equalsIgnoreCase("content-type")) {
                        int loc = headerval.indexOf(';');
                        String type;
                        if (loc > 0) {
                            type = headerval.substring(0, loc).trim();
                        } else {
                            type = headerval;
                        }
                        if (!isMimeTypeAllowed(type)) {
                            isCopy = false;
                            break;
                        } else {
                            response.setHeader(headername, headerval);
                            if (getDebugHook() != null) {
                                getDebugHook().getDumpResponse().addHeader(headername, headerval);
                            }
                        }
                    } else if ((statusCode == 401 || statusCode == 403)
                            && headername.equalsIgnoreCase("WWW-Authenticate")) { // $NON-NLS-1$
                        if (headerval.indexOf("Basic") != -1) { // $NON-NLS-1$
                            String pathInfo = request.getPathInfo();
                            String[] pathParts = (pathInfo.startsWith("/") ? pathInfo.substring(1) : pathInfo)
                                    .split("/");
                            if (pathParts.length > 1) {
                                StringBuilder strb = new StringBuilder("Basic realm=\""); // $NON-NLS-1$
                                strb.append(request.getContextPath());
                                strb.append(request.getServletPath());
                                strb.append('/');
                                strb.append(pathParts[0]);
                                strb.append('/');
                                strb.append(pathParts[1]);
                                strb.append('"');
                                headerval = strb.toString();
                                response.setHeader(headername, headerval);
                                if (getDebugHook() != null) {
                                    getDebugHook().getDumpResponse().addHeader(headername, headerval);
                                }
                            }
                        }
                    } else {
                        response.setHeader(headername, headerval);
                        if (getDebugHook() != null) {
                            getDebugHook().getDumpResponse().addHeader(headername, headerval);
                        }
                    }
                }
            }

            // Need to move response body over too
            if (statusCode == HttpServletResponse.SC_NO_CONTENT
                    || statusCode == HttpServletResponse.SC_NOT_MODIFIED) {
                response.setHeader("Content-Length", "0");
                if (getDebugHook() != null) {
                    getDebugHook().getDumpResponse().addHeader("Content-Length", "0");
                }
            } else if (isCopy) {
                HttpEntity entity = clientResponse.getEntity();
                InputStream inStream = entity.getContent();
                if (inStream != null) {
                    OutputStream os = response.getOutputStream();
                    if (TRACE) {
                        OutputStream tos = new TraceOutputStream(os, System.out, false);
                        os = tos;
                    }
                    StreamUtil.copyStream(inStream, os);
                    os.flush();
                } else {
                    response.setHeader("Content-Length", "0");
                    if (getDebugHook() != null) {
                        getDebugHook().getDumpResponse().addHeader("Content-Length", "0");
                    }
                }
            }
        } catch (IOException ex) {
            throw new ServletException(ex);
        }
        ProxyProfiler.profileTimedRequest(timedObject, "prepareResponse");
    }

    protected void writeErrorResponse(String errorMessage, String[] parameters, String[] values,
            HttpServletResponse response, HttpServletRequest request) throws ServletException {
        writeErrorResponse(404, errorMessage, parameters, values, response, request);
    }

    public static void writeErrorResponse(int httpstatus, String errorMessage, String[] parameters, String[] values,
            HttpServletResponse response, HttpServletRequest request) throws ServletException {
        JsonJavaObject o = new JsonJavaObject();
        o.putString("Message", errorMessage);
        List<JsonJavaObject> params = new ArrayList<JsonJavaObject>();
        if (parameters != null && parameters.length > 0) {
            for (int i = 0; i < parameters.length; i++) {
                JsonJavaObject e = new JsonJavaObject();
                e.putString(parameters[i], values[i]);
                params.add(e);
            }
        }
        o.putObject("Parameters", params);

        try {
            response.setStatus(httpstatus);
            //response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.addHeader("content-type", "application/json"); // $NON-NLS-1$ $NON-NLS-2$
            response.addHeader("Server", "SBT"); // $NON-NLS-1$ $NON-NLS-2$
            response.addHeader("Connection", "close"); // $NON-NLS-1$ $NON-NLS-2$
            response.addDateHeader("Date", System.currentTimeMillis()); // $NON-NLS-1$
            response.getOutputStream().print(JsonGenerator.toJson(JsonJavaFactory.instanceEx, o));
        } catch (Exception e) {
        }
    }

    private String[][] getCookieStrings(String set_cookie) {
        String[] pairs = set_cookie.split(";");
        String[][] ret = new String[pairs.length][];

        for (int i = 0; i < pairs.length; i++) {
            String p = pairs[i];
            ret[i] = new String[2];

            int eqloc = p.indexOf('=');
            if (eqloc < 0) {
                ret[i][0] = p;
                ret[i][1] = null;
            } else {
                ret[i][0] = p.substring(0, eqloc).trim();
                ret[i][1] = p.substring(eqloc + 1).trim();
            }
        }

        return ret;
    }

    protected URI getRequestURI(HttpServletRequest request) throws ServletException {
        try {
            Object timedObject = ProxyProfiler.getTimedObject();
            String orgUrl = getRequestURIPath(request);
            String queryargs = getRequestURLQueryString(request);
            if (StringUtil.isNotEmpty(queryargs)) {
                orgUrl += '?' + queryargs;
            }
            URI url = new URI(orgUrl);
            ProxyProfiler.profileTimedRequest(timedObject, "get request uri");
            return url;
        } catch (URISyntaxException ex) {
            throw new ServletException(ex);
        }
    }

    protected String getRequestURIPath(HttpServletRequest request) throws ServletException {
        String pathinfo = request.getPathInfo();
        int pos = pathinfo.indexOf("/http/"); // $NON-NLS-1$
        if (pos == -1) {
            pos = pathinfo.indexOf("/https/"); // $NON-NLS-1$
        }
        if (pos > 0) {
            pathinfo = pathinfo.substring(pos);
        }
        String orgUrl = pathinfo.substring(1).replaceFirst("\\/", "://");
        return orgUrl;
    }

    protected String getRequestURLQueryString(HttpServletRequest request) throws ServletException {
        String queryargs = request.getQueryString();
        return queryargs;
    }

    private static String encodeBase64(byte[] b) {
        try {
            StringWriter sw = new StringWriter();
            Base64.OutputStream b64 = new Base64.OutputStream(new WriterOutputStream(sw));

            int len = b.length;
            for (int i = 0; i < len; i++) {
                int c = b[i];
                b64.write(c);
            }
            b64.flushBuffer();

            return sw.toString();
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    private static byte[] decodeBase64(String s) {
        try {
            Base64.InputStream b64 = new Base64.InputStream(new ReaderInputStream(new StringReader(s)));
            ByteBuffer bb = ByteBuffer.allocate(1024 * 4); // max cookie size
            int byt;
            while ((byt = b64.read()) >= 0) {
                bb.put((byte) (byt & 0xFF));
            }
            return bb.array();
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    protected static String encodeCookieNameAndPath(String name, String path, String domain)
            throws ServletException {
        try {
            String s = new StringBuilder(name).append(';').append(path).append(';').append(domain).toString();
            String encoded = URLEncoder.encode(s, "UTF-8");
            return encoded;
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    protected static String[] decodeCookieNameAndPath(String encoded) throws ServletException {
        try {
            String s = URLDecoder.decode(encoded, "UTF-8");
            return StringUtil.splitString(s, ';');
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    //    protected static String encodeCookieNameAndPath(String name, String path, String domain) throws ServletException {
    //        final ByteBuffer bb = ByteBuffer.allocate(1024*4); // max cookie size
    //        final byte[] buffer = new byte[512];
    //        final Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION, true);
    //        String ret = null;
    //        try {
    //            // Join name and path strings with ';' and get the UTF-8 bytes.
    //            byte[] in = new StringBuilder(name)
    //                            .append(';')
    //                            .append(path)
    //                            .append(';')
    //                            .append(domain)
    //                            .toString().getBytes("UTF-8");
    //            deflater.setInput(in);
    //            deflater.finish();
    //            int written = 0;
    //
    //            // Deflate the byte array into out
    //            while ( (written = deflater.deflate(buffer)) == buffer.length ) {
    //                bb.put(buffer, bb.position(), written);
    //            }
    //            bb.put(buffer, bb.position(), written);
    //            byte[] out = new byte[bb.position()];
    //            System.arraycopy(bb.array(), 0, out, 0, out.length);
    //
    //            // Base64Encode the out byte array and replace unsafe characters.
    //            // Replace padding '=' with a single number of how many padding chars there were.
    //            ret = encodeBase64(out).replaceAll("\\s", "").replaceAll("[/]", "_").replaceAll("[+]", "-"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
    //            int i = ret.indexOf('=');
    //            if (i < 0)
    //                i = ret.length();
    //            int len = ret.length() - i;
    //            ret = ret.substring(0, i);
    //            ret = ret + len;
    //        } catch (Throwable t) {}
    //        return ret;
    //    }
    //    protected static String[] decodeCookieNameAndPath(String encoded) throws ServletException {
    //        final Inflater inflater = new Inflater(true);
    //        final ByteBuffer bb = ByteBuffer.allocate(1024*4); // max cookie size
    //        final byte[] buffer = new byte[512];
    //
    //        String[] ret = null;
    //        try {
    //            // Replace the safe characters with the proper Base64 alphabet characters.
    //            encoded = encoded.replaceAll("[_]", "/").replaceAll("[-]", "_"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    //
    //            // Replace the padding digit with the correct number of padding characters.
    //            int len = 0;
    //            try {
    //                len = Integer.parseInt(encoded.substring(encoded.length() - 1));
    //                encoded = encoded.substring(0, encoded.length() - 1);
    //            } catch (Exception e) {
    //            }
    //            final String padding = "==="; // $NON-NLS-1$
    //            encoded = encoded + padding.substring(0, len);
    //
    //            // Base64Decode the string into a byte array 'in'.
    //            byte[] in = decodeBase64(encoded);
    //            inflater.setInput(in);
    //
    //            // Inflate 'in' into the byte buffer.
    //            int written = 0;
    //            while ( (written = inflater.inflate(buffer)) == buffer.length ) {
    //                bb.put(buffer, bb.position(), written);
    //            }
    //            bb.put(buffer, bb.position(), written);
    //
    //            // Make a new string from the inflated bytebuffer, using UTF-8 charset
    //            String namepath = new String(bb.array(), 0, bb.position(), "UTF-8");
    //
    //            // split the name/path pair into the return array value.
    //            ret = namepath.split(";");
    //        } catch (Throwable t) {}
    //        return ret;
    //    }
}