Java tutorial
/** * * jerry-http - Common Java Functionality * Copyright (c) 2012-2015, Sandeep Gupta * * http://sangupta.com/projects/jerry-http * * 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.sangupta.jerry.http; import java.io.IOException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SchemeSocketFactory; import org.apache.http.conn.ssl.SSLInitializationException; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.RedirectLocations; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import com.sangupta.jerry.util.AssertUtils; /** * Global static HTTP executor that configures and maintains the Apache * HTTP client connection managers and all to work with HTTP requests. * * @author sangupta * @since 0.3 */ public class HttpExecutor { /** * Create an HttpClient with the PoolingClientConnectionManager. * This connection manager must be used if more than one thread will * be using the HttpClient. */ private static final PoolingHttpClientConnectionManager HTTP_CONNECTION_MANAGER; /** * The singleton instance of HttpClient */ public static final HttpClient HTTP_CLIENT; /** * Build up the default instance */ static { SchemeRegistry schemeRegistry = new SchemeRegistry(); SchemeSocketFactory plain = PlainSocketFactory.getSocketFactory(); schemeRegistry.register(new Scheme("http", 80, plain)); SchemeSocketFactory ssl = null; try { ssl = SSLSocketFactory.getSystemSocketFactory(); } catch (SSLInitializationException ex) { SSLContext sslcontext; try { sslcontext = SSLContext.getInstance(SSLSocketFactory.TLS); sslcontext.init(null, new TrustManager[] { // make sure that we accept all SSL certificates new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } } }, null); ssl = new SSLSocketFactory(sslcontext); } catch (SecurityException ignore) { // do nothing } catch (KeyManagementException ignore) { // do nothing } catch (NoSuchAlgorithmException ignore) { // do nothing } } if (ssl != null) { schemeRegistry.register(new Scheme("https", 443, ssl)); } HTTP_CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(); HTTP_CONNECTION_MANAGER.setDefaultMaxPerRoute(100); HTTP_CONNECTION_MANAGER.setMaxTotal(200); CloseableHttpClient closeableHttpClient = HttpClients.custom().setConnectionManager(HTTP_CONNECTION_MANAGER) .build(); HTTP_CLIENT = new HttpRateLimitingClient(closeableHttpClient); } /** * Default {@link HttpExecutor} instance that can be used across application */ public static final HttpExecutor DEFAULT = new HttpExecutor(HTTP_CLIENT); /** * Return the underlying {@link HttpClient} instance that can be used to * make web requests. All requests shot using this client honor * rate-limiting. * * @return the enclosed {@link HttpClient} instance */ public static final HttpClient getHttpClient() { return HTTP_CLIENT; } /** * Get a new {@link HttpExecutor} instance based on the underlying * {@link HttpClient}. * * @return a new {@link HttpExecutor} instance */ public static final HttpExecutor newInstance() { return new HttpExecutor(HTTP_CLIENT); } /** * Get a new {@link HttpExecutor} instance based on given {@link HttpClient} * instance * * @param client * the {@link HttpClient} to use * * @return a new {@link HttpExecutor} instance * * @throws IllegalArgumentException * if the given {@link HttpClient} is <code>null</code> */ public static final HttpExecutor newInstance(HttpClient client) { if (client == null) { throw new IllegalArgumentException("HttpClient instance cannot be null"); } return new HttpExecutor(client); } /** * Set overall maximum connections that can be handled by the underlying * connection manager. * * @param numConnections * the number of connections to set * * @throws IllegalArgumentException * if the number of connections is less than <code>1</code> */ public static void setMaxConnections(int numConnections) { if (numConnections < 1) { throw new IllegalArgumentException("Number of connections cannot be less than 1"); } HTTP_CONNECTION_MANAGER.setMaxTotal(numConnections); } /** * Set overall maximum connections per route (over all hosts) that can be * handled by the underlying connection manager. * * @param numConnections * the number of connections to set * * @throws IllegalArgumentException * if the number of connections is less than <code>1</code> */ public static void setMaxConnectionsPerRoute(int numConnections) { if (numConnections < 1) { throw new IllegalArgumentException("Number of connections cannot be less than 1"); } HTTP_CONNECTION_MANAGER.setDefaultMaxPerRoute(numConnections); } /** * Set maximum connections that will be operated over the given route, that * will be handled by the underlying connection manager. * * @param route the {@link HttpRoute} on which to set maximum connections * * @param numConnections the number of connections to set * @throws IllegalArgumentException * if the number of connections is less than <code>ZERO</code> */ public static void setMaxConnectionsOnHost(HttpRoute route, int numConnections) { if (numConnections < 0) { throw new IllegalArgumentException("Number of connections cannot be less than 1"); } HTTP_CONNECTION_MANAGER.setMaxPerRoute(route, numConnections); } /** * Set maximum connections that will be operated over the given host on port * 80, that will be handled by the underlying connection manager. * * @param hostName * the host name for which the limit needs to be set * * @param numConnections * the number of connections to set * * @throws IllegalArgumentException * if the number of connections is less than <code>ZERO</code> * * @throws IllegalArgumentException * if the host name is <code>null</code> or empty. */ public static void setMaxConnectionsOnHost(String hostName, int numConnections) { if (AssertUtils.isEmpty(hostName)) { throw new IllegalArgumentException("Hostname cannot be null/empty"); } HttpRoute route = new HttpRoute(new HttpHost(hostName)); setMaxConnectionsOnHost(route, numConnections); } /** * Set maximum connections that will be operated over the given host on * given port, that will be handled by the underlying connection manager. * * @param hostName * the host name for which the limit needs to be set * * @param port * the port on which the limit needs to be set * * @param numConnections * the number of connections to set * * @throws IllegalArgumentException * if the number of connections is less than <code>ZERO</code> * * @throws IllegalArgumentException * if the host name is <code>null</code> or empty. */ public static void setMaxConnectionsOnHost(String hostName, int port, int numConnections) { if (AssertUtils.isEmpty(hostName)) { throw new IllegalArgumentException("Hostname cannot be null/empty"); } HttpRoute route = new HttpRoute(new HttpHost(hostName, port)); setMaxConnectionsOnHost(route, numConnections); } // Instance class starts from here /** * The underlying {@link HttpClient} that will be used by the executor * */ private final HttpClient client; /** * The authentication caching instance that will be used by the executor * */ private final AuthCache authCache; /** * Not declared final - for an instance may not be required throught the application life-cycle */ private CredentialsProvider credentialsProvider; /** * Not declared final - for an instance may not be required throught the application life-cycle */ private CookieStore cookieStore; private HttpExecutor(final HttpClient client) { if (client == null) { throw new IllegalArgumentException("Cannot create executor over null client instance"); } this.client = client; this.authCache = new BasicAuthCache(); } /** * Execute the given web request and return the obtained raw web response. * * @param webRequest * the {@link WebRequest} to be executed * * @return the {@link WebRawResponse} obtained after execution * * @throws IOException * if something fails * * @throws ClientProtocolException * if something fails */ public WebRawResponse execute(WebRequest webRequest) throws ClientProtocolException, IOException { // sharing the context may lead to circular redirects in case // of redirections from two request objects towards a single // URI - like hitting http://google.com twice leads to circular // redirects in the second request HttpContext localHttpContext = new BasicHttpContext(); localHttpContext.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credentialsProvider); localHttpContext.setAttribute(HttpClientContext.AUTH_CACHE, this.authCache); localHttpContext.setAttribute(HttpClientContext.COOKIE_STORE, this.cookieStore); // localHttpContext.removeAttribute(DefaultRedirectStrategy.REDIRECT_LOCATIONS); HttpRequestBase httpRequest = webRequest.getHttpRequest(); httpRequest.reset(); return new WebRawResponse(this.client.execute(httpRequest, localHttpContext), localHttpContext); } // Methods related to rate limiting /** * Add new rate limiting for the given host. * * @param hostName * @param limit * @param timeUnit */ public HttpExecutor addRateLimiting(String hostName, int limit, TimeUnit timeUnit) { if (this.client instanceof HttpRateLimitingClient) { ((HttpRateLimitingClient) this.client).addRateLimiting(hostName, limit, timeUnit); return this; } throw new IllegalStateException("Current client does not support rate-limiting"); } /** * Remove any previous rate limiting that has been set for the host. * * @param hostName * the host name for which we need to remove rate limiting */ public HttpExecutor removeRateLimiting(String hostName) { if (this.client instanceof HttpRateLimitingClient) { ((HttpRateLimitingClient) this.client).removeRateLimiting(hostName); return this; } throw new IllegalStateException("Current client does not support rate-limiting"); } /** * Remove all previously set rate limiting. * * @return this very {@link HttpExecutor} */ public HttpExecutor removeAllRateLimiting() { if (this.client instanceof HttpRateLimitingClient) { ((HttpRateLimitingClient) this.client).removeAllRateLimiting(); return this; } throw new IllegalStateException("Current client does not support rate-limiting"); } // Methods related to authentication /** * Add provided authentication * * @param authScope * the {@link AuthScope} that needs to be set * * @param credentials * the {@link Credentials} that need to be set * * @return this very {@link HttpExecutor} */ public HttpExecutor addAuthentication(AuthScope authScope, Credentials credentials) { if (this.credentialsProvider == null) { this.credentialsProvider = new BasicCredentialsProvider(); } this.credentialsProvider.setCredentials(authScope, credentials); return this; } /** * Clear all authentication that may have been set. * * @return this very {@link HttpExecutor} */ public HttpExecutor clearAllAuthentication() { if (this.credentialsProvider != null) { this.credentialsProvider.clear(); } return this; } /** * Add authentication for a given host * * @param host * the host name for which authentication needs to be set * * @param userName * the username that needs to be set * * @param password * the password that needs to be set * * @return this very {@link HttpExecutor} */ public HttpExecutor addAuthentication(String host, String userName, String password) { AuthScope authScope = new AuthScope(host, 80); Credentials credentials = new UsernamePasswordCredentials(userName, password); return this.addAuthentication(authScope, credentials); } /** * Add authentication for given host and port. * * @param host * the host name for which authentication needs to be set * * @param port * the port for which authentication needs to be set * * @param userName * the username that needs to be set * * @param password * the password that needs to be set * * @return this very {@link HttpExecutor} */ public HttpExecutor addAuthentication(String host, int port, String userName, String password) { AuthScope authScope = new AuthScope(host, port); Credentials credentials = new UsernamePasswordCredentials(userName, password); return this.addAuthentication(authScope, credentials); } /** * Remove authentication for given host. * * @param host the hostname for which authentication needs to be removed * * @return this very {@link HttpExecutor} * */ public HttpExecutor removeAuthentication(String host) { AuthScope authScope = new AuthScope(host, 80); return this.addAuthentication(authScope, null); } /** * Remove authentication for given host and port. * * @param host * the hostname * * @param port * the port number * * @return this very {@link HttpExecutor} */ public HttpExecutor removeAuthentication(String host, int port) { AuthScope authScope = new AuthScope(host, port); return this.addAuthentication(authScope, null); } // Methods related to cookie store /** * Replace or set the given cookie store as the cookie store instance to be * used. * * @param cookieStore * the cookie store that needs to be set * * @return this very {@link HttpExecutor} */ public HttpExecutor setCookieStore(final CookieStore cookieStore) { this.cookieStore = cookieStore; return this; } /** * Clear all cookies in the cookie store if any present. * * @return this very {@link HttpExecutor} */ public HttpExecutor clearCookies() { if (this.cookieStore != null) { this.cookieStore.clear(); } return this; } // Utility methods start here /** * Set overall maximum connections that can be handled by the underlying connection manager. * * @param numConnections */ public HttpExecutor maxConnections(int numConnections) { setMaxConnections(numConnections); return this; } /** * Set overall maximum connections per route (over all hosts) that can be handled by the underlying connection * manager. * * @param numConnections */ public HttpExecutor maxConnectionsPerRoute(int numConnections) { setMaxConnectionsPerRoute(numConnections); return this; } /** * Set maximum connections that will be operated over the given route, that will be handled by the underlying * connection manager. * * @param route * @param numConnections */ public HttpExecutor maxConnectionsOnHost(HttpRoute route, int numConnections) { setMaxConnectionsOnHost(route, numConnections); return this; } /** * Set maximum connections that will be operated over the given host on port 80, that will be handled by the underlying * connection manager. * * @param hostName * @param numConnections */ public HttpExecutor maxConnectionsOnHost(String hostName, int numConnections) { HttpRoute route = new HttpRoute(new HttpHost(hostName)); setMaxConnectionsOnHost(route, numConnections); return this; } /** * Set maximum connections that will be operated over the given host on given port, that will be handled by the underlying * connection manager. * * @param hostName * @param port * @param numConnections */ public HttpExecutor maxConnectionsOnHost(String hostName, int port, int numConnections) { HttpRoute route = new HttpRoute(new HttpHost(hostName, port)); setMaxConnectionsOnHost(route, numConnections); return this; } // Finalization methods /* (non-Javadoc) * @see java.lang.Object#finalize() */ @Override protected void finalize() throws Throwable { super.finalize(); try { HTTP_CONNECTION_MANAGER.close(); } catch (Throwable t) { // eat up } try { HTTP_CONNECTION_MANAGER.shutdown(); } catch (Throwable t) { // eat up } } }