com.wordpress.metaphorm.authProxy.httpClient.impl.OAuthProxyConnectionApacheHttpCommonsClientImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.wordpress.metaphorm.authProxy.httpClient.impl.OAuthProxyConnectionApacheHttpCommonsClientImpl.java

Source

package com.wordpress.metaphorm.authProxy.httpClient.impl;
/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 *
 *
 * This class is partly based on another Servlet licensed under Apache License 2.0. Differences include...
 *  
 *  - allow any HTTP endpoint to be proxied
 *  - provide an implementation of com.wordpress.metaphorm.authProxy.httpClient.AuthProxyConnection interface 
 *  - provide a concrete implementation of com.wordpress.metaphorm.authProxy.httpClient.impl.AbstractOAuthProxyConnectionImpl
 * 
 * The original Servlet source code can be found here:
 * https://svn.apache.org/repos/asf/rave/donations/ogce-gadget-container/ishindig-webapp/src/main/java/cgl/shindig/layoutmanager/servlet/ProxyServlet.java
 *
 */

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.wordpress.metaphorm.authProxy.RedirectRequiredException;
import com.wordpress.metaphorm.authProxy.httpClient.HttpConstants;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;

/**
 * @author Stian Sigvartsen
 */
public class OAuthProxyConnectionApacheHttpCommonsClientImpl extends AbstractOAuthProxyConnectionImpl {

    /**
     * Serialization UID.
     */
    private static final long serialVersionUID = 1L;

    /**
      * The directory to use to temporarily store uploaded files
      */
    private static final File FILE_UPLOAD_TEMP_DIRECTORY = new File(System.getProperty("java.io.tmpdir"));

    /**
     * The maximum size for uploaded files in bytes. Default value is 5MB.
     */
    private int intMaxFileUploadSize = 5 * 1024 * 1024;

    private HttpServletRequest servletReq;
    private int httpStatusCode;
    private HttpMethod httpMethod;
    private Map<String, List<String>> responseHeaderMap;

    public OAuthProxyConnectionApacheHttpCommonsClientImpl(HttpServletRequest servletReq)
            throws MalformedURLException, IOException {

        this.servletReq = servletReq;

        prepareHttpMethod(servletReq);
    }

    public boolean isNegotiatingConnection() {
        return true;
    }

    public HttpMethod getHttpMethod() {
        return httpMethod;
    }

    private void prepareHttpMethod(HttpServletRequest servletReq) throws IOException {

        if (servletReq.getMethod().equalsIgnoreCase("GET")) {
            prepareGetMethod(servletReq);
        } else if (servletReq.getMethod().equalsIgnoreCase("POST")) {
            preparePostMethod(servletReq);
        } else {
            throw new IOException("Unsupport HTTP method: " + servletReq.getMethod());
        }

        httpStatusCode = 0;
        responseHeaderMap = null;
    }

    private void prepareGetMethod(HttpServletRequest httpServletRequest) throws IOException {

        // Create a GET request
        GetMethod getMethodProxyRequest = new GetMethod(this.getProxyURL(httpServletRequest));
        // Forward the request headers
        setProxyRequestHeaders(httpServletRequest, getMethodProxyRequest);

        this.httpMethod = getMethodProxyRequest;
    }

    private void preparePostMethod(HttpServletRequest httpServletRequest) throws IOException {

        // Create a standard POST request
        PostMethod postMethodProxyRequest = new PostMethod(this.getProxyURL(httpServletRequest));
        // Forward the request headers
        setProxyRequestHeaders(httpServletRequest, postMethodProxyRequest);
        // Check if this is a mulitpart (file upload) POST
        if (ServletFileUpload.isMultipartContent(httpServletRequest)) {
            this.handleMultipartPost(postMethodProxyRequest, httpServletRequest);
        } else {
            this.handleStandardPost(postMethodProxyRequest, httpServletRequest);
        }

        this.httpMethod = postMethodProxyRequest;
    }

    @Override
    public URL getRequestedURL() throws MalformedURLException {
        return new URL(servletReq.getRequestURL().toString());
    }

