anhttpclient.impl.DefaultWebBrowser.java Source code

Java tutorial

Introduction

Here is the source code for anhttpclient.impl.DefaultWebBrowser.java

Source

/*
 * Copyright (c) 2011 Sergey Prilukin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package anhttpclient.impl;

import anhttpclient.HttpConstants;
import anhttpclient.WebBrowser;
import anhttpclient.WebRequest;
import anhttpclient.WebResponse;
import anhttpclient.EntityEnclosingWebRequest;
import anhttpclient.impl.request.HttpGetWebRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
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.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
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.conn.params.ConnRoutePNames;
import org.apache.http.cookie.Cookie;
import org.apache.http.cookie.params.CookieSpecPNames;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.GZIPInputStream;

/**
 * Default implementation of {@link anhttpclient.WebBrowser}
 * Has such configurable parameters:
 * - default headers    : Collection of HTTP headers which will be sent with every http request
 * - retry count        : count of repeating http request if previous one was unsuccessful
 * - connection timeout : time in milliseconds which determine connection timeout of HTTP request
 * - socket timeout     : time in milliseconds which determine socket timeout of HTTP request
 *
 * @author Sergey Prilukin
 */
public class DefaultWebBrowser implements WebBrowser {
    public static final Log log = LogFactory.getLog(DefaultWebBrowser.class);

    protected HttpClient httpClient;
    protected CookieStore cookieStore = new BasicCookieStore();
    protected Map<String, String> defaultHeaders = new HashMap<String, String>();
    protected int retryCount = WebBrowserConstants.DEFAULT_RETRY_COUNT;
    protected int socketTimeout = WebBrowserConstants.DEFAULT_SOCKET_TIMEOUT;
    protected int connectionTimeout = WebBrowserConstants.DEFAULT_CONNECTION_TIMEOUT;
    protected ThreadLocal<HttpRequestBase> httpRequest = new ThreadLocal<HttpRequestBase>();

    private HttpParams httpParams;
    private String clientConnectionFactoryClassName = WebBrowserConstants.DEFAULT_CLIENT_CONNECTION_FACTORY_CLASS_NAME;
    private boolean threadSafe = false;
    private boolean initialized = false;

    static class GzipDecompressingEntity extends HttpEntityWrapper {
        public GzipDecompressingEntity(final HttpEntity entity) {
            super(entity);
        }

        @Override
        public InputStream getContent() throws IOException, IllegalStateException {
            // the wrapped entity's getContent() decides about repeatability
            InputStream wrappedin = wrappedEntity.getContent();
            return new GZIPInputStream(wrappedin);
        }

        @Override
        public long getContentLength() {
            // length of ungzipped content is not known
            return -1;
        }
    }

    /**
     * Allows to set httpClient implementation directly
     * @param httpClient instance of {@link HttpClient}
     */
    public void setHttpClient(HttpClient httpClient) {
        synchronized (this) {
            this.httpClient = httpClient;
            this.initialized = false;
        }
    }

    /**
     * Allows to set {@link HttpParams}
     * Will take effect only if httpClient is initialized inside DefaultWebBrowser
     * @param httpParams {@link HttpParams} to set.
     */
    public void setHttpParams(HttpParams httpParams) {
        this.httpParams = httpParams;
        if (this.httpParams != null
                && this.httpParams.getParameter(ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME) == null) {
            this.httpParams.setParameter(ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME,
                    clientConnectionFactoryClassName);
        }

        if (httpClient != null && httpClient instanceof DefaultHttpClient) {
            synchronized (this) {
                httpClient = null;
                this.initialized = false;
            }
        }
    }

