/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package org.fcrepo.common.http; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.http.params.CoreConnectionPNames; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A general-purpose, connection-pooling HTTP Client. All methods are * thread-safe. Provides option for client to handle HTTP redirects * * @author Chris Wilper, Scott Prater * @version $Id$ */ public class WebClient { private static final Logger logger = LoggerFactory.getLogger(WebClient.class); private final WebClientConfiguration wconfig; private final PoolingClientConnectionManager cManager; /** * The proxy configuration for the web client. */ private final ProxyConfiguration proxy; public WebClient() { wconfig = new WebClientConfiguration(); proxy = new ProxyConfiguration(); cManager = configureConnectionManager(wconfig); } public WebClient(WebClientConfiguration webconfig) { wconfig = webconfig; proxy = new ProxyConfiguration(); cManager = configureConnectionManager(wconfig); } public WebClient(ProxyConfiguration proxyconfig){ wconfig = new WebClientConfiguration(); proxy = proxyconfig; cManager = configureConnectionManager(wconfig); } public WebClient(WebClientConfiguration webconfig, ProxyConfiguration proxyconfig){ wconfig = webconfig; proxy = proxyconfig; cManager = configureConnectionManager(wconfig); } private PoolingClientConnectionManager configureConnectionManager( WebClientConfiguration wconfig){ logger.debug("User-Agent is '" + wconfig.getUserAgent() + "'"); logger.debug("Max total connections is " + wconfig.getMaxTotalConn()); logger.debug("Max connections per host is " + wconfig.getMaxConnPerHost()); logger.debug("Connection timeout is " + wconfig.getTimeoutSecs()); logger.debug("Socket Connection timeout is " + wconfig.getSockTimeoutSecs()); logger.debug("Follow redirects? " + wconfig.getFollowRedirects()); logger.debug("Max number of redirects to follow is " + wconfig.getMaxRedirects()); PoolingClientConnectionManager cManager = new PoolingClientConnectionManager(); cManager.setDefaultMaxPerRoute(wconfig.getMaxConnPerHost()); cManager.setMaxTotal(wconfig.getMaxTotalConn()); //TODO pick the ports up from configuration cManager.getSchemeRegistry().register( new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); cManager.getSchemeRegistry().register( new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); return cManager; } public void shutDown() { cManager.shutdown(); } public HttpClient getHttpClient(String hostOrUrl) throws IOException, ConnectTimeoutException { return getHttpClient(hostOrUrl, null); } public HttpClient getHttpClient(String hostOrURL, UsernamePasswordCredentials creds) throws IOException, ConnectTimeoutException { String host = null; if (hostOrURL != null) { if (hostOrURL.indexOf("/") != -1) { URL url = new URL(hostOrURL); host = url.getHost(); } else { host = hostOrURL; } } DefaultHttpClient client; if (host != null && creds != null) { client = new PreemptiveAuth(cManager); client.getCredentialsProvider().setCredentials(new AuthScope(host, AuthScope.ANY_PORT), creds); } else { client = new DefaultHttpClient(cManager); } client.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, wconfig.getTimeoutSecs() * 1000); client.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, wconfig.getSockTimeoutSecs() * 1000); if (proxy.isHostProxyable(host)) { HttpHost proxyHost = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort(), "http"); client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost); if (proxy.hasValidCredentials()) { client.getCredentialsProvider().setCredentials(new AuthScope(proxy.getProxyHost(), proxy.getProxyPort()), new UsernamePasswordCredentials(proxy.getProxyUser(), proxy.getProxyPassword())); } } return client; } public HttpInputStream get(String url, boolean failIfNotOK) throws IOException { return get(url, failIfNotOK, null); } public HttpInputStream get(String url, boolean failIfNotOK, String user, String pass) throws IOException { return get(url, failIfNotOK, user, pass, null, null, null); } public HttpInputStream get(String url, boolean failIfNotOK, String user, String pass, String ifNoneMatch, String ifModifiedSince, String range) throws IOException { UsernamePasswordCredentials creds = null; if (user != null && !user.isEmpty() && pass != null && !pass.isEmpty()) creds = new UsernamePasswordCredentials(user, pass); return get(url, failIfNotOK, creds, ifNoneMatch, ifModifiedSince, range); } public HttpInputStream head(String url, boolean failIfNotOK) throws IOException { return head(url, failIfNotOK, null); } public HttpInputStream head(String url, boolean failIfNotOK, String user, String pass) throws IOException { return head(url, failIfNotOK, user, pass, null, null, null); } public HttpInputStream head(String url, boolean failIfNotOK, String user, String pass, String ifNoneMatch, String ifModifiedSince, String range) throws IOException { UsernamePasswordCredentials creds = null; if (user != null && !user.isEmpty() && pass != null && !pass.isEmpty()) creds = new UsernamePasswordCredentials(user, pass); return head(url, failIfNotOK, creds, ifNoneMatch, ifModifiedSince, range); } public HttpInputStream head(String url, boolean failIfNotOK, UsernamePasswordCredentials creds, String ifNoneMatch, String ifModifiedSince, String range) throws IOException { return execute(new HttpHead(url), url, failIfNotOK, creds, ifNoneMatch, ifModifiedSince, range); } /** * Get an HTTP resource with the response as an InputStream, given a URL. If * FOLLOW_REDIRECTS is true, up to MAX_REDIRECTS redirects will be followed. * Note that if credentials are provided, for security reasons they will * only be provided to the FIRST url in a chain of redirects. Note that if * the HTTP response has no body, the InputStream will be empty. The success * of a request can be checked with getResponseCode(). Usually you'll want * to see a 200. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html * for other codes. * * @param url * A URL that we want to do an HTTP GET upon * @param failIfNotOK * boolean value indicating if an exception should be thrown if we do * NOT receive an HTTP 200 response (OK) * @return HttpInputStream the HTTP response * @throws IOException */ public HttpInputStream get(String url, boolean failIfNotOK, UsernamePasswordCredentials creds) throws IOException { return get(url, failIfNotOK, creds, null, null, null); } public HttpInputStream get(String url, boolean failIfNotOK, UsernamePasswordCredentials creds, String ifNoneMatch, String ifModifiedSince, String range) throws IOException { return execute(new HttpGet(url), url, failIfNotOK, creds, ifNoneMatch, ifModifiedSince, range); } public HttpInputStream head(String url, boolean failIfNotOK, UsernamePasswordCredentials creds) throws IOException { return head(url, failIfNotOK, creds, null, null, null); } private HttpInputStream execute(HttpUriRequest request, String url, boolean failIfNotOK, UsernamePasswordCredentials creds, String ifNoneMatch, String ifModifiedSince, String range) throws IOException { HttpClient client; setHeaders(request, wconfig.getUserAgent(), ifNoneMatch, ifModifiedSince, range); if (creds != null && creds.getUserName() != null && creds.getUserName().length() > 0) { client = getHttpClient(url, creds); } else { client = getHttpClient(url); } HttpInputStream in = new HttpInputStream(client, request); int status = in.getStatusCode(); if (failIfNotOK) { if (status != HttpStatus.SC_OK && status != HttpStatus.SC_NOT_MODIFIED && status != HttpStatus.SC_PARTIAL_CONTENT && status != HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE) { //if (followRedirects && in.getStatusCode() == 302){ if (wconfig.getFollowRedirects() && 300 <= status && status <= 399) { int count = 1; while (300 <= status && status <= 399 && count <= wconfig.getMaxRedirects()) { if (in.getResponseHeader(HttpHeaders.LOCATION) == null) { in.close(); throw new IOException("Redirect HTTP response provided no location header."); } url = in.getResponseHeader(HttpHeaders.LOCATION).getValue(); in.close(); setHeaders(request, wconfig.getUserAgent(), ifNoneMatch, ifModifiedSince, range); in = new HttpInputStream(client, request); status = in.getStatusCode(); count++; } if (300 <= status && status <= 399) { in.close(); throw new IOException("Too many redirects"); } else if (status != 200) { in.close(); throw new IOException("Request failed [" + in.getStatusCode() + " " + in.getStatusText() + "]"); } // redirect was successful! } else { try { throw new IOException("Request failed [" + in.getStatusCode() + " " + in.getStatusText() + "]"); } finally { try { in.close(); } catch (Exception e) { logger.error("Can't close InputStream: " + e.getMessage()); } } } } } return in; } public String getResponseAsString(String url, boolean failIfNotOK) throws IOException { return getResponseAsString(url, failIfNotOK, null); } public String getResponseAsString(String url, boolean failIfNotOK, UsernamePasswordCredentials creds) throws IOException { InputStream in = get(url, failIfNotOK, creds); // Convert the response into a String. try { BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuffer buffer = new StringBuffer(); String line = reader.readLine(); while (line != null) { buffer.append(line + "\n"); line = reader.readLine(); } return buffer.toString(); } finally { try { in.close(); } catch (Exception e) { logger.error("Can't close InputStream: " + e.getMessage()); } } } private static void setHeaders( HttpUriRequest request, String ua, String ifNoneMatch, String ifModifiedSince, String range) { if (ifNoneMatch != null) { request.setHeader(HttpHeaders.IF_NONE_MATCH, ifNoneMatch); } if (ifModifiedSince != null) { request.setHeader(HttpHeaders.IF_MODIFIED_SINCE, ifModifiedSince); } if (range != null) { request.setHeader(HttpHeaders.RANGE, range); } if (ua != null) { request.setHeader(HttpHeaders.USER_AGENT, ua); } } }