    public void sendRequest() throws IOException {

        executeProxyRequest(httpMethod);
    }

    public void reset() throws IOException {

        prepareHttpMethod(this.servletReq);
    }

    @Override
    public InputStream getInputStream() throws IOException {

        /*
        List<String> compression;
        if ((compression = this.getResponseHeaders().get(HttpConstants.STRING_CONTENT_ENCODING_HEADER_NAME)) != null) {
            
           if (compression.size() > 0 && compression.get(0).contains("gzip")) {
        return new GZIPInputStream(this.httpMethod.getResponseBodyAsStream());
           }
        }
        */

        return this.httpMethod.getResponseBodyAsStream();
    }

    @Override
    public int getResponseCode() throws IOException {
        return this.httpStatusCode;
    }

    @Override
    public String getContentType() {

        Header header;
        String contentType = ((header = this.httpMethod
                .getResponseHeader(HttpConstants.STRING_CONTENT_TYPE_HEADER_NAME)) != null ? header.getValue()
                        : null);

        _log.debug("Response header " + HttpConstants.STRING_CONTENT_TYPE_HEADER_NAME + " = " + contentType);

        return contentType;
    }

    /**
     * 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 {

        prepareGetMethod(httpServletRequest);

        // TODO: Add code to send respond if servlet deployment support is desirable
    }

    /**
     * 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 {

        preparePostMethod(httpServletRequest);
    }

    /**
     * Sets up the given {@link PostMethod} to send the same multipart POST
     * data as was sent in the given {@link HttpServletRequest}
     * @param postMethodProxyRequest The {@link PostMethod} that we are
     *                                configuring to send a multipart POST request
     * @param httpServletRequest The {@link HttpServletRequest} that contains
     *                            the mutlipart POST data to be sent via the {@link PostMethod}
     */
    @SuppressWarnings("unchecked")
    private void handleMultipartPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest)
            throws IOException {

        _log.debug("handleMultipartPost()");

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

    /**
     * Sets up the given {@link PostMethod} to send the same standard POST
     * data as was sent in the given {@link HttpServletRequest}
     * @param postMethodProxyRequest The {@link PostMethod} that we are
     *                                configuring to send a standard POST request
     * @param httpServletRequest The {@link HttpServletRequest} that contains
     *                            the POST data to be sent via the {@link PostMethod}
     */
    @SuppressWarnings("unchecked")
    private void handleStandardPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest) {

        _log.debug("handleStandardPost()");

        // Get the client POST data as a Map
        Map<String, String[]> mapPostParameters = (Map<String, String[]>) httpServletRequest.getParameterMap();
        // Create a List to hold the NameValuePairs to be passed to the PostMethod
        List<NameValuePair> listNameValuePairs = new ArrayList<NameValuePair>();
        // Iterate the parameter names
        for (String stringParameterName : mapPostParameters.keySet()) {

            StringBuffer debugMsg = new StringBuffer();
            debugMsg.append("Post param: " + stringParameterName);

            // Iterate the values for each parameter name
            String[] stringArrayParameterValues = mapPostParameters.get(stringParameterName);
            for (String stringParamterValue : stringArrayParameterValues) {

                debugMsg.append(" \"" + stringParamterValue + "\"");

                // Create a NameValuePair and store in list
                NameValuePair nameValuePair = new NameValuePair(stringParameterName, stringParamterValue);
                listNameValuePairs.add(nameValuePair);
            }
            _log.debug(debugMsg.toString());
        }
        // Set the proxy request POST data 
        postMethodProxyRequest.setRequestBody(listNameValuePairs.toArray(new NameValuePair[] {}));
    }

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

        //Utils.traceRequest(httpServletRequest);

        // Create a default HttpClient
        HttpClient httpClient = new HttpClient();
        httpMethodProxyRequest.setFollowRedirects(false);

        _log.debug("Sending request to " + httpMethodProxyRequest.getURI());

        for (Header header : httpMethodProxyRequest.getRequestHeaders()) {
            _log.debug("  Header \"" + header.getName() + "\" = \"" + header.getValue() + "\"");
        }

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

        // Persist the respose headers
        this.responseHeaderMap = new HashMap<String, List<String>>();
        for (Header header : this.httpMethod.getResponseHeaders()) {

            responseHeaderMap.put(header.getName(), Arrays.asList(header.getValue()));
        }

        // 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(HttpConstants.STRING_LOCATION_HEADER)
                    .getValue();
            if (stringLocation == null) {
                throw new IOException("Recieved status code: " + stringStatusCode + " but no "
                        + HttpConstants.STRING_LOCATION_HEADER + " header was found in the response");
            }

            throw new RedirectRequiredException(new URL(stringLocation));

        } 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(HttpConstants.STRING_CONTENT_LENGTH_HEADER_NAME, 0);
            //httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        }

        // Pass the response code back to the client
        this.httpStatusCode = intProxyResponseCode;

        _log.debug("Response code was " + this.httpStatusCode);

        // Pass response headers back to the client
        // TODO: Implement support for proxying response headers
        //Header[] headerArrayResponse = httpMethodProxyRequest.getResponseHeaders();
        //for(Header header : headerArrayResponse) {
        //   httpServletResponse.setHeader(header.getName(), header.getValue());
        //}

        // Send the content to the client
        //InputStream inputStreamProxyResponse = httpMethodProxyRequest.getResponseBodyAsStream();
        //BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStreamProxyResponse);
        //OutputStream outputStreamClientResponse = httpServletResponse.getOutputStream();
        //int intNextByte;
        //while ( ( intNextByte = bufferedInputStream.read() ) != -1 ) {
        //   outputStreamClientResponse.write(intNextByte);
        //}
    }

    /**
     * Retreives all of the headers from the servlet request and sets them on
     * the proxy request
     * 
     * @param httpServletRequest The request object representing the client's
     *                            request to the servlet engine
     * @param httpMethodProxyRequest The request that we are about to send to
     *                                the proxy host
     */
    @SuppressWarnings("unchecked")
    private void setProxyRequestHeaders(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
        // Get an Enumeration of all of the header names sent by the client
        Enumeration<String> enumerationOfHeaderNames = httpServletRequest.getHeaderNames();
        while (enumerationOfHeaderNames.hasMoreElements()) {
            String stringHeaderName = (String) enumerationOfHeaderNames.nextElement();
            if (stringHeaderName.equalsIgnoreCase(HttpConstants.STRING_CONTENT_LENGTH_HEADER_NAME))
                continue;

            // Support gzip transfer encoding
            if (stringHeaderName.equalsIgnoreCase(HttpConstants.STRING_ACCEPT_ENCODING_HEADER_NAME)) {
                httpMethodProxyRequest.setRequestHeader(stringHeaderName, "gzip");
                continue;
            }

            // As per the Java Servlet API 2.5 documentation:
            //      Some headers, such as Accept-Language can be sent by clients
            //      as several headers each with a different value rather than
            //      sending the header as a comma separated list.
            // Thus, we get an Enumeration of the header values sent by the client
            Enumeration<String> enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName);
            while (enumerationOfHeaderValues.hasMoreElements()) {
                String stringHeaderValue = (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(HttpConstants.STRING_HOST_HEADER_NAME)){
                //   stringHeaderValue = getProxyHostAndPort();
                //}

                Header header = new Header(stringHeaderName, stringHeaderValue);
                // Set the same header on the proxy request
                httpMethodProxyRequest.setRequestHeader(header);
            }
        }
    }

    // Accessors
    private String getProxyURL(HttpServletRequest httpServletRequest) throws MalformedURLException {

        return deriveURL(httpServletRequest).toString();
    }

    private int getMaxFileUploadSize() {
        return this.intMaxFileUploadSize;
    }

    private void setMaxFileUploadSize(int intMaxFileUploadSizeNew) {
        this.intMaxFileUploadSize = intMaxFileUploadSizeNew;
    }

    @Override
    public Map<String, List<String>> getResponseHeaders() throws IOException {

        return responseHeaderMap;
    }

    private static Log _log = LogFactoryUtil.getLog(OAuthProxyConnectionApacheHttpCommonsClientImpl.class);
}