net.paissad.minus.utils.HttpClientUtils.java Source code

Java tutorial

Introduction

Here is the source code for net.paissad.minus.utils.HttpClientUtils.java

Source

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

package net.paissad.minus.utils;

import static net.paissad.minus.AppConstants.APP_USER_AGENT;
import static net.paissad.minus.MinusConstants.MINUS_COOKIE_NAME;
import static net.paissad.minus.MinusConstants.MINUS_DOMAIN_NAME;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.SSLHandshakeException;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie2;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import net.paissad.minus.api.MinusHttpResponse;
import net.paissad.minus.exception.MinusException;

/**
 * @author Papa Issa DIAKHATE (paissad)
 * 
 */
public class HttpClientUtils {

    public static enum RequestType {
        GET, POST,
    }

    public static enum ExpectedResponseType {
        STRING, HTTP_ENTITY;
    }

    /** This class should not be instantiated. */
    private HttpClientUtils() {
    }

    /**
     * Sends a HTTP GET request.
     * 
     * @param baseURL - The base URL of the request.
     * @param parametersBody - The parameters of the request.
     * @param sessionId _ The session id to re-use. If <code>null</code> or
     *            empty, then let the server give a new session id.
     * @param additionalRequestHeaders
     * @param expectedResponseType
     * @return An instance of {@link MinusHttpResponse}
     * @throws MinusException
     */
    public static MinusHttpResponse doGet(final String baseURL, final Map<String, String> parametersBody,
            final String sessionId, final Header[] additionalRequestHeaders,
            final ExpectedResponseType expectedResponseType) throws MinusException {

        return sendRequest(baseURL, parametersBody, sessionId, RequestType.GET, additionalRequestHeaders,
                expectedResponseType);
    }

    /**
     * Sends a HTTP POST request.
     * 
     * @param baseURL - The base URL of the request.
     * @param parametersBody - The parameters of the request.
     * @param sessionId _ The session id to re-use. If <code>null</code> or
     *            empty, then let the server give a new session id.
     * @param additionalRequestHeaders
     * @param expectedResponseType
     * @return An instance of {@link MinusHttpResponse}
     * @throws MinusException
     */
    public static MinusHttpResponse doPost(final String baseURL, final Map<String, String> parametersBody,
            final String sessionId, final Header[] additionalRequestHeaders,
            final ExpectedResponseType expectedResponseType) throws MinusException {

        return sendRequest(baseURL, parametersBody, sessionId, RequestType.POST, additionalRequestHeaders,
                expectedResponseType);
    }

    // _________________________________________________________________________

    /**
     * Convenience method for sending HTTP requests.
     * <b><span style="color:red">Note</span></b>: This method is intended to be
     * used internally, not by end-users.
     * 
     * @param baseURL - <b>Example</b>: http://minus.com/api
     * @param parametersBody - The parameters (name => value pairs) to pass to
     *            the request.
     * @param sessionId - If <tt>null</tt> or empty, then create and use a new
     *            session, otherwise, use the specified session_id (which is
     *            stored in a cookie).
     * @param requestType
     * @param additionalRequestHeaders -
     * @param expectedResponseType
     * @return The response retrieved from Minus API.
     * @throws MinusException
     */
    private static MinusHttpResponse sendRequest(final String baseURL, final Map<String, String> parametersBody,
            final String sessionId, final RequestType requestType, final Header[] additionalRequestHeaders,
            final ExpectedResponseType expectedResponseType) throws MinusException {

        DefaultHttpClient client = null;
        HttpRequestBase request = null;
        InputStream responseContent = null;
        boolean errorOccured = false;

        try {
            if (requestType == RequestType.GET) {
                request = new HttpGet(baseURL);
                if (parametersBody != null && !parametersBody.isEmpty()) {
                    request = appendParametersToRequest(request, parametersBody);
                }

            } else if (requestType == RequestType.POST) {
                UrlEncodedFormEntity encodedEntity = new UrlEncodedFormEntity(getHttpParamsFromMap(parametersBody),
                        HTTP.UTF_8);
                request = new HttpPost(baseURL);
                ((HttpPost) request).setEntity(encodedEntity);

            } else {
                throw new MinusException("The method (" + requestType + ") is unknown, weird ...");
            }

            request.addHeader(new BasicHeader("User-Agent", APP_USER_AGENT));
            if (additionalRequestHeaders != null && additionalRequestHeaders.length > 0) {
                for (Header aHeader : additionalRequestHeaders) {
                    request.addHeader(aHeader);
                }
            }

            client = new DefaultHttpClient();
            client.setHttpRequestRetryHandler(new MinusHttpRequestRetryHandler());

            client.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, true);
            client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);