    private HttpParams getBasicHttpParams() {
        HttpParams params = new BasicHttpParams();
        params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
        params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, HTTP.UTF_8);
        params.setParameter(CoreProtocolPNames.USER_AGENT, WebBrowserConstants.DEFAULT_USER_AGENT);
        params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout);
        params.setParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false);
        params.setParameter(ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME, clientConnectionFactoryClassName);

        /*Custom parameter to be used in implementation of {@link ClientConnectionManagerFactory}*/
        params.setParameter(ClientConnectionManagerFactoryImpl.THREAD_SAFE_CONNECTION_MANAGER, this.threadSafe);

        return params;
    }

    private void addGZIPResponseInterceptor(HttpClient httpClient) {
        if (AbstractHttpClient.class.isAssignableFrom(httpClient.getClass())) {
            ((AbstractHttpClient) httpClient).addResponseInterceptor(new HttpResponseInterceptor() {
                public void process(final HttpResponse response, final HttpContext context)
                        throws HttpException, IOException {
                    HttpEntity entity = response.getEntity();
                    if (entity == null) {
                        return;
                    }

                    Header contentEncodingHeader = entity.getContentEncoding();
                    if (contentEncodingHeader != null) {
                        HeaderElement[] codecs = contentEncodingHeader.getElements();
                        for (HeaderElement codec : codecs) {
                            if (codec.getName().equalsIgnoreCase(HttpConstants.GZIP)) {
                                response.setEntity(new GzipDecompressingEntity(response.getEntity()));
                                return;
                            }
                        }
                    }
                }
            });
        }
    }

    /**
     * Default constructor
     * apache http client will initialized here as not thread safe http client
     */
    public DefaultWebBrowser() {
        this(false);
    }

    /**
     * Constructor which allows to specify if webBrowser should be thread Safe
     * @param threadSafe if {@code true} then webBrowser will be thread safe
     * and not thread safe - if {@code false}
     */
    public DefaultWebBrowser(boolean threadSafe) {
        this.threadSafe = threadSafe;
    }

    /**
     * Initialize new instance of httpClient
     */
    private void initHttpClient() {
        if (!this.initialized) {
            synchronized (this) {
                if (!this.initialized) {
                    if (httpClient == null) {
                        if (httpParams == null) {
                            httpParams = getBasicHttpParams();
                        }

                        httpClient = new DefaultHttpClient(null, getBasicHttpParams());
                        addGZIPResponseInterceptor(httpClient);
                    }

                    this.initialized = true;
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void setDefaultHeaders(final Map<String, String> defaultHeaders) {
        this.defaultHeaders.clear();
        for (Map.Entry<String, String> entryObject : defaultHeaders.entrySet()) {
            String headerName = entryObject.getKey();
            String headerValue = entryObject.getValue();
            this.addHeader(headerName, headerValue);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void setDefaultHeaders(Properties defaultHeaders) {
        this.defaultHeaders.clear();
        for (Map.Entry<Object, Object> entry : defaultHeaders.entrySet()) {
            String headerName = String.valueOf(entry.getKey());
            String headerValue = String.valueOf(entry.getValue());
            this.addHeader(headerName, headerValue);
        }
    }

    /**
     * Makes default initialization of HttpMethodBase before any request
     * such as cookie policy, retrycount, timeout
     *
     * @param httpMethodBase {@link HttpRequestBase} for making default initialization
     */
    private void setDefaultMethodParams(final HttpRequestBase httpMethodBase) {
        httpMethodBase.getParams().setBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, true);
        httpMethodBase.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);

        // We use here DefaultHttpMethodRetryHandler with <b>true</b> parameter
        // because we suppose that if method was successfully sent its headers
        // it could also be retried
        if (AbstractHttpClient.class.isAssignableFrom(httpClient.getClass())) {
            ((AbstractHttpClient) httpClient)
                    .setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, true));
        }
        httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, socketTimeout);
    }

    /**
     * Add default headers to web request and
     * then add specific headers for current request
     *
     * @param httpMethodBase http method for adding headers
     * @param methodHeaders  headers specific for current request
     */
    private void setHeaders(final HttpRequestBase httpMethodBase, final Map<String, String> methodHeaders) {

        //set default headers
        for (Map.Entry<String, String> entry : defaultHeaders.entrySet()) {
            httpMethodBase.setHeader(entry.getKey(), entry.getValue());
        }

        //set method headers
        for (Map.Entry<String, String> entry : methodHeaders.entrySet()) {
            httpMethodBase.setHeader(entry.getKey(), entry.getValue());
        }
    }

    /**
     * Execute specified request
     *
     * @param httpUriRequest request to execute
     * @return code of http response
     * @throws java.io.IOException if errors occurs during request
     * @throws java.net.SocketTimeoutException if timeout occurs see params
     *          settings in {@link #setDefaultMethodParams} method
     */
    private HttpResponse executeMethod(HttpUriRequest httpUriRequest) throws IOException {
        if (log.isDebugEnabled()) {
            for (Header header : httpUriRequest.getAllHeaders()) {
                log.debug(String.format("ANHTTPCLIENT. Request header: [%s: %s]", header.getName(),
                        header.getValue()));
            }
        }

        HttpContext localContext = new BasicHttpContext();
        localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
        return httpClient.execute(httpUriRequest, localContext);
    }

    /**
     * {@inheritDoc}
     */
    public WebResponse getResponse(String url) throws IOException {
        return getResponse(url, null);
    }

    public WebResponse getResponse(String url, String expectedResponseCharset) throws IOException {
        WebRequest req = new HttpGetWebRequest(url);

        return getResponse(req, expectedResponseCharset);
    }

    /**
     * Return {@link HttpRequestBase} from WebRequest which is
     * shell on http GET or DELETE request
     *
     * @param webRequest shell under http GET request
     * @param httpRequest HttpRequestBase instance
     * @return HttpMethodBase for specified shell on http GET request
     */
    private HttpRequestBase populateHttpRequestBaseMethod(WebRequest webRequest, HttpRequestBase httpRequest) {
        setDefaultMethodParams(httpRequest);
        setHeaders(httpRequest, webRequest.getHeaders());

        return httpRequest;
    }

    /**
     * Return {@link HttpRequestBase} from WebRequest which is
     * shell on http POST or PUT request
     *
     * @param webRequest shell under http POST request
     * @param httpRequest HttpEntityEnclosingRequestBase instance
     * @return HttpMethodBase for specified shell on http POST request
     */
    private HttpRequestBase populateHttpEntityEnclosingRequestBaseMethod(WebRequest webRequest,
            HttpEntityEnclosingRequestBase httpRequest) {

        EntityEnclosingWebRequest webRequestWithBody = (EntityEnclosingWebRequest) webRequest;
        setDefaultMethodParams(httpRequest);
        setHeaders(httpRequest, webRequestWithBody.getHeaders());

        HttpEntity entity = null;

        if (webRequestWithBody.getFormParams() != null && webRequestWithBody.getFormParams().size() > 0) {

            StringBuilder contentType = (new StringBuilder(HttpConstants.MIME_FORM_ENCODED)).append("; charset=")
                    .append(webRequestWithBody.getFormParamsCharset());

            httpRequest.addHeader(HTTP.CONTENT_TYPE, contentType.toString());

            // data - name/value params
            List<NameValuePair> nameValuePairList = null;
            Map<String, String> requestParams = webRequestWithBody.getFormParams();
            if ((requestParams != null) && (requestParams.size() > 0)) {
                nameValuePairList = new ArrayList<NameValuePair>();
                for (Map.Entry<String, String> entry : requestParams.entrySet()) {
                    nameValuePairList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
            }

            if (nameValuePairList != null) {
                try {
                    entity = new UrlEncodedFormEntity(nameValuePairList, HTTP.UTF_8);
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
            }
        } else if (webRequestWithBody.getParts().size() > 0) {
            entity = new MultipartEntity();
            for (Map.Entry<String, ContentBody> entry : webRequestWithBody.getParts().entrySet()) {
                ((MultipartEntity) entity).addPart(entry.getKey(), entry.getValue());
            }
        }

        if (entity != null) {
            httpRequest.setEntity(entity);
        }

        return httpRequest;
    }

    /**
     * If {@link HttpRequestBase} httpMethodBase has :location: header in response headers then
     * redirect will be perfirmed
     *
     * @param response {@link org.apache.http.HttpResponse} which will be wrapped into {@link WebResponse}
     * @param httpMethodBase {@link HttpRequestBase} with original request
     * @param charset charset of response text content
     * @return web response which is not needed in redirects
     * @throws java.io.IOException if errors occured during executing redirect
     */
    private WebResponse processResponse(HttpResponse response, HttpRequestBase httpMethodBase, String charset)
            throws IOException {
        if (log.isDebugEnabled()) {
            for (Header header : response.getAllHeaders()) {
                log.debug(String.format("ANHTTPCLIENT. Response header: [%s: %s]", header.getName(),
                        header.getValue()));
            }
        }

        return new HttpWebResponse(response, httpMethodBase, charset);
    }

    /**
     * {@inheritDoc}
     */
    public WebResponse getResponse(WebRequest webRequest) throws IOException {
        return getResponse(webRequest, null);
    }

    /**
     * {@inheritDoc}
     */
    public WebResponse getResponse(WebRequest webRequest, String charset) throws IOException {
        initHttpClient();

        switch (webRequest.getRequestMethod()) {
        case GET:
            httpRequest.set(populateHttpRequestBaseMethod(webRequest, new HttpGet(webRequest.getUrl())));
            break;
        case HEAD:
            httpRequest.set(populateHttpRequestBaseMethod(webRequest, new HttpHead(webRequest.getUrl())));
            break;
        case OPTIONS:
            httpRequest.set(populateHttpRequestBaseMethod(webRequest, new HttpOptions(webRequest.getUrl())));
            break;
        case TRACE:
            httpRequest.set(populateHttpRequestBaseMethod(webRequest, new HttpTrace(webRequest.getUrl())));
            break;
        case DELETE:
            httpRequest.set(populateHttpRequestBaseMethod(webRequest, new HttpDelete(webRequest.getUrl())));
            break;
        case POST:
            httpRequest.set(
                    populateHttpEntityEnclosingRequestBaseMethod(webRequest, new HttpPost(webRequest.getUrl())));
            break;
        case PUT:
            httpRequest.set(
                    populateHttpEntityEnclosingRequestBaseMethod(webRequest, new HttpPut(webRequest.getUrl())));
            break;
        default:
            throw new RuntimeException("Method not yet supported: " + webRequest.getRequestMethod());
        }

        WebResponse resp;

        HttpResponse response = executeMethod(httpRequest.get());
        if (response == null) {
            throw new IOException(
                    "ANHTTPCLIENT. An empty response received from server. Possible reason: host is offline");
        }

        resp = processResponse(response, httpRequest.get(), charset);
        httpRequest.set(null);
        return resp;
    }

    /**
     * {@inheritDoc}
     */
    public Map<String, String> getHeaders() {
        return Collections.unmodifiableMap(defaultHeaders);
    }

    /**
     * {@inheritDoc}
     */
    public String getHeader(String headerName) {
        for (Map.Entry<String, String> entry : defaultHeaders.entrySet()) {
            if (entry.getKey().equalsIgnoreCase(headerName)) {
                return entry.getValue();
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public void addHeaders(Map<String, String> headers) {
        defaultHeaders.putAll(headers);
    }

    /**
     * {@inheritDoc}
     */
    public void addHeader(String name, String value) {
        defaultHeaders.put(name, value);
    }

    /**
     * {@inheritDoc}
     */
    public Integer getRetryCount() {
        return retryCount;
    }

    /**
     * {@inheritDoc}
     */
    public void setRetryCount(Integer retryCount) {
        this.retryCount = retryCount;
    }

    /**
     * {@inheritDoc}
     */
    public Integer getSocketTimeout() {
        return socketTimeout;
    }

    /**
     * {@inheritDoc}
     */
    public void setSocketTimeout(Integer socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

    /**
     * {@inheritDoc}
     */
    public Integer getConnectionTimeout() {
        return connectionTimeout;
    }

    /**
     * {@inheritDoc}
     */
    public void setConnectionTimeout(Integer connectionTimeout) {
        initHttpClient();
        this.connectionTimeout = connectionTimeout;
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout);
    }

    /**
     * {@inheritDoc}
     */
    public List<Cookie> getCookies() {
        return cookieStore.getCookies();
    }

    public Cookie getCookieByName(String name) {
        for (Cookie cookie : cookieStore.getCookies()) {
            if (cookie.getName().equals(name)) {
                return cookie;
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public void addCookie(Cookie cookie) {
        cookieStore.addCookie(cookie);
    }

    /**
     * {@inheritDoc}
     */
    public void addCookies(List<Cookie> cookies) {
        for (Cookie cookie : cookies) {
            BasicClientCookie apacheCookie = new BasicClientCookie(cookie.getName(), cookie.getValue());
            apacheCookie.setPath(cookie.getPath());
            apacheCookie.setDomain(cookie.getDomain());
            cookieStore.addCookie(apacheCookie);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void clearAllCookies() {
        cookieStore.clear();
    }

    /**
     * {@inheritDoc}
     */
    public void setProxy(String url, int port) {
        initHttpClient();
        HttpHost proxy = new HttpHost(url, port);
        httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
    }

    /**
     * {@inheritDoc}
     */
    public void clearProxy() {
        initHttpClient();
        httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, null);
    }

    /**
     * {@inheritDoc}
     */
    public void abort() {
        if (httpRequest.get() != null && !httpRequest.get().isAborted()) {
            httpRequest.get().abort();
        }
    }
}