eu.eubrazilcc.lvl.core.http.client.HttpClientProvider.java Source code

Java tutorial

Introduction

Here is the source code for eu.eubrazilcc.lvl.core.http.client.HttpClientProvider.java

Source

/*
 * Copyright 2014 EUBrazilCC (EU?Brazil Cloud Connect)
 * 
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by 
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * 
 *   http://ec.europa.eu/idabc/eupl
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and 
 * limitations under the Licence.
 * 
 * This product combines work with different licenses. See the "NOTICE" text
 * file for details on the various modules and licenses.
 * The "NOTICE" text file is part of the distribution. Any derivative works
 * that you distribute must include a readable copy of the "NOTICE" text file.
 */

package eu.eubrazilcc.lvl.core.http.client;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.io.Files.asByteSink;
import static java.util.Collections.synchronizedList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trimToNull;
import static org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM;
import static org.apache.http.entity.ContentType.TEXT_XML;
import static org.apache.http.entity.ContentType.getOrDefault;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.http.Consts;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
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.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;

import com.google.common.io.ByteSink;

/**
 * Provides two classes of HTTP clients: a) a client that reuses connections from a pool; and b) a client isolated from the pool.
 * Regardless of the client, HTTP/1.1 protocol version is used.
 * @author Erik Torres <ertorser@upv.es>
 */
public class HttpClientProvider implements AutoCloseable {

    private final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
    private final ConnectionKeepAliveStrategy keepAliveStrategy = new ConnectionKeepAliveStrategy() {
        public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
            // honor 'keep-alive' header
            final HeaderElementIterator it = new BasicHeaderElementIterator(
                    response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                final HeaderElement he = it.nextElement();
                final String param = he.getName();
                final String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    try {
                        return Long.parseLong(value) * 1000l;
                    } catch (NumberFormatException ignore) {
                    }
                }
            }
            // otherwise keep alive for 30 seconds
            return 30l * 1000l;
        }
    };
    private final CloseableHttpClient httpClient;
    private final List<Http1_1Request> requests = synchronizedList(new ArrayList<Http1_1Request>());

    private HttpClientProvider() {
        final ConnectionConfig connConfig = ConnectionConfig.custom().setCharset(Consts.UTF_8).build();
        connManager.setDefaultConnectionConfig(connConfig);
        connManager.setMaxTotal(100);
        connManager.setDefaultMaxPerRoute(10);
        httpClient = HttpClients.custom().setConnectionManager(connManager).setKeepAliveStrategy(keepAliveStrategy)
                .evictExpiredConnections().evictIdleConnections(30l, TimeUnit.SECONDS).build();
    }

    protected CloseableHttpClient getHttpClient() {
        return httpClient;
    }

    protected CloseableHttpClient getIsolatedHttpClient() {
        return HttpClients.custom().setKeepAliveStrategy(keepAliveStrategy).evictExpiredConnections()
                .evictIdleConnections(30l, TimeUnit.SECONDS).build();
    }

    public Http1_1Request request(final String uri) {
        final Http1_1Request request = new Http1_1Request(uri, this);
        requests.add(request);
        return request;
    }

    public void abort(final Http1_1Request request) {
        requests.remove(request);
    }

    public void dispose(final Http1_1Request request) {
        requests.remove(request);
    }

    public static HttpClientProvider create() {
        return new HttpClientProvider();
    }

    @Override
    public void close() throws Exception {
        if (httpClient != null) {
            httpClient.close();
        }
    }

    /**
     * HTTP/1.1 request.
     * @author Erik Torres <ertorser@upv.es>
     */
    public static class Http1_1Request {

        private final URI uri;
        private String method;
        private Iterable<? extends NameValuePair> formParams;

        private final HttpClientProvider pool;
        private final RequestConfig.Builder configBuilder = RequestConfig.custom();

        public Http1_1Request(final String uri, final HttpClientProvider manager) {
            checkArgument(isNotBlank(trimToNull(uri)), "Uninitialized or invalid URI");
            this.uri = URI.create(uri);
            this.pool = checkNotNull(manager, "Uninitialized manager");
        }

        public void abort() {
            pool.abort(this);
        }

        public void dispose() {
            pool.dispose(this);
        }

        /**
         * Prepare a GET request that uses HTTP/1.1.
         * @return a reference to this class.
         */
        public Http1_1Request get() {
            method = HttpGet.METHOD_NAME;
            this.configBuilder.setExpectContinueEnabled(false);
            return this;
        }

        /**
         * Prepare a POST request that uses HTTP/1.1 with the 'expect-continue' handshake.
         * @return a reference to this class.
         */
        public Http1_1Request post() {
            method = HttpPost.METHOD_NAME;
            this.configBuilder.setExpectContinueEnabled(true);
            return this;
        }

        public Http1_1Request bodyForm(final NameValuePair... formParams) {
            return bodyForm(Arrays.asList(formParams));
        }

        public Http1_1Request bodyForm(final Iterable<? extends NameValuePair> formParams) {
            this.formParams = formParams;
            return this;
        }

        public <T> T handleResponse(final ResponseHandler<T> responseHandler, final boolean isolated)
                throws ClientProtocolException, IOException {
            try {
                final HttpUriRequest httpRequest = createHttpRequest();
                T response = null;
                if (!isolated) {
                    response = pool.getHttpClient().execute(httpRequest, responseHandler);
                } else {
                    try (final CloseableHttpClient isolatedClient = pool.getIsolatedHttpClient()) {
                        response = isolatedClient.execute(httpRequest, responseHandler);
                    }
                }
                return response;
            } finally {
                dispose();
            }
        }

        public void saveContent(final File outfile, final boolean isolated) throws IOException {
            handleResponse(new ResponseHandler<Void>() {
                @Override
                public Void handleResponse(final HttpResponse response) throws IOException {
                    final StatusLine statusLine = response.getStatusLine();
                    final HttpEntity entity = response.getEntity();
                    if (statusLine.getStatusCode() >= 300) {
                        throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
                    }
                    if (entity == null) {
                        throw new ClientProtocolException("Response contains no content");
                    }
                    final ContentType contentType = getOrDefault(entity);
                    final String mimeType = contentType.getMimeType();
                    if (!mimeType.equals(APPLICATION_OCTET_STREAM.getMimeType())
                            && !mimeType.equals(TEXT_XML.getMimeType())) {
                        throw new ClientProtocolException("Unexpected content type: " + contentType);
                    }
                    Charset charset = contentType.getCharset();
                    if (charset == null) {
                        charset = HTTP.DEF_CONTENT_CHARSET;
                    }
                    final ByteSink sink = asByteSink(outfile);
                    sink.writeFrom(entity.getContent());
                    return null;
                }
            }, isolated);
        }

        private HttpUriRequest createHttpRequest() {
            HttpUriRequest httpRequest = null;
            if (HttpGet.METHOD_NAME.equals(method)) {
                httpRequest = RequestBuilder.get(uri).setVersion(HttpVersion.HTTP_1_1).build();
            } else if (HttpPost.METHOD_NAME.equals(method)) {
                final Iterable<? extends NameValuePair> bodyForm = formParams != null ? formParams
                        : new ArrayList<NameValuePair>();
                httpRequest = RequestBuilder.post(uri).setEntity(new UrlEncodedFormEntity(bodyForm, Consts.UTF_8))
                        .setVersion(HttpVersion.HTTP_1_1).build();
            } else {
                throw new IllegalStateException("Unsupported HTTP method: " + method);
            }
            return httpRequest;
        }

    }

}