            CookieStore cookieStore = new BasicCookieStore();

            Cookie sessionCookie = null;
            if (sessionId != null && !sessionId.trim().isEmpty()) {
                sessionCookie = new BasicClientCookie2(MINUS_COOKIE_NAME, sessionId);
                ((BasicClientCookie2) sessionCookie).setPath("/");
                ((BasicClientCookie2) sessionCookie).setDomain(MINUS_DOMAIN_NAME);
                ((BasicClientCookie2) sessionCookie).setVersion(0);
                cookieStore.addCookie(sessionCookie);
            }

            client.setCookieStore(cookieStore);

            HttpContext localContext = new BasicHttpContext();
            // Bind custom cookie store to the local context
            localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);

            // Execute the request ... pass local context as a parameter
            HttpResponse resp = client.execute(request, localContext);

            // Let's update the cookie have the name 'sessionid'
            for (Cookie aCookie : client.getCookieStore().getCookies()) {
                if (aCookie.getName().equals(MINUS_COOKIE_NAME)) {
                    sessionCookie = aCookie;
                    break;
                }
            }

            Object result = null;
            int statusCode = resp.getStatusLine().getStatusCode();

            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity entity = resp.getEntity();
                if (entity != null) {
                    if (expectedResponseType == ExpectedResponseType.STRING) {
                        result = EntityUtils.toString(entity);
                        EntityUtils.consume(entity);
                    } else if (expectedResponseType == ExpectedResponseType.HTTP_ENTITY) {
                        result = entity;
                    }
                }
            } else {
                // The response code is not OK.
                StringBuilder errMsg = new StringBuilder();
                errMsg.append("HTTP ").append(requestType).append(" failed => ").append(resp.getStatusLine());
                if (request != null) {
                    errMsg.append(" : ").append(request.getURI());
                }
                throw new MinusException(errMsg.toString());
            }

            return new MinusHttpResponse(result, sessionCookie);

        } catch (Exception e) {
            errorOccured = true;
            if (request != null) {
                request.abort();
            }
            String errMsg = "Error while executing the HTTP " + requestType + " request : " + e.getMessage();
            throw new MinusException(errMsg, e);

        } finally {
            if (client != null) {
                // We must not close the client is the expected response is an
                // InputStream. Indeed, if ever we close the client, we won't be
                // able to read the response because of SocketException.
                if (errorOccured) {
                    client.getConnectionManager().shutdown();
                } else if (expectedResponseType != ExpectedResponseType.HTTP_ENTITY) {
                    client.getConnectionManager().shutdown();
                }
            }
            CommonUtils.closeAllStreamsQuietly(responseContent);
        }
    }

    // _________________________________________________________________________

    /**
     * 
     * @param baseURL
     * @param parametersBody
     * @param sessionId
     * @param fileToUpload - The file to upload.
     * @param filename - The name of the file to use during the upload process.
     * @return The response received from the Minus API.
     * @throws MinusException
     */
    public static MinusHttpResponse doUpload(final String baseURL, final Map<String, String> parametersBody,
            final String sessionId, final File fileToUpload, final String filename) throws MinusException {

        DefaultHttpClient client = null;
        HttpPost uploadRequest = null;
        InputStream responseContent = null;

        try {
            String url = baseURL;
            if (parametersBody != null && !parametersBody.isEmpty()) {
                url += CommonUtils.encodeParams(parametersBody);
            }
            uploadRequest = new HttpPost(url);
            client = new DefaultHttpClient();
            client.setHttpRequestRetryHandler(new MinusHttpRequestRetryHandler());

            FileEntity fileEntity = new FileEntity(fileToUpload, "application/octet-stream");
            uploadRequest.setEntity(fileEntity);

            // We add this headers as specified by the Minus.com API during file
            // upload.
            uploadRequest.addHeader("Content-Disposition", "attachment; filename=a.bin");
            uploadRequest.addHeader("Content-Type", "application/octet-stream");

            client.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, true);
            client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);

            CookieStore cookieStore = new BasicCookieStore();

            Cookie sessionCookie = null;
            if (sessionId != null && !sessionId.trim().isEmpty()) {
                sessionCookie = new BasicClientCookie2(MINUS_COOKIE_NAME, sessionId);
                ((BasicClientCookie2) sessionCookie).setPath("/");
                ((BasicClientCookie2) sessionCookie).setDomain(MINUS_DOMAIN_NAME);
                ((BasicClientCookie2) sessionCookie).setVersion(0);
                cookieStore.addCookie(sessionCookie);
            }

            client.setCookieStore(cookieStore);
            HttpContext localContext = new BasicHttpContext();
            localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);

            HttpResponse httpResponse = client.execute(uploadRequest);

            // Let's update the cookie have the name 'sessionid'
            for (Cookie aCookie : client.getCookieStore().getCookies()) {
                if (aCookie.getName().equals(MINUS_COOKIE_NAME)) {
                    sessionCookie = aCookie;
                    break;
                }
            }

            StringBuilder result = new StringBuilder();
            int statusCode = httpResponse.getStatusLine().getStatusCode();

            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity respEntity = httpResponse.getEntity();
                if (respEntity != null) {
                    result.append(EntityUtils.toString(respEntity));
                    EntityUtils.consume(respEntity);
                }

            } else {
                // The response code is not OK.
                StringBuilder errMsg = new StringBuilder();
                errMsg.append(" Upload failed => ").append(httpResponse.getStatusLine());
                if (uploadRequest != null) {
                    errMsg.append(" : ").append(uploadRequest.getURI());
                }
                throw new MinusException(errMsg.toString());
            }

            return new MinusHttpResponse(result.toString(), sessionCookie);

        } catch (Exception e) {
            if (uploadRequest != null) {
                uploadRequest.abort();
            }
            String errMsg = "Error while uploading file (" + fileToUpload + ") : " + e.getMessage();
            throw new MinusException(errMsg, e);

        } finally {
            if (client != null) {
                client.getConnectionManager().shutdown();
            }
            CommonUtils.closeAllStreamsQuietly(responseContent);
        }
    }

    // _________________________________________________________________________

    private static List<NameValuePair> getHttpParamsFromMap(final Map<String, String> parametersBody) {

        List<NameValuePair> params = new ArrayList<NameValuePair>();
        if (parametersBody != null && !parametersBody.isEmpty()) {
            Iterator<Entry<String, String>> iter = parametersBody.entrySet().iterator();
            while (iter.hasNext()) {
                Entry<String, String> o = iter.next();
                params.add(new BasicNameValuePair(o.getKey(), o.getValue()));
            }
        }
        return params;
    }

    // _________________________________________________________________________

    /**
     * Adds/appends the content of the map to the current parameters of the
     * specified {@link HttpRequestBase}.
     * 
     * @param request - The request for which we want to add new parameters.
     * @param parametersBody - The parameters to add.
     */
    private static HttpRequestBase appendParametersToRequest(HttpRequestBase request,
            final Map<String, String> parametersBody) {

        Iterator<Entry<String, String>> iter = parametersBody.entrySet().iterator();
        while (iter.hasNext()) {
            Entry<String, String> o = iter.next();
            request.getParams().setParameter(o.getKey(), o.getValue());
        }
        return request;
    }

    // _________________________________________________________________________

    // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html#d4e292
    @NotThreadSafe
    private static class MinusHttpRequestRetryHandler implements HttpRequestRetryHandler {

        private static final int MAX_RETRIES = 5;

        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            if (executionCount >= MAX_RETRIES) {
                return false; // Do not retry if over max retry count
            }
            if (exception instanceof NoHttpResponseException) {
                return true; // Retry if the server dropped connection on us
            }
            if (exception instanceof SSLHandshakeException) {
                return false; // Do not retry on SSL handshake exception
            }
            HttpRequest request = (HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                return true; // Retry if the request is considered idempotent
            }
            return false;
        }
    }

    // _________________________________________________________________________

}