com.jivesoftware.os.jive.utils.http.client.ApacheHttpClient31BackedHttpClient.java Source code

Java tutorial

Introduction

Here is the source code for com.jivesoftware.os.jive.utils.http.client.ApacheHttpClient31BackedHttpClient.java

Source

/*
 * Copyright 2013 Jive Software, Inc
 *
 * 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.jivesoftware.os.jive.utils.http.client;

import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.commonshttp3.CommonsHttp3OAuthConsumer;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.StatusLine;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.glassfish.jersey.media.multipart.ContentDisposition;

class ApacheHttpClient31BackedHttpClient implements HttpClient {

    private static final MetricLogger LOG = MetricLoggerFactory.getLogger(true);
    private static final String TIMER_NAME = "OutboundHttpRequest";
    private final org.apache.commons.httpclient.HttpClient client;
    private final Map<String, String> headersForEveryRequest;
    private String consumerKey;
    private String consumerSecret;
    private boolean isOauthEnabled = false;
    private boolean isSSLEnabled = false;
    private static final int JSON_POST_LOG_LENGTH_LIMIT = 2048;

    public ApacheHttpClient31BackedHttpClient(org.apache.commons.httpclient.HttpClient client,
            Map<String, String> headersForEveryRequest) {
        this.client = client;
        this.headersForEveryRequest = headersForEveryRequest;
    }

    @Override
    public HttpResponse get(String path) throws HttpClientException {
        return get(path, null, -1);
    }

    @Override
    public HttpResponse get(String path, Map<String, String> headers) throws HttpClientException {
        return get(path, headers, -1);
    }

    @Override
    public HttpResponse get(String path, int timeoutMillis) throws HttpClientException {
        return get(path, null, timeoutMillis);
    }

    @Override
    public HttpResponse get(String path, Map<String, String> headers, int timeoutMillis)
            throws HttpClientException {
        return executeMethod(new GetMethod(path), headers, timeoutMillis);
    }

    @Override
    public HttpStreamResponse getStream(String path) throws HttpClientException {
        return getStream(path, -1);
    }

    @Override
    public HttpStreamResponse getStream(String path, int timeoutMillis) throws HttpClientException {
        return executeMethodStream(new DeleteMethod(path), timeoutMillis);
    }

    @Override
    public HttpResponse delete(String path) throws HttpClientException {
        return delete(path, null, -1);
    }

    @Override
    public HttpResponse delete(String path, Map<String, String> headers) throws HttpClientException {
        return delete(path, headers, -1);
    }

    @Override
    public HttpResponse delete(String path, int timeoutMillis) throws HttpClientException {
        return delete(path, null, timeoutMillis);
    }

    @Override
    public HttpResponse delete(String path, Map<String, String> headers, int timeoutMillis)
            throws HttpClientException {
        return executeMethod(new DeleteMethod(path), headers, timeoutMillis);
    }

    @Override
    public HttpStreamResponse deleteStream(String path) throws HttpClientException {
        return deleteStream(path, -1);
    }

    @Override
    public HttpStreamResponse deleteStream(String path, int timeoutMillis) throws HttpClientException {
        return executeMethodStream(new DeleteMethod(path), timeoutMillis);
    }

    @Override
    public HttpResponse postJson(String path, String postJsonBody) throws HttpClientException {
        return postJson(path, postJsonBody, null, -1);
    }

    @Override
    public HttpResponse postJson(String path, String postJsonBody, Map<String, String> headers)
            throws HttpClientException {
        return postJson(path, postJsonBody, headers, -1);
    }

    @Override
    public HttpResponse postJson(String path, String postJsonBody, int timeoutMillis) throws HttpClientException {
        return postJson(path, postJsonBody, null, timeoutMillis);
    }

    @Override
    public HttpResponse postJson(String path, String postJsonBody, Map<String, String> headers, int timeoutMillis)
            throws HttpClientException {
        return executeMethodJson(new PostMethod(path), postJsonBody, headers, timeoutMillis);
    }

    @Override
    public HttpResponse postBytes(String path, byte[] postBytes) throws HttpClientException {
        return postBytes(path, postBytes, -1);
    }

    @Override
    public HttpResponse postBytes(String path, byte[] postBytes, int timeoutMillis) throws HttpClientException {
        return executeMethodBytes(new PostMethod(path), postBytes, timeoutMillis);
    }

    @Override
    public HttpResponse putJson(String path, String putJsonBody) throws HttpClientException {
        return putJson(path, putJsonBody, null, -1);
    }

    @Override
    public HttpResponse putJson(String path, String putJsonBody, Map<String, String> headers)
            throws HttpClientException {
        return putJson(path, putJsonBody, headers, -1);
    }

    @Override
    public HttpResponse putJson(String path, String putJsonBody, int timeoutMillis) throws HttpClientException {
        return putJson(path, putJsonBody, null, timeoutMillis);
    }

    @Override
    public HttpResponse putJson(String path, String putJsonBody, Map<String, String> headers, int timeoutMillis)
            throws HttpClientException {
        return executeMethodJson(new PutMethod(path), putJsonBody, headers, timeoutMillis);
    }

    @Override
    public HttpResponse putBytes(String path, byte[] putBytes) throws HttpClientException {
        return putBytes(path, putBytes, -1);
    }

    @Override
    public HttpResponse putBytes(String path, byte[] putBytes, int timeoutMillis) throws HttpClientException {
        return executeMethodBytes(new PutMethod(path), putBytes, timeoutMillis);
    }

    private HttpResponse executeMethodJson(EntityEnclosingMethod method, String jsonBody,
            Map<String, String> headers, int timeoutMillis) throws HttpClientException {
        try {
            setRequestHeaders(headers, method);

            method.setRequestEntity(
                    new StringRequestEntity(jsonBody, Constants.APPLICATION_JSON_CONTENT_TYPE, "UTF-8"));
            method.setRequestHeader(Constants.CONTENT_TYPE_HEADER_NAME, Constants.APPLICATION_JSON_CONTENT_TYPE);
            if (timeoutMillis > 0) {
                return executeWithTimeout(method, timeoutMillis);
            } else {
                return execute(method);
            }
        } catch (Exception e) {
            String trimmedMethodBody = (jsonBody.length() > JSON_POST_LOG_LENGTH_LIMIT)
                    ? jsonBody.substring(0, JSON_POST_LOG_LENGTH_LIMIT)
                    : jsonBody;
            throw new HttpClientException("Error executing " + method.getName() + " request to: "
                    + client.getHostConfiguration().getHostURL() + " path: " + method.getPath() + " JSON body: "
                    + trimmedMethodBody, e);
        }
    }

    private HttpResponse executeMethodBytes(EntityEnclosingMethod method, byte[] putBytes, int timeoutMillis)
            throws HttpClientException {
        try {
            method.setRequestEntity(new ByteArrayRequestEntity(putBytes, Constants.APPLICATION_JSON_CONTENT_TYPE));
            method.setRequestHeader(Constants.CONTENT_TYPE_HEADER_NAME, Constants.APPLICATION_OCTET_STREAM_TYPE);
            if (timeoutMillis > 0) {
                return executeWithTimeout(method, timeoutMillis);
            } else {
                return execute(method);
            }
        } catch (Exception e) {
            String trimmedMethodBody = (putBytes.length > JSON_POST_LOG_LENGTH_LIMIT)
                    ? new String(putBytes, 0, JSON_POST_LOG_LENGTH_LIMIT)
                    : new String(putBytes);
            throw new HttpClientException("Error executing " + method.getName() + " request to:"
                    + client.getHostConfiguration().getHostURL() + " path: " + method.getPath() + " byte body: "
                    + trimmedMethodBody, e);
        }
    }

    @Override
    public String toString() {
        return "ApacheHttpClient31BackedHttpClient{" + "client=" + client + ", headersForEveryRequest="
                + headersForEveryRequest + ", consumerKey=" + consumerKey + ", consumerSecret=" + consumerSecret
                + ", isOauthEnabled=" + isOauthEnabled + ", isSSLEnabled=" + isSSLEnabled + '}';
    }

    private HttpResponse executeMethod(HttpMethodBase method, Map<String, String> headers, int timeoutMillis)
            throws HttpClientException {
        setRequestHeaders(headers, method);

        if (timeoutMillis > 0) {
            return executeWithTimeout(method, timeoutMillis);
        }
        try {
            return execute(method);
        } catch (Exception e) {
            throw new HttpClientException("Error executing " + method.getName() + " request to: "
                    + client.getHostConfiguration().getHostURL() + " path: " + method.getPath(), e);
        }
    }

    private HttpStreamResponse executeMethodStream(HttpMethodBase method, int timeoutMillis)
            throws HttpClientException {
        try {
            return executeStreamWithTimeout(method, timeoutMillis);
        } catch (Exception e) {
            throw new HttpClientException("Error executing " + method.getName() + " request to: "
                    + client.getHostConfiguration().getHostURL() + " path: " + method.getPath(), e);
        }
    }

    private HttpResponse executeWithTimeout(final HttpMethodBase httpMethod, int timeoutMillis) {
        client.getParams().setParameter("http.method.retry-handler", new DefaultHttpMethodRetryHandler(0, false));
        ExecutorService service = Executors.newSingleThreadExecutor();

        Future<HttpResponse> future = service.submit(new Callable<HttpResponse>() {
            @Override
            public HttpResponse call() throws IOException {
                return execute(httpMethod);
            }
        });

        try {
            return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            String uriInfo = "";
            try {
                uriInfo = " for " + httpMethod.getURI();
            } catch (Exception ie) {
            }
            LOG.warn("Http connection thread was interrupted or has timed out" + uriInfo, e);
            return new HttpResponse(HttpStatus.SC_REQUEST_TIMEOUT, "Request Timeout", null);
        } finally {
            service.shutdownNow();
        }
    }

    private HttpResponse execute(HttpMethod method) throws IOException {
        if (isOauthEnabled) {
            signWithOAuth(method);
        }

        applyHeadersCommonToAllRequests(method);

        byte[] responseBody;
        StatusLine statusLine = null;
        LOG.startTimer(TIMER_NAME);
        try {

            client.executeMethod(method);

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            InputStream responseBodyAsStream = method.getResponseBodyAsStream();
            if (responseBodyAsStream != null) {
                IOUtils.copy(responseBodyAsStream, outputStream);
            }

            responseBody = outputStream.toByteArray();
            statusLine = method.getStatusLine();

            return new HttpResponse(statusLine.getStatusCode(), statusLine.getReasonPhrase(), responseBody);

        } finally {
            method.releaseConnection();
            LOG.stopTimer(TIMER_NAME);
        }
    }

    private HttpStreamResponse executeStreamWithTimeout(final HttpMethodBase HttpMethod, int timeoutMillis) {
        client.getParams().setParameter("http.method.retry-handler", new DefaultHttpMethodRetryHandler(0, false));
        ExecutorService service = Executors.newSingleThreadExecutor();

        Future<HttpStreamResponse> future = service.submit(new Callable<HttpStreamResponse>() {
            @Override
            public HttpStreamResponse call() throws IOException {
                return executeStream(HttpMethod);
            }
        });

        try {
            return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            String uriInfo = "";
            try {
                uriInfo = " for " + HttpMethod.getURI();
            } catch (Exception ie) {
            }
            LOG.warn("Http connection thread was interrupted or has timed out" + uriInfo, e);
            return null;
        } finally {
            service.shutdownNow();
        }
    }

    private HttpStreamResponse executeStream(HttpMethod method) throws IOException {
        if (isOauthEnabled) {
            signWithOAuth(method);
        }

        applyHeadersCommonToAllRequests(method);

        LOG.startTimer(TIMER_NAME);
        try {
            int status = client.executeMethod(method);

            checkStreamStatus(status, method);

            return createStreamResponse(method);
        } catch (Exception e) {
            throw new IOException("Failed to get stream", e);
        } finally {
            LOG.stopTimer(TIMER_NAME);
        }
    }

    private void checkStreamStatus(int status, HttpMethod httpMethod) throws HttpClientException {
        LOG.debug(String.format("Got status: %s %s", status, httpMethod.getStatusText()));
        if (status != 200 && status != 201) {
            try {
                String responseBodyAsString = httpMethod.getResponseBodyAsString();
                if (!StringUtils.isEmpty(responseBodyAsString)) {
                    responseBodyAsString = new String(responseBodyAsString.getBytes(), "UTF-8");
                    throw new HttpClientException(
                            "Bad status : " + httpMethod.getStatusText() + ":\n" + responseBodyAsString);
                } else {
                    throw new HttpClientException("Bad status : " + httpMethod.getStatusLine());
                }
            } catch (Exception e) {
                throw new HttpClientException("Bad status : " + status + ". Could not read response body.");
            }
        }
    }

    private void signWithOAuth(HttpMethod method) throws IOException {
        try {
            HostConfiguration hostConfiguration = client.getHostConfiguration();

            URI uri = method.getURI();
            URI newUri = new URI((isSSLEnabled) ? "https" : "http", uri.getUserinfo(), hostConfiguration.getHost(),
                    hostConfiguration.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());

            method.setURI(newUri);

            //URI checkUri = method.getURI();
            //String checkUriString = checkUri.toString();
            CommonsHttp3OAuthConsumer oAuthConsumer = new CommonsHttp3OAuthConsumer(consumerKey, consumerSecret);
            oAuthConsumer.setTokenWithSecret(consumerKey, consumerSecret);
            oAuthConsumer.sign(method);
        } catch (Exception e) {
            throw new IOException("Failed to OAuth sign HTTPRequest", e);
        }
    }

    private void setRequestHeaders(Map<String, String> headers, HttpMethodBase method) {
        if (headers != null) {
            for (Map.Entry<String, String> header : headers.entrySet()) {
                method.setRequestHeader(header.getKey(), header.getValue());
            }
        }
    }

    private void applyHeadersCommonToAllRequests(HttpMethod method) {
        for (Map.Entry<String, String> headerEntry : headersForEveryRequest.entrySet()) {
            method.addRequestHeader(headerEntry.getKey(), headerEntry.getValue());
        }
    }

    public void setConsumerTokens(String consumerKey, String consumerSecret) {
        if (StringUtils.isEmpty(consumerKey)) {
            throw new IllegalArgumentException("consumerKey cannot be empty or null.");
        }
        if (StringUtils.isEmpty(consumerSecret)) {
            throw new IllegalArgumentException("consumerSecret cannot be empty or null.");
        }
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecret;
        isOauthEnabled = true;

    }

    public boolean isOAuthEnabled() {
        return isOauthEnabled;
    }

    // I hate this! This is a hack to get it working in time!
    public void setUsingSSL() {
        isSSLEnabled = true;
    }

    // package scope for testing ...
    OAuthConsumer getConsumer() {
        if (isOauthEnabled) {
            return new CommonsHttp3OAuthConsumer(consumerKey, consumerSecret);
        }
        return null;
    }

    void setState(HttpState state) {
        client.setState(state);
    }

    void setHostConfiguration(HostConfiguration hostConfiguration) {
        client.setHostConfiguration(hostConfiguration);
    }

    private String safeHostString(HostConfiguration hostConfiguration) {
        if (hostConfiguration.getHost() != null) {
            return hostConfiguration.getHostURL();
        }
        return "";
    }

    private HttpStreamResponse createStreamResponse(HttpMethod method) throws IOException {

        StatusLine statusLine = method.getStatusLine();
        String filename = getFileName(method);
        long length = getContentLength(method);
        String contentType = getContentType(method);

        HttpStreamResponse streamResponse = new HttpStreamResponse(statusLine.getStatusCode(),
                statusLine.getReasonPhrase(), method.getResponseBodyAsStream(), filename, contentType, length);
        return streamResponse;
    }

    /*
     helper methods to get data from headers
     */
    private String getFileName(HttpMethod method) {
        String filename;
        filename = GetFileNameFromContentDisposition(method);
        if (StringUtils.isNotEmpty(filename)) {
            return filename;
        } else {
            filename = getFileNameFromURL(method);
        }
        return filename;

    }

    private String GetFileNameFromContentDisposition(HttpMethod method) {
        String filename = "";
        ContentDisposition contentDisposition;
        Header[] contentDispositionHeader = method.getResponseHeaders("Content-Disposition");
        if (contentDispositionHeader != null && contentDispositionHeader.length == 1
                && contentDispositionHeader[0] != null && contentDispositionHeader[0].getValue() != null) {
            try {
                contentDisposition = new ContentDisposition(contentDispositionHeader[0].getValue());
                filename = contentDisposition.getFileName();
            } catch (java.text.ParseException e) {
                LOG.warn("cant parse content disposition", e);
            }
        }
        return filename;
    }

    private String getFileNameFromURL(HttpMethod method) {
        String filename = "";
        try {
            String baseName = FilenameUtils.getBaseName(method.getURI().getURI());
            String extension = FilenameUtils.getExtension(method.getURI().getURI());
            extension = extension.substring(0,
                    extension.indexOf("&") < 0 ? extension.length() : extension.indexOf("&"));
            extension = extension.substring(0,
                    extension.indexOf("?") < 0 ? extension.length() : extension.indexOf("?"));
            if (StringUtils.isNotEmpty(baseName) && StringUtils.isNotEmpty(extension)) {
                filename = baseName + "." + extension;
            }
        } catch (URIException e) {
            LOG.warn("cant parse url for getting filename", e);
        }
        return filename;
    }

    private long getContentLength(HttpMethod method) {
        long size = -1;
        Header[] contentLengthHeader = method.getResponseHeaders("Content-Length");
        if (contentLengthHeader != null && contentLengthHeader.length == 1 && contentLengthHeader[0] != null
                && contentLengthHeader[0].getValue() != null) {
            try {
                String contentLength = contentLengthHeader[0].getValue();
                size = Long.parseLong(contentLength);
            } catch (NumberFormatException e) {
                LOG.warn("cant parse content Content-Length", e);
            }
        }
        return size;
    }

    private String getContentType(HttpMethod method) {
        String contentType = "application/octet-stream";
        Header[] contentTypeHeaders = method.getResponseHeaders("Content-Type");
        if (contentTypeHeaders != null && contentTypeHeaders.length == 1 && contentTypeHeaders[0] != null
                && contentTypeHeaders[0].getValue() != null) {
            contentType = contentTypeHeaders[0].getValue();
        }
        return contentType;
    }
}