diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client')
99 files changed, 17651 insertions, 0 deletions
diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java new file mode 100644 index 000000000..67798a683 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java @@ -0,0 +1,164 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.HashMap; +import java.util.Map; + +import ch.boye.httpclientandroidlib.client.BackoffManager; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.pool.ConnPoolControl; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <p>The <code>AIMDBackoffManager</code> applies an additive increase, + * multiplicative decrease (AIMD) to managing a dynamic limit to + * the number of connections allowed to a given host. You may want + * to experiment with the settings for the cooldown periods and the + * backoff factor to get the adaptive behavior you want.</p> + * + * <p>Generally speaking, shorter cooldowns will lead to more steady-state + * variability but faster reaction times, while longer cooldowns + * will lead to more stable equilibrium behavior but slower reaction + * times.</p> + * + * <p>Similarly, higher backoff factors promote greater + * utilization of available capacity at the expense of fairness + * among clients. Lower backoff factors allow equal distribution of + * capacity among clients (fairness) to happen faster, at the + * expense of having more server capacity unused in the short term.</p> + * + * @since 4.2 + */ +public class AIMDBackoffManager implements BackoffManager { + + private final ConnPoolControl<HttpRoute> connPerRoute; + private final Clock clock; + private final Map<HttpRoute,Long> lastRouteProbes; + private final Map<HttpRoute,Long> lastRouteBackoffs; + private long coolDown = 5 * 1000L; + private double backoffFactor = 0.5; + private int cap = 2; // Per RFC 2616 sec 8.1.4 + + /** + * Creates an <code>AIMDBackoffManager</code> to manage + * per-host connection pool sizes represented by the + * given {@link ConnPoolControl}. + * @param connPerRoute per-host routing maximums to + * be managed + */ + public AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute) { + this(connPerRoute, new SystemClock()); + } + + AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute, final Clock clock) { + this.clock = clock; + this.connPerRoute = connPerRoute; + this.lastRouteProbes = new HashMap<HttpRoute,Long>(); + this.lastRouteBackoffs = new HashMap<HttpRoute,Long>(); + } + + public void backOff(final HttpRoute route) { + synchronized(connPerRoute) { + final int curr = connPerRoute.getMaxPerRoute(route); + final Long lastUpdate = getLastUpdate(lastRouteBackoffs, route); + final long now = clock.getCurrentTime(); + if (now - lastUpdate.longValue() < coolDown) { + return; + } + connPerRoute.setMaxPerRoute(route, getBackedOffPoolSize(curr)); + lastRouteBackoffs.put(route, Long.valueOf(now)); + } + } + + private int getBackedOffPoolSize(final int curr) { + if (curr <= 1) { + return 1; + } + return (int)(Math.floor(backoffFactor * curr)); + } + + public void probe(final HttpRoute route) { + synchronized(connPerRoute) { + final int curr = connPerRoute.getMaxPerRoute(route); + final int max = (curr >= cap) ? cap : curr + 1; + final Long lastProbe = getLastUpdate(lastRouteProbes, route); + final Long lastBackoff = getLastUpdate(lastRouteBackoffs, route); + final long now = clock.getCurrentTime(); + if (now - lastProbe.longValue() < coolDown || now - lastBackoff.longValue() < coolDown) { + return; + } + connPerRoute.setMaxPerRoute(route, max); + lastRouteProbes.put(route, Long.valueOf(now)); + } + } + + private Long getLastUpdate(final Map<HttpRoute,Long> updates, final HttpRoute route) { + Long lastUpdate = updates.get(route); + if (lastUpdate == null) { + lastUpdate = Long.valueOf(0L); + } + return lastUpdate; + } + + /** + * Sets the factor to use when backing off; the new + * per-host limit will be roughly the current max times + * this factor. <code>Math.floor</code> is applied in the + * case of non-integer outcomes to ensure we actually + * decrease the pool size. Pool sizes are never decreased + * below 1, however. Defaults to 0.5. + * @param d must be between 0.0 and 1.0, exclusive. + */ + public void setBackoffFactor(final double d) { + Args.check(d > 0.0 && d < 1.0, "Backoff factor must be 0.0 < f < 1.0"); + backoffFactor = d; + } + + /** + * Sets the amount of time, in milliseconds, to wait between + * adjustments in pool sizes for a given host, to allow + * enough time for the adjustments to take effect. Defaults + * to 5000L (5 seconds). + * @param l must be positive + */ + public void setCooldownMillis(final long l) { + Args.positive(coolDown, "Cool down"); + coolDown = l; + } + + /** + * Sets the absolute maximum per-host connection pool size to + * probe up to; defaults to 2 (the default per-host max). + * @param cap must be >= 1 + */ + public void setPerHostConnectionCap(final int cap) { + Args.positive(cap, "Per host connection cap"); + this.cap = cap; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java new file mode 100644 index 000000000..a02c1a03b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java @@ -0,0 +1,189 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthSchemeRegistry; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.params.AuthPolicy; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Base class for {@link AuthenticationHandler} implementations. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ch.boye.httpclientandroidlib.client.AuthenticationStrategy} + */ +@Deprecated +@Immutable +public abstract class AbstractAuthenticationHandler implements AuthenticationHandler { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private static final List<String> DEFAULT_SCHEME_PRIORITY = + Collections.unmodifiableList(Arrays.asList(new String[] { + AuthPolicy.SPNEGO, + AuthPolicy.NTLM, + AuthPolicy.DIGEST, + AuthPolicy.BASIC + })); + + public AbstractAuthenticationHandler() { + super(); + } + + protected Map<String, Header> parseChallenges( + final Header[] headers) throws MalformedChallengeException { + + final Map<String, Header> map = new HashMap<String, Header>(headers.length); + for (final Header header : headers) { + final CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int endIndex = pos; + final String s = buffer.substring(beginIndex, endIndex); + map.put(s.toLowerCase(Locale.ENGLISH), header); + } + return map; + } + + /** + * Returns default list of auth scheme names in their order of preference. + * + * @return list of auth scheme names + */ + protected List<String> getAuthPreferences() { + return DEFAULT_SCHEME_PRIORITY; + } + + /** + * Returns default list of auth scheme names in their order of preference + * based on the HTTP response and the current execution context. + * + * @param response HTTP response. + * @param context HTTP execution context. + * + * @since 4.1 + */ + protected List<String> getAuthPreferences( + final HttpResponse response, + final HttpContext context) { + return getAuthPreferences(); + } + + public AuthScheme selectScheme( + final Map<String, Header> challenges, + final HttpResponse response, + final HttpContext context) throws AuthenticationException { + + final AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute( + ClientContext.AUTHSCHEME_REGISTRY); + Asserts.notNull(registry, "AuthScheme registry"); + Collection<String> authPrefs = getAuthPreferences(response, context); + if (authPrefs == null) { + authPrefs = DEFAULT_SCHEME_PRIORITY; + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication schemes in the order of preference: " + + authPrefs); + } + + AuthScheme authScheme = null; + for (final String id: authPrefs) { + final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + + if (challenge != null) { + if (this.log.isDebugEnabled()) { + this.log.debug(id + " authentication scheme selected"); + } + try { + authScheme = registry.getAuthScheme(id, response.getParams()); + break; + } catch (final IllegalStateException e) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication scheme " + id + " not supported"); + // Try again + } + } + } else { + if (this.log.isDebugEnabled()) { + this.log.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + if (authScheme == null) { + // If none selected, something is wrong + throw new AuthenticationException( + "Unable to respond to any of these challenges: " + + challenges); + } + return authScheme; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java new file mode 100644 index 000000000..f862c9886 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java @@ -0,0 +1,990 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestInterceptor; +import ch.boye.httpclientandroidlib.HttpResponseInterceptor; +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthSchemeRegistry; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.BackoffManager; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.RequestDirector; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.params.AuthPolicy; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.params.CookiePolicy; +import ch.boye.httpclientandroidlib.client.params.HttpClientParamConfig; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManagerFactory; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.cookie.CookieSpecRegistry; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.auth.BasicSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.DigestSchemeFactory; +/* KerberosSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.auth.NTLMSchemeFactory; +/* SPNegoSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.conn.BasicClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.DefaultHttpRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.SchemeRegistryFactory; +import ch.boye.httpclientandroidlib.impl.cookie.BestMatchSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.BrowserCompatSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.IgnoreSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.NetscapeDraftSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2109SpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2965SpecFactory; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.BasicHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.DefaultedHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.ImmutableHttpProcessor; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Base class for {@link ch.boye.httpclientandroidlib.client.HttpClient} implementations. + * This class acts as a facade to a number of special purpose handler or + * strategy implementations responsible for handling of a particular aspect + * of the HTTP protocol such as redirect or authentication handling or + * making decision about connection persistence and keep alive duration. + * This enables the users to selectively replace default implementation + * of those aspects with custom, application specific ones. This class + * also provides factory methods to instantiate those objects: + * <ul> + * <li>{@link HttpRequestExecutor}</li> object used to transmit messages + * over HTTP connections. The {@link #createRequestExecutor()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link BasicHttpProcessor}</li> object to manage a list of protocol + * interceptors and apply cross-cutting protocol logic to all incoming + * and outgoing HTTP messages. The {@link #createHttpProcessor()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link HttpRequestRetryHandler}</li> object used to decide whether + * or not a failed HTTP request is safe to retry automatically. + * The {@link #createHttpRequestRetryHandler()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link ClientConnectionManager}</li> object used to manage + * persistent HTTP connections. + * <li>{@link ConnectionReuseStrategy}</li> object used to decide whether + * or not a HTTP connection can be kept alive and re-used for subsequent + * HTTP requests. The {@link #createConnectionReuseStrategy()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link ConnectionKeepAliveStrategy}</li> object used to decide how + * long a persistent HTTP connection can be kept alive. + * The {@link #createConnectionKeepAliveStrategy()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link CookieSpecRegistry}</li> object used to maintain a list of + * supported cookie specifications. + * The {@link #createCookieSpecRegistry()} must be implemented by concrete + * super classes to instantiate this object. + * <li>{@link CookieStore}</li> object used to maintain a collection of + * cookies. The {@link #createCookieStore()} must be implemented by + * concrete super classes to instantiate this object. + * <li>{@link AuthSchemeRegistry}</li> object used to maintain a list of + * supported authentication schemes. + * The {@link #createAuthSchemeRegistry()} must be implemented by concrete + * super classes to instantiate this object. + * <li>{@link CredentialsProvider}</li> object used to maintain + * a collection user credentials. The {@link #createCredentialsProvider()} + * must be implemented by concrete super classes to instantiate + * this object. + * <li>{@link AuthenticationStrategy}</li> object used to authenticate + * against the target host. + * The {@link #createTargetAuthenticationStrategy()} must be implemented + * by concrete super classes to instantiate this object. + * <li>{@link AuthenticationStrategy}</li> object used to authenticate + * against the proxy host. + * The {@link #createProxyAuthenticationStrategy()} must be implemented + * by concrete super classes to instantiate this object. + * <li>{@link HttpRoutePlanner}</li> object used to calculate a route + * for establishing a connection to the target host. The route + * may involve multiple intermediate hops. + * The {@link #createHttpRoutePlanner()} must be implemented + * by concrete super classes to instantiate this object. + * <li>{@link RedirectStrategy}</li> object used to determine if an HTTP + * request should be redirected to a new location in response to an HTTP + * response received from the target server. + * <li>{@link UserTokenHandler}</li> object used to determine if the + * execution context is user identity specific. + * The {@link #createUserTokenHandler()} must be implemented by + * concrete super classes to instantiate this object. + * </ul> + * <p> + * This class also maintains a list of protocol interceptors intended + * for processing outgoing requests and incoming responses and provides + * methods for managing those interceptors. New protocol interceptors can be + * introduced to the protocol processor chain or removed from it if needed. + * Internally protocol interceptors are stored in a simple + * {@link java.util.ArrayList}. They are executed in the same natural order + * as they are added to the list. + * <p> + * AbstractHttpClient is thread safe. It is recommended that the same + * instance of this class is reused for multiple request executions. + * When an instance of DefaultHttpClient is no longer needed and is about + * to go out of scope the connection manager associated with it must be + * shut down by calling {@link ClientConnectionManager#shutdown()}! + * + * @since 4.0 + * + * @deprecated (4.3) use {@link HttpClientBuilder}. + */ +@ThreadSafe +@Deprecated +public abstract class AbstractHttpClient extends CloseableHttpClient { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** The parameters. */ + @GuardedBy("this") + private HttpParams defaultParams; + + /** The request executor. */ + @GuardedBy("this") + private HttpRequestExecutor requestExec; + + /** The connection manager. */ + @GuardedBy("this") + private ClientConnectionManager connManager; + + /** The connection re-use strategy. */ + @GuardedBy("this") + private ConnectionReuseStrategy reuseStrategy; + + /** The connection keep-alive strategy. */ + @GuardedBy("this") + private ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The cookie spec registry. */ + @GuardedBy("this") + private CookieSpecRegistry supportedCookieSpecs; + + /** The authentication scheme registry. */ + @GuardedBy("this") + private AuthSchemeRegistry supportedAuthSchemes; + + /** The HTTP protocol processor and its immutable copy. */ + @GuardedBy("this") + private BasicHttpProcessor mutableProcessor; + + @GuardedBy("this") + private ImmutableHttpProcessor protocolProcessor; + + /** The request retry handler. */ + @GuardedBy("this") + private HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + @GuardedBy("this") + private RedirectStrategy redirectStrategy; + + /** The target authentication handler. */ + @GuardedBy("this") + private AuthenticationStrategy targetAuthStrategy; + + /** The proxy authentication handler. */ + @GuardedBy("this") + private AuthenticationStrategy proxyAuthStrategy; + + /** The cookie store. */ + @GuardedBy("this") + private CookieStore cookieStore; + + /** The credentials provider. */ + @GuardedBy("this") + private CredentialsProvider credsProvider; + + /** The route planner. */ + @GuardedBy("this") + private HttpRoutePlanner routePlanner; + + /** The user token handler. */ + @GuardedBy("this") + private UserTokenHandler userTokenHandler; + + /** The connection backoff strategy. */ + @GuardedBy("this") + private ConnectionBackoffStrategy connectionBackoffStrategy; + + /** The backoff manager. */ + @GuardedBy("this") + private BackoffManager backoffManager; + + /** + * Creates a new HTTP client. + * + * @param conman the connection manager + * @param params the parameters + */ + protected AbstractHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + super(); + defaultParams = params; + connManager = conman; + } // constructor + + + protected abstract HttpParams createHttpParams(); + + + protected abstract BasicHttpProcessor createHttpProcessor(); + + + protected HttpContext createHttpContext() { + final HttpContext context = new BasicHttpContext(); + context.setAttribute( + ClientContext.SCHEME_REGISTRY, + getConnectionManager().getSchemeRegistry()); + context.setAttribute( + ClientContext.AUTHSCHEME_REGISTRY, + getAuthSchemes()); + context.setAttribute( + ClientContext.COOKIESPEC_REGISTRY, + getCookieSpecs()); + context.setAttribute( + ClientContext.COOKIE_STORE, + getCookieStore()); + context.setAttribute( + ClientContext.CREDS_PROVIDER, + getCredentialsProvider()); + return context; + } + + + protected ClientConnectionManager createClientConnectionManager() { + final SchemeRegistry registry = SchemeRegistryFactory.createDefault(); + + ClientConnectionManager connManager = null; + final HttpParams params = getParams(); + + ClientConnectionManagerFactory factory = null; + + final String className = (String) params.getParameter( + ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME); + if (className != null) { + try { + final Class<?> clazz = Class.forName(className); + factory = (ClientConnectionManagerFactory) clazz.newInstance(); + } catch (final ClassNotFoundException ex) { + throw new IllegalStateException("Invalid class name: " + className); + } catch (final IllegalAccessException ex) { + throw new IllegalAccessError(ex.getMessage()); + } catch (final InstantiationException ex) { + throw new InstantiationError(ex.getMessage()); + } + } + if (factory != null) { + connManager = factory.newInstance(params, registry); + } else { + connManager = new BasicClientConnectionManager(registry); + } + + return connManager; + } + + + protected AuthSchemeRegistry createAuthSchemeRegistry() { + final AuthSchemeRegistry registry = new AuthSchemeRegistry(); + registry.register( + AuthPolicy.BASIC, + new BasicSchemeFactory()); + registry.register( + AuthPolicy.DIGEST, + new DigestSchemeFactory()); + registry.register( + AuthPolicy.NTLM, + new NTLMSchemeFactory()); + /* SPNegoSchemeFactory removed by HttpClient for Android script. */ + /* KerberosSchemeFactory removed by HttpClient for Android script. */ + return registry; + } + + + protected CookieSpecRegistry createCookieSpecRegistry() { + final CookieSpecRegistry registry = new CookieSpecRegistry(); + registry.register( + CookiePolicy.BEST_MATCH, + new BestMatchSpecFactory()); + registry.register( + CookiePolicy.BROWSER_COMPATIBILITY, + new BrowserCompatSpecFactory()); + registry.register( + CookiePolicy.NETSCAPE, + new NetscapeDraftSpecFactory()); + registry.register( + CookiePolicy.RFC_2109, + new RFC2109SpecFactory()); + registry.register( + CookiePolicy.RFC_2965, + new RFC2965SpecFactory()); + registry.register( + CookiePolicy.IGNORE_COOKIES, + new IgnoreSpecFactory()); + return registry; + } + + protected HttpRequestExecutor createRequestExecutor() { + return new HttpRequestExecutor(); + } + + protected ConnectionReuseStrategy createConnectionReuseStrategy() { + return new DefaultConnectionReuseStrategy(); + } + + protected ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy() { + return new DefaultConnectionKeepAliveStrategy(); + } + + protected HttpRequestRetryHandler createHttpRequestRetryHandler() { + return new DefaultHttpRequestRetryHandler(); + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + protected RedirectHandler createRedirectHandler() { + return new DefaultRedirectHandler(); + } + + protected AuthenticationStrategy createTargetAuthenticationStrategy() { + return new TargetAuthenticationStrategy(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + protected AuthenticationHandler createTargetAuthenticationHandler() { + return new DefaultTargetAuthenticationHandler(); + } + + protected AuthenticationStrategy createProxyAuthenticationStrategy() { + return new ProxyAuthenticationStrategy(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + protected AuthenticationHandler createProxyAuthenticationHandler() { + return new DefaultProxyAuthenticationHandler(); + } + + protected CookieStore createCookieStore() { + return new BasicCookieStore(); + } + + protected CredentialsProvider createCredentialsProvider() { + return new BasicCredentialsProvider(); + } + + protected HttpRoutePlanner createHttpRoutePlanner() { + return new DefaultHttpRoutePlanner(getConnectionManager().getSchemeRegistry()); + } + + protected UserTokenHandler createUserTokenHandler() { + return new DefaultUserTokenHandler(); + } + + // non-javadoc, see interface HttpClient + public synchronized final HttpParams getParams() { + if (defaultParams == null) { + defaultParams = createHttpParams(); + } + return defaultParams; + } + + /** + * Replaces the parameters. + * The implementation here does not update parameters of dependent objects. + * + * @param params the new default parameters + */ + public synchronized void setParams(final HttpParams params) { + defaultParams = params; + } + + + public synchronized final ClientConnectionManager getConnectionManager() { + if (connManager == null) { + connManager = createClientConnectionManager(); + } + return connManager; + } + + + public synchronized final HttpRequestExecutor getRequestExecutor() { + if (requestExec == null) { + requestExec = createRequestExecutor(); + } + return requestExec; + } + + + public synchronized final AuthSchemeRegistry getAuthSchemes() { + if (supportedAuthSchemes == null) { + supportedAuthSchemes = createAuthSchemeRegistry(); + } + return supportedAuthSchemes; + } + + public synchronized void setAuthSchemes(final AuthSchemeRegistry registry) { + supportedAuthSchemes = registry; + } + + public synchronized final ConnectionBackoffStrategy getConnectionBackoffStrategy() { + return connectionBackoffStrategy; + } + + public synchronized void setConnectionBackoffStrategy(final ConnectionBackoffStrategy strategy) { + connectionBackoffStrategy = strategy; + } + + public synchronized final CookieSpecRegistry getCookieSpecs() { + if (supportedCookieSpecs == null) { + supportedCookieSpecs = createCookieSpecRegistry(); + } + return supportedCookieSpecs; + } + + public synchronized final BackoffManager getBackoffManager() { + return backoffManager; + } + + public synchronized void setBackoffManager(final BackoffManager manager) { + backoffManager = manager; + } + + public synchronized void setCookieSpecs(final CookieSpecRegistry registry) { + supportedCookieSpecs = registry; + } + + public synchronized final ConnectionReuseStrategy getConnectionReuseStrategy() { + if (reuseStrategy == null) { + reuseStrategy = createConnectionReuseStrategy(); + } + return reuseStrategy; + } + + + public synchronized void setReuseStrategy(final ConnectionReuseStrategy strategy) { + this.reuseStrategy = strategy; + } + + + public synchronized final ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() { + if (keepAliveStrategy == null) { + keepAliveStrategy = createConnectionKeepAliveStrategy(); + } + return keepAliveStrategy; + } + + + public synchronized void setKeepAliveStrategy(final ConnectionKeepAliveStrategy strategy) { + this.keepAliveStrategy = strategy; + } + + + public synchronized final HttpRequestRetryHandler getHttpRequestRetryHandler() { + if (retryHandler == null) { + retryHandler = createHttpRequestRetryHandler(); + } + return retryHandler; + } + + public synchronized void setHttpRequestRetryHandler(final HttpRequestRetryHandler handler) { + this.retryHandler = handler; + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + public synchronized final RedirectHandler getRedirectHandler() { + return createRedirectHandler(); + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + public synchronized void setRedirectHandler(final RedirectHandler handler) { + this.redirectStrategy = new DefaultRedirectStrategyAdaptor(handler); + } + + /** + * @since 4.1 + */ + public synchronized final RedirectStrategy getRedirectStrategy() { + if (redirectStrategy == null) { + redirectStrategy = new DefaultRedirectStrategy(); + } + return redirectStrategy; + } + + /** + * @since 4.1 + */ + public synchronized void setRedirectStrategy(final RedirectStrategy strategy) { + this.redirectStrategy = strategy; + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized final AuthenticationHandler getTargetAuthenticationHandler() { + return createTargetAuthenticationHandler(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized void setTargetAuthenticationHandler(final AuthenticationHandler handler) { + this.targetAuthStrategy = new AuthenticationStrategyAdaptor(handler); + } + + /** + * @since 4.2 + */ + public synchronized final AuthenticationStrategy getTargetAuthenticationStrategy() { + if (targetAuthStrategy == null) { + targetAuthStrategy = createTargetAuthenticationStrategy(); + } + return targetAuthStrategy; + } + + /** + * @since 4.2 + */ + public synchronized void setTargetAuthenticationStrategy(final AuthenticationStrategy strategy) { + this.targetAuthStrategy = strategy; + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized final AuthenticationHandler getProxyAuthenticationHandler() { + return createProxyAuthenticationHandler(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized void setProxyAuthenticationHandler(final AuthenticationHandler handler) { + this.proxyAuthStrategy = new AuthenticationStrategyAdaptor(handler); + } + + /** + * @since 4.2 + */ + public synchronized final AuthenticationStrategy getProxyAuthenticationStrategy() { + if (proxyAuthStrategy == null) { + proxyAuthStrategy = createProxyAuthenticationStrategy(); + } + return proxyAuthStrategy; + } + + /** + * @since 4.2 + */ + public synchronized void setProxyAuthenticationStrategy(final AuthenticationStrategy strategy) { + this.proxyAuthStrategy = strategy; + } + + public synchronized final CookieStore getCookieStore() { + if (cookieStore == null) { + cookieStore = createCookieStore(); + } + return cookieStore; + } + + public synchronized void setCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + public synchronized final CredentialsProvider getCredentialsProvider() { + if (credsProvider == null) { + credsProvider = createCredentialsProvider(); + } + return credsProvider; + } + + public synchronized void setCredentialsProvider(final CredentialsProvider credsProvider) { + this.credsProvider = credsProvider; + } + + public synchronized final HttpRoutePlanner getRoutePlanner() { + if (this.routePlanner == null) { + this.routePlanner = createHttpRoutePlanner(); + } + return this.routePlanner; + } + + public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + } + + public synchronized final UserTokenHandler getUserTokenHandler() { + if (this.userTokenHandler == null) { + this.userTokenHandler = createUserTokenHandler(); + } + return this.userTokenHandler; + } + + public synchronized void setUserTokenHandler(final UserTokenHandler handler) { + this.userTokenHandler = handler; + } + + protected synchronized final BasicHttpProcessor getHttpProcessor() { + if (mutableProcessor == null) { + mutableProcessor = createHttpProcessor(); + } + return mutableProcessor; + } + + private synchronized HttpProcessor getProtocolProcessor() { + if (protocolProcessor == null) { + // Get mutable HTTP processor + final BasicHttpProcessor proc = getHttpProcessor(); + // and create an immutable copy of it + final int reqc = proc.getRequestInterceptorCount(); + final HttpRequestInterceptor[] reqinterceptors = new HttpRequestInterceptor[reqc]; + for (int i = 0; i < reqc; i++) { + reqinterceptors[i] = proc.getRequestInterceptor(i); + } + final int resc = proc.getResponseInterceptorCount(); + final HttpResponseInterceptor[] resinterceptors = new HttpResponseInterceptor[resc]; + for (int i = 0; i < resc; i++) { + resinterceptors[i] = proc.getResponseInterceptor(i); + } + protocolProcessor = new ImmutableHttpProcessor(reqinterceptors, resinterceptors); + } + return protocolProcessor; + } + + public synchronized int getResponseInterceptorCount() { + return getHttpProcessor().getResponseInterceptorCount(); + } + + public synchronized HttpResponseInterceptor getResponseInterceptor(final int index) { + return getHttpProcessor().getResponseInterceptor(index); + } + + public synchronized HttpRequestInterceptor getRequestInterceptor(final int index) { + return getHttpProcessor().getRequestInterceptor(index); + } + + public synchronized int getRequestInterceptorCount() { + return getHttpProcessor().getRequestInterceptorCount(); + } + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + protocolProcessor = null; + } + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp, final int index) { + getHttpProcessor().addInterceptor(itcp, index); + protocolProcessor = null; + } + + public synchronized void clearResponseInterceptors() { + getHttpProcessor().clearResponseInterceptors(); + protocolProcessor = null; + } + + public synchronized void removeResponseInterceptorByClass(final Class<? extends HttpResponseInterceptor> clazz) { + getHttpProcessor().removeResponseInterceptorByClass(clazz); + protocolProcessor = null; + } + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + protocolProcessor = null; + } + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp, final int index) { + getHttpProcessor().addInterceptor(itcp, index); + protocolProcessor = null; + } + + public synchronized void clearRequestInterceptors() { + getHttpProcessor().clearRequestInterceptors(); + protocolProcessor = null; + } + + public synchronized void removeRequestInterceptorByClass(final Class<? extends HttpRequestInterceptor> clazz) { + getHttpProcessor().removeRequestInterceptorByClass(clazz); + protocolProcessor = null; + } + + @Override + protected final CloseableHttpResponse doExecute(final HttpHost target, final HttpRequest request, + final HttpContext context) + throws IOException, ClientProtocolException { + + Args.notNull(request, "HTTP request"); + // a null target may be acceptable, this depends on the route planner + // a null context is acceptable, default context created below + + HttpContext execContext = null; + RequestDirector director = null; + HttpRoutePlanner routePlanner = null; + ConnectionBackoffStrategy connectionBackoffStrategy = null; + BackoffManager backoffManager = null; + + // Initialize the request execution context making copies of + // all shared objects that are potentially threading unsafe. + synchronized (this) { + + final HttpContext defaultContext = createHttpContext(); + if (context == null) { + execContext = defaultContext; + } else { + execContext = new DefaultedHttpContext(context, defaultContext); + } + final HttpParams params = determineParams(request); + final RequestConfig config = HttpClientParamConfig.getRequestConfig(params); + execContext.setAttribute(ClientContext.REQUEST_CONFIG, config); + + // Create a director for this request + director = createClientRequestDirector( + getRequestExecutor(), + getConnectionManager(), + getConnectionReuseStrategy(), + getConnectionKeepAliveStrategy(), + getRoutePlanner(), + getProtocolProcessor(), + getHttpRequestRetryHandler(), + getRedirectStrategy(), + getTargetAuthenticationStrategy(), + getProxyAuthenticationStrategy(), + getUserTokenHandler(), + params); + routePlanner = getRoutePlanner(); + connectionBackoffStrategy = getConnectionBackoffStrategy(); + backoffManager = getBackoffManager(); + } + + try { + if (connectionBackoffStrategy != null && backoffManager != null) { + final HttpHost targetForRoute = (target != null) ? target + : (HttpHost) determineParams(request).getParameter( + ClientPNames.DEFAULT_HOST); + final HttpRoute route = routePlanner.determineRoute(targetForRoute, request, execContext); + + final CloseableHttpResponse out; + try { + out = CloseableHttpResponseProxy.newProxy( + director.execute(target, request, execContext)); + } catch (final RuntimeException re) { + if (connectionBackoffStrategy.shouldBackoff(re)) { + backoffManager.backOff(route); + } + throw re; + } catch (final Exception e) { + if (connectionBackoffStrategy.shouldBackoff(e)) { + backoffManager.backOff(route); + } + if (e instanceof HttpException) { + throw (HttpException)e; + } + if (e instanceof IOException) { + throw (IOException)e; + } + throw new UndeclaredThrowableException(e); + } + if (connectionBackoffStrategy.shouldBackoff(out)) { + backoffManager.backOff(route); + } else { + backoffManager.probe(route); + } + return out; + } else { + return CloseableHttpResponseProxy.newProxy( + director.execute(target, request, execContext)); + } + } catch(final HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + return new DefaultRequestDirector( + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectHandler, + targetAuthHandler, + proxyAuthHandler, + userTokenHandler, + params); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + return new DefaultRequestDirector( + log, + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectStrategy, + targetAuthHandler, + proxyAuthHandler, + userTokenHandler, + params); + } + + + /** + * @since 4.2 + */ + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationStrategy targetAuthStrategy, + final AuthenticationStrategy proxyAuthStrategy, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + return new DefaultRequestDirector( + log, + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectStrategy, + targetAuthStrategy, + proxyAuthStrategy, + userTokenHandler, + params); + } + + /** + * Obtains parameters for executing a request. + * The default implementation in this class creates a new + * {@link ClientParamsStack} from the request parameters + * and the client parameters. + * <br/> + * This method is called by the default implementation of + * {@link #execute(HttpHost,HttpRequest,HttpContext)} + * to obtain the parameters for the + * {@link DefaultRequestDirector}. + * + * @param req the request that will be executed + * + * @return the parameters to use + */ + protected HttpParams determineParams(final HttpRequest req) { + return new ClientParamsStack + (null, getParams(), req.getParams(), null); + } + + + public void close() { + getConnectionManager().shutdown(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java new file mode 100644 index 000000000..a65ce7e95 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java @@ -0,0 +1,172 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.LinkedList; +import java.util.Locale; +import java.util.Map; +import java.util.Queue; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthOption; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.client.AuthCache; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.params.AuthPolicy; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * @deprecated (4.2) do not use + */ +@Immutable +@Deprecated +class AuthenticationStrategyAdaptor implements AuthenticationStrategy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final AuthenticationHandler handler; + + public AuthenticationStrategyAdaptor(final AuthenticationHandler handler) { + super(); + this.handler = handler; + } + + public boolean isAuthenticationRequested( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) { + return this.handler.isAuthenticationRequested(response, context); + } + + public Map<String, Header> getChallenges( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + return this.handler.getChallenges(response, context); + } + + public Queue<AuthOption> select( + final Map<String, Header> challenges, + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(challenges, "Map of auth challenges"); + Args.notNull(authhost, "Host"); + Args.notNull(response, "HTTP response"); + Args.notNull(context, "HTTP context"); + + final Queue<AuthOption> options = new LinkedList<AuthOption>(); + final CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute( + ClientContext.CREDS_PROVIDER); + if (credsProvider == null) { + this.log.debug("Credentials provider not set in the context"); + return options; + } + + final AuthScheme authScheme; + try { + authScheme = this.handler.selectScheme(challenges, response, context); + } catch (final AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn(ex.getMessage(), ex); + } + return options; + } + final String id = authScheme.getSchemeName(); + final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + authScheme.processChallenge(challenge); + + final AuthScope authScope = new AuthScope( + authhost.getHostName(), + authhost.getPort(), + authScheme.getRealm(), + authScheme.getSchemeName()); + + final Credentials credentials = credsProvider.getCredentials(authScope); + if (credentials != null) { + options.add(new AuthOption(authScheme, credentials)); + } + return options; + } + + public void authSucceeded( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE); + if (isCachable(authScheme)) { + if (authCache == null) { + authCache = new BasicAuthCache(); + context.setAttribute(ClientContext.AUTH_CACHE, authCache); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Caching '" + authScheme.getSchemeName() + + "' auth scheme for " + authhost); + } + authCache.put(authhost, authScheme); + } + } + + public void authFailed( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + final AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE); + if (authCache == null) { + return; + } + if (this.log.isDebugEnabled()) { + this.log.debug("Removing from cache '" + authScheme.getSchemeName() + + "' auth scheme for " + authhost); + } + authCache.remove(authhost); + } + + private boolean isCachable(final AuthScheme authScheme) { + if (authScheme == null || !authScheme.isComplete()) { + return false; + } + final String schemeName = authScheme.getSchemeName(); + return schemeName.equalsIgnoreCase(AuthPolicy.BASIC) || + schemeName.equalsIgnoreCase(AuthPolicy.DIGEST); + } + + public AuthenticationHandler getHandler() { + return this.handler; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java new file mode 100644 index 000000000..38b7df6d2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java @@ -0,0 +1,245 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Queue; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthOption; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.client.AuthCache; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +@Immutable +abstract class AuthenticationStrategyImpl implements AuthenticationStrategy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private static final List<String> DEFAULT_SCHEME_PRIORITY = + Collections.unmodifiableList(Arrays.asList(AuthSchemes.SPNEGO, + AuthSchemes.KERBEROS, + AuthSchemes.NTLM, + AuthSchemes.DIGEST, + AuthSchemes.BASIC)); + + private final int challengeCode; + private final String headerName; + + AuthenticationStrategyImpl(final int challengeCode, final String headerName) { + super(); + this.challengeCode = challengeCode; + this.headerName = headerName; + } + + public boolean isAuthenticationRequested( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + final int status = response.getStatusLine().getStatusCode(); + return status == this.challengeCode; + } + + public Map<String, Header> getChallenges( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(response, "HTTP response"); + final Header[] headers = response.getHeaders(this.headerName); + final Map<String, Header> map = new HashMap<String, Header>(headers.length); + for (final Header header : headers) { + final CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int endIndex = pos; + final String s = buffer.substring(beginIndex, endIndex); + map.put(s.toLowerCase(Locale.ENGLISH), header); + } + return map; + } + + abstract Collection<String> getPreferredAuthSchemes(RequestConfig config); + + public Queue<AuthOption> select( + final Map<String, Header> challenges, + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(challenges, "Map of auth challenges"); + Args.notNull(authhost, "Host"); + Args.notNull(response, "HTTP response"); + Args.notNull(context, "HTTP context"); + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + final Queue<AuthOption> options = new LinkedList<AuthOption>(); + final Lookup<AuthSchemeProvider> registry = clientContext.getAuthSchemeRegistry(); + if (registry == null) { + this.log.debug("Auth scheme registry not set in the context"); + return options; + } + final CredentialsProvider credsProvider = clientContext.getCredentialsProvider(); + if (credsProvider == null) { + this.log.debug("Credentials provider not set in the context"); + return options; + } + final RequestConfig config = clientContext.getRequestConfig(); + Collection<String> authPrefs = getPreferredAuthSchemes(config); + if (authPrefs == null) { + authPrefs = DEFAULT_SCHEME_PRIORITY; + } + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication schemes in the order of preference: " + authPrefs); + } + + for (final String id: authPrefs) { + final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + if (challenge != null) { + final AuthSchemeProvider authSchemeProvider = registry.lookup(id); + if (authSchemeProvider == null) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication scheme " + id + " not supported"); + // Try again + } + continue; + } + final AuthScheme authScheme = authSchemeProvider.create(context); + authScheme.processChallenge(challenge); + + final AuthScope authScope = new AuthScope( + authhost.getHostName(), + authhost.getPort(), + authScheme.getRealm(), + authScheme.getSchemeName()); + + final Credentials credentials = credsProvider.getCredentials(authScope); + if (credentials != null) { + options.add(new AuthOption(authScheme, credentials)); + } + } else { + if (this.log.isDebugEnabled()) { + this.log.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + return options; + } + + public void authSucceeded( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + Args.notNull(authhost, "Host"); + Args.notNull(authScheme, "Auth scheme"); + Args.notNull(context, "HTTP context"); + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + if (isCachable(authScheme)) { + AuthCache authCache = clientContext.getAuthCache(); + if (authCache == null) { + authCache = new BasicAuthCache(); + clientContext.setAuthCache(authCache); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Caching '" + authScheme.getSchemeName() + + "' auth scheme for " + authhost); + } + authCache.put(authhost, authScheme); + } + } + + protected boolean isCachable(final AuthScheme authScheme) { + if (authScheme == null || !authScheme.isComplete()) { + return false; + } + final String schemeName = authScheme.getSchemeName(); + return schemeName.equalsIgnoreCase(AuthSchemes.BASIC) || + schemeName.equalsIgnoreCase(AuthSchemes.DIGEST); + } + + public void authFailed( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + Args.notNull(authhost, "Host"); + Args.notNull(context, "HTTP context"); + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + final AuthCache authCache = clientContext.getAuthCache(); + if (authCache != null) { + if (this.log.isDebugEnabled()) { + this.log.debug("Clearing cached auth scheme for " + authhost); + } + authCache.remove(authhost); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java new file mode 100644 index 000000000..70c0e3404 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java @@ -0,0 +1,190 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.URI; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.ServiceUnavailableRetryStrategy; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * {@link HttpClient} implementation that can automatically retry the request in case of + * a non-2xx response using the {@link ServiceUnavailableRetryStrategy} interface. + * + * @since 4.2 + * + * @deprecated (4.3) use {@link HttpClientBuilder}. + */ +@Deprecated +@ThreadSafe +public class AutoRetryHttpClient implements HttpClient { + + private final HttpClient backend; + + private final ServiceUnavailableRetryStrategy retryStrategy; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + public AutoRetryHttpClient( + final HttpClient client, final ServiceUnavailableRetryStrategy retryStrategy) { + super(); + Args.notNull(client, "HttpClient"); + Args.notNull(retryStrategy, "ServiceUnavailableRetryStrategy"); + this.backend = client; + this.retryStrategy = retryStrategy; + } + + /** + * Constructs a {@code AutoRetryHttpClient} with default caching settings that + * stores cache entries in memory and uses a vanilla + * {@link DefaultHttpClient} for backend requests. + */ + public AutoRetryHttpClient() { + this(new DefaultHttpClient(), new DefaultServiceUnavailableRetryStrategy()); + } + + /** + * Constructs a {@code AutoRetryHttpClient} with the given caching options that + * stores cache entries in memory and uses a vanilla + * {@link DefaultHttpClient} for backend requests. + * + * @param config + * retry configuration module options + */ + public AutoRetryHttpClient(final ServiceUnavailableRetryStrategy config) { + this(new DefaultHttpClient(), config); + } + + /** + * Constructs a {@code AutoRetryHttpClient} with default caching settings that + * stores cache entries in memory and uses the given {@link HttpClient} for + * backend requests. + * + * @param client + * used to make origin requests + */ + public AutoRetryHttpClient(final HttpClient client) { + this(client, new DefaultServiceUnavailableRetryStrategy()); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request) + throws IOException { + final HttpContext defaultContext = null; + return execute(target, request, defaultContext); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException { + return execute(target, request, responseHandler, null); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException { + final HttpResponse resp = execute(target, request, context); + return responseHandler.handleResponse(resp); + } + + public HttpResponse execute(final HttpUriRequest request) throws IOException { + final HttpContext context = null; + return execute(request, context); + } + + public HttpResponse execute(final HttpUriRequest request, final HttpContext context) + throws IOException { + final URI uri = request.getURI(); + final HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), + uri.getScheme()); + return execute(httpHost, request, context); + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException { + return execute(request, responseHandler, null); + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException { + final HttpResponse resp = execute(request, context); + return responseHandler.handleResponse(resp); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request, + final HttpContext context) throws IOException { + for (int c = 1;; c++) { + final HttpResponse response = backend.execute(target, request, context); + try { + if (retryStrategy.retryRequest(response, c, context)) { + EntityUtils.consume(response.getEntity()); + final long nextInterval = retryStrategy.getRetryInterval(); + try { + log.trace("Wait for " + nextInterval); + Thread.sleep(nextInterval); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedIOException(); + } + } else { + return response; + } + } catch (final RuntimeException ex) { + try { + EntityUtils.consume(response.getEntity()); + } catch (final IOException ioex) { + log.warn("I/O error consuming response content", ioex); + } + throw ex; + } + } + } + + public ClientConnectionManager getConnectionManager() { + return backend.getConnectionManager(); + } + + public HttpParams getParams() { + return backend.getParams(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java new file mode 100644 index 000000000..810f73a0a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java @@ -0,0 +1,105 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.HashMap; + +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.client.AuthCache; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.UnsupportedSchemeException; +import ch.boye.httpclientandroidlib.impl.conn.DefaultSchemePortResolver; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of {@link AuthCache}. + * + * @since 4.0 + */ +@NotThreadSafe +public class BasicAuthCache implements AuthCache { + + private final HashMap<HttpHost, AuthScheme> map; + private final SchemePortResolver schemePortResolver; + + /** + * Default constructor. + * + * @since 4.3 + */ + public BasicAuthCache(final SchemePortResolver schemePortResolver) { + super(); + this.map = new HashMap<HttpHost, AuthScheme>(); + this.schemePortResolver = schemePortResolver != null ? schemePortResolver : + DefaultSchemePortResolver.INSTANCE; + } + + public BasicAuthCache() { + this(null); + } + + protected HttpHost getKey(final HttpHost host) { + if (host.getPort() <= 0) { + final int port; + try { + port = schemePortResolver.resolve(host); + } catch (final UnsupportedSchemeException ignore) { + return host; + } + return new HttpHost(host.getHostName(), port, host.getSchemeName()); + } else { + return host; + } + } + + public void put(final HttpHost host, final AuthScheme authScheme) { + Args.notNull(host, "HTTP host"); + this.map.put(getKey(host), authScheme); + } + + public AuthScheme get(final HttpHost host) { + Args.notNull(host, "HTTP host"); + return this.map.get(getKey(host)); + } + + public void remove(final HttpHost host) { + Args.notNull(host, "HTTP host"); + this.map.remove(getKey(host)); + } + + public void clear() { + this.map.clear(); + } + + @Override + public String toString() { + return this.map.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java new file mode 100644 index 000000000..624a8f81d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java @@ -0,0 +1,144 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieIdentityComparator; + +/** + * Default implementation of {@link CookieStore} + * + * + * @since 4.0 + */ +@ThreadSafe +public class BasicCookieStore implements CookieStore, Serializable { + + private static final long serialVersionUID = -7581093305228232025L; + + @GuardedBy("this") + private final TreeSet<Cookie> cookies; + + public BasicCookieStore() { + super(); + this.cookies = new TreeSet<Cookie>(new CookieIdentityComparator()); + } + + /** + * Adds an {@link Cookie HTTP cookie}, replacing any existing equivalent cookies. + * If the given cookie has already expired it will not be added, but existing + * values will still be removed. + * + * @param cookie the {@link Cookie cookie} to be added + * + * @see #addCookies(Cookie[]) + * + */ + public synchronized void addCookie(final Cookie cookie) { + if (cookie != null) { + // first remove any old cookie that is equivalent + cookies.remove(cookie); + if (!cookie.isExpired(new Date())) { + cookies.add(cookie); + } + } + } + + /** + * Adds an array of {@link Cookie HTTP cookies}. Cookies are added individually and + * in the given array order. If any of the given cookies has already expired it will + * not be added, but existing values will still be removed. + * + * @param cookies the {@link Cookie cookies} to be added + * + * @see #addCookie(Cookie) + * + */ + public synchronized void addCookies(final Cookie[] cookies) { + if (cookies != null) { + for (final Cookie cooky : cookies) { + this.addCookie(cooky); + } + } + } + + /** + * Returns an immutable array of {@link Cookie cookies} that this HTTP + * state currently contains. + * + * @return an array of {@link Cookie cookies}. + */ + public synchronized List<Cookie> getCookies() { + //create defensive copy so it won't be concurrently modified + return new ArrayList<Cookie>(cookies); + } + + /** + * Removes all of {@link Cookie cookies} in this HTTP state + * that have expired by the specified {@link java.util.Date date}. + * + * @return true if any cookies were purged. + * + * @see Cookie#isExpired(Date) + */ + public synchronized boolean clearExpired(final Date date) { + if (date == null) { + return false; + } + boolean removed = false; + for (final Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (it.next().isExpired(date)) { + it.remove(); + removed = true; + } + } + return removed; + } + + /** + * Clears all cookies. + */ + public synchronized void clear() { + cookies.clear(); + } + + @Override + public synchronized String toString() { + return cookies.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java new file mode 100644 index 000000000..99c5cccc2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java @@ -0,0 +1,109 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of {@link CredentialsProvider}. + * + * @since 4.0 + */ +@ThreadSafe +public class BasicCredentialsProvider implements CredentialsProvider { + + private final ConcurrentHashMap<AuthScope, Credentials> credMap; + + /** + * Default constructor. + */ + public BasicCredentialsProvider() { + super(); + this.credMap = new ConcurrentHashMap<AuthScope, Credentials>(); + } + + public void setCredentials( + final AuthScope authscope, + final Credentials credentials) { + Args.notNull(authscope, "Authentication scope"); + credMap.put(authscope, credentials); + } + + /** + * Find matching {@link Credentials credentials} for the given authentication scope. + * + * @param map the credentials hash map + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + */ + private static Credentials matchCredentials( + final Map<AuthScope, Credentials> map, + final AuthScope authscope) { + // see if we get a direct hit + Credentials creds = map.get(authscope); + if (creds == null) { + // Nope. + // Do a full scan + int bestMatchFactor = -1; + AuthScope bestMatch = null; + for (final AuthScope current: map.keySet()) { + final int factor = authscope.match(current); + if (factor > bestMatchFactor) { + bestMatchFactor = factor; + bestMatch = current; + } + } + if (bestMatch != null) { + creds = map.get(bestMatch); + } + } + return creds; + } + + public Credentials getCredentials(final AuthScope authscope) { + Args.notNull(authscope, "Authentication scope"); + return matchCredentials(this.credMap, authscope); + } + + public void clear() { + this.credMap.clear(); + } + + @Override + public String toString() { + return credMap.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java new file mode 100644 index 000000000..13ae4f856 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java @@ -0,0 +1,73 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.HttpResponseException; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * A {@link ResponseHandler} that returns the response body as a String + * for successful (2xx) responses. If the response code was >= 300, the response + * body is consumed and an {@link HttpResponseException} is thrown. + * <p/> + * If this is used with + * {@link ch.boye.httpclientandroidlib.client.HttpClient#execute( + * ch.boye.httpclientandroidlib.client.methods.HttpUriRequest, ResponseHandler)}, + * HttpClient may handle redirects (3xx responses) internally. + * + * @since 4.0 + */ +@Immutable +public class BasicResponseHandler implements ResponseHandler<String> { + + /** + * Returns the response body as a String if the response was successful (a + * 2xx status code). If no response body exists, this returns null. If the + * response was unsuccessful (>= 300 status code), throws an + * {@link HttpResponseException}. + */ + public String handleResponse(final HttpResponse response) + throws HttpResponseException, IOException { + final StatusLine statusLine = response.getStatusLine(); + final HttpEntity entity = response.getEntity(); + if (statusLine.getStatusCode() >= 300) { + EntityUtils.consume(entity); + throw new HttpResponseException(statusLine.getStatusCode(), + statusLine.getReasonPhrase()); + } + return entity == null ? null : EntityUtils.toString(entity); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java new file mode 100644 index 000000000..37f5daedd --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java @@ -0,0 +1,269 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.params.AbstractHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Represents a stack of parameter collections. + * When retrieving a parameter, the stack is searched in a fixed order + * and the first match returned. Setting parameters via the stack is + * not supported. To minimize overhead, the stack has a fixed size and + * does not maintain an internal array. + * The supported stack entries, sorted by increasing priority, are: + * <ol> + * <li>Application parameters: + * expected to be the same for all clients used by an application. + * These provide "global", that is application-wide, defaults. + * </li> + * <li>Client parameters: + * specific to an instance of + * {@link ch.boye.httpclientandroidlib.client.HttpClient HttpClient}. + * These provide client specific defaults. + * </li> + * <li>Request parameters: + * specific to a single request execution. + * For overriding client and global defaults. + * </li> + * <li>Override parameters: + * specific to an instance of + * {@link ch.boye.httpclientandroidlib.client.HttpClient HttpClient}. + * These can be used to set parameters that cannot be overridden + * on a per-request basis. + * </li> + * </ol> + * Each stack entry may be <code>null</code>. That is preferable over + * an empty params collection, since it avoids searching the empty collection + * when looking up parameters. + * + * @since 4.0 + * + * @deprecated (4.3) use configuration classes provided 'ch.boye.httpclientandroidlib.config' + * and 'ch.boye.httpclientandroidlib.client.config' + */ +@NotThreadSafe +@Deprecated +public class ClientParamsStack extends AbstractHttpParams { + + /** The application parameter collection, or <code>null</code>. */ + protected final HttpParams applicationParams; + + /** The client parameter collection, or <code>null</code>. */ + protected final HttpParams clientParams; + + /** The request parameter collection, or <code>null</code>. */ + protected final HttpParams requestParams; + + /** The override parameter collection, or <code>null</code>. */ + protected final HttpParams overrideParams; + + + /** + * Creates a new parameter stack from elements. + * The arguments will be stored as-is, there is no copying to + * prevent modification. + * + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(final HttpParams aparams, final HttpParams cparams, + final HttpParams rparams, final HttpParams oparams) { + applicationParams = aparams; + clientParams = cparams; + requestParams = rparams; + overrideParams = oparams; + } + + + /** + * Creates a copy of a parameter stack. + * The new stack will have the exact same entries as the argument stack. + * There is no copying of parameters. + * + * @param stack the stack to copy + */ + public ClientParamsStack(final ClientParamsStack stack) { + this(stack.getApplicationParams(), + stack.getClientParams(), + stack.getRequestParams(), + stack.getOverrideParams()); + } + + + /** + * Creates a modified copy of a parameter stack. + * The new stack will contain the explicitly passed elements. + * For elements where the explicit argument is <code>null</code>, + * the corresponding element from the argument stack is used. + * There is no copying of parameters. + * + * @param stack the stack to modify + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(final ClientParamsStack stack, + final HttpParams aparams, final HttpParams cparams, + final HttpParams rparams, final HttpParams oparams) { + this((aparams != null) ? aparams : stack.getApplicationParams(), + (cparams != null) ? cparams : stack.getClientParams(), + (rparams != null) ? rparams : stack.getRequestParams(), + (oparams != null) ? oparams : stack.getOverrideParams()); + } + + + /** + * Obtains the application parameters of this stack. + * + * @return the application parameters, or <code>null</code> + */ + public final HttpParams getApplicationParams() { + return applicationParams; + } + + /** + * Obtains the client parameters of this stack. + * + * @return the client parameters, or <code>null</code> + */ + public final HttpParams getClientParams() { + return clientParams; + } + + /** + * Obtains the request parameters of this stack. + * + * @return the request parameters, or <code>null</code> + */ + public final HttpParams getRequestParams() { + return requestParams; + } + + /** + * Obtains the override parameters of this stack. + * + * @return the override parameters, or <code>null</code> + */ + public final HttpParams getOverrideParams() { + return overrideParams; + } + + + /** + * Obtains a parameter from this stack. + * See class comment for search order. + * + * @param name the name of the parameter to obtain + * + * @return the highest-priority value for that parameter, or + * <code>null</code> if it is not set anywhere in this stack + */ + public Object getParameter(final String name) { + Args.notNull(name, "Parameter name"); + + Object result = null; + + if (overrideParams != null) { + result = overrideParams.getParameter(name); + } + if ((result == null) && (requestParams != null)) { + result = requestParams.getParameter(name); + } + if ((result == null) && (clientParams != null)) { + result = clientParams.getParameter(name); + } + if ((result == null) && (applicationParams != null)) { + result = applicationParams.getParameter(name); + } + return result; + } + + /** + * Does <i>not</i> set a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * @param value ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public HttpParams setParameter(final String name, final Object value) + throws UnsupportedOperationException { + + throw new UnsupportedOperationException + ("Setting parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> remove a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public boolean removeParameter(final String name) { + throw new UnsupportedOperationException + ("Removing parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> copy parameters. + * Parameter stacks are lightweight objects, expected to be instantiated + * as needed and to be used only in a very specific context. On top of + * that, they are read-only. The typical copy operation to prevent + * accidental modification of parameters passed by the application to + * a framework object is therefore pointless and disabled. + * Create a new stack if you really need a copy. + * <br/> + * Derived classes may change this behavior. + * + * @return <code>this</code> parameter stack + */ + public HttpParams copy() { + return this; + } + + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/Clock.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/Clock.java new file mode 100644 index 000000000..14eeec4a0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/Clock.java @@ -0,0 +1,43 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +/** + * Interface used to enable easier testing of time-related behavior. + * + * @since 4.2 + * + */ +interface Clock { + + /** + * Returns the current time, expressed as the number of + * milliseconds since the epoch. + * @return current time + */ + long getCurrentTime(); +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java new file mode 100644 index 000000000..c2fc5a5de --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java @@ -0,0 +1,244 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URI; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * Base implementation of {@link HttpClient} that also implements {@link Closeable}. + * + * @since 4.3 + */ +@ThreadSafe +public abstract class CloseableHttpClient implements HttpClient, Closeable { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + protected abstract CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, + HttpContext context) throws IOException, ClientProtocolException; + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + return doExecute(target, request, context); + } + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpUriRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + Args.notNull(request, "HTTP request"); + return doExecute(determineTarget(request), request, context); + } + + private static HttpHost determineTarget(final HttpUriRequest request) throws ClientProtocolException { + // A null target may be acceptable if there is a default target. + // Otherwise, the null target is detected in the director. + HttpHost target = null; + + final URI requestURI = request.getURI(); + if (requestURI.isAbsolute()) { + target = URIUtils.extractHost(requestURI); + if (target == null) { + throw new ClientProtocolException("URI does not specify a valid host name: " + + requestURI); + } + } + return target; + } + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpUriRequest request) throws IOException, ClientProtocolException { + return execute(request, (HttpContext) null); + } + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpHost target, + final HttpRequest request) throws IOException, ClientProtocolException { + return doExecute(target, request, (HttpContext) null); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param request the request to execute + * @param responseHandler the response handler + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(request, responseHandler, null); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param request the request to execute + * @param responseHandler the response handler + * @param context the context to use for the execution, or + * <code>null</code> to use the default context + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + final HttpHost target = determineTarget(request); + return execute(target, request, responseHandler, context); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(target, request, responseHandler, null); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * @param context the context to use for the execution, or + * <code>null</code> to use the default context + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + Args.notNull(responseHandler, "Response handler"); + + final HttpResponse response = execute(target, request, context); + + final T result; + try { + result = responseHandler.handleResponse(response); + } catch (final Exception t) { + final HttpEntity entity = response.getEntity(); + try { + EntityUtils.consume(entity); + } catch (final Exception t2) { + // Log this exception. The original exception is more + // important and will be thrown to the caller. + this.log.warn("Error consuming content after an exception.", t2); + } + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + if (t instanceof IOException) { + throw (IOException) t; + } + throw new UndeclaredThrowableException(t); + } + + // Handling the response was successful. Ensure that the content has + // been fully consumed. + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + return result; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java new file mode 100644 index 000000000..9869740df --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java @@ -0,0 +1,87 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * @since 4.3 + */ +@NotThreadSafe +class CloseableHttpResponseProxy implements InvocationHandler { + + private final HttpResponse original; + + CloseableHttpResponseProxy(final HttpResponse original) { + super(); + this.original = original; + } + + public void close() throws IOException { + final HttpEntity entity = this.original.getEntity(); + EntityUtils.consume(entity); + } + + public Object invoke( + final Object proxy, final Method method, final Object[] args) throws Throwable { + final String mname = method.getName(); + if (mname.equals("close")) { + close(); + return null; + } else { + try { + return method.invoke(original, args); + } catch (final InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause != null) { + throw cause; + } else { + throw ex; + } + } + } + } + + public static CloseableHttpResponse newProxy(final HttpResponse original) { + return (CloseableHttpResponse) Proxy.newProxyInstance( + CloseableHttpResponseProxy.class.getClassLoader(), + new Class<?>[] { CloseableHttpResponse.class }, + new CloseableHttpResponseProxy(original)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java new file mode 100644 index 000000000..f4169225e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java @@ -0,0 +1,93 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.protocol.RequestAcceptEncoding; +import ch.boye.httpclientandroidlib.client.protocol.ResponseContentEncoding; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpProcessor; + +/** + * {@link DefaultHttpClient} sub-class which includes a {@link RequestAcceptEncoding} + * for the request and response. + * + * <b>Deprecation note:</b> due to the way this class modifies a response body + * without changing the response headers to reflect the entity changes, it cannot + * be used as the "backend" for a caching {@link + * ch.boye.httpclientandroidlib.client.HttpClient} and still have uncompressed responses be cached. + * Users are encouraged to use the {@link DecompressingHttpClient} instead + * of this class, which can be wired in either before or after caching, depending on + * whether you want to cache responses in compressed or uncompressed form. + * + * @since 4.1 + * + * @deprecated (4.2) use {@link HttpClientBuilder} + */ +@Deprecated +@ThreadSafe // since DefaultHttpClient is +public class ContentEncodingHttpClient extends DefaultHttpClient { + + /** + * Creates a new HTTP client from parameters and a connection manager. + * + * @param params the parameters + * @param conman the connection manager + */ + public ContentEncodingHttpClient(final ClientConnectionManager conman, final HttpParams params) { + super(conman, params); + } + + /** + * @param params + */ + public ContentEncodingHttpClient(final HttpParams params) { + this(null, params); + } + + /** + * + */ + public ContentEncodingHttpClient() { + this(null); + } + + /** + * {@inheritDoc} + */ + @Override + protected BasicHttpProcessor createHttpProcessor() { + final BasicHttpProcessor result = super.createHttpProcessor(); + + result.addRequestInterceptor(new RequestAcceptEncoding()); + result.addResponseInterceptor(new ResponseContentEncoding()); + + return result; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java new file mode 100644 index 000000000..4d55303ad --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java @@ -0,0 +1,214 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.net.URI; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestInterceptor; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseInterceptor; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.protocol.RequestAcceptEncoding; +import ch.boye.httpclientandroidlib.client.protocol.ResponseContentEncoding; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * <p>Decorator adding support for compressed responses. This class sets + * the <code>Accept-Encoding</code> header on requests to indicate + * support for the <code>gzip</code> and <code>deflate</code> + * compression schemes; it then checks the <code>Content-Encoding</code> + * header on the response to uncompress any compressed response bodies. + * The {@link java.io.InputStream} of the entity will contain the uncompressed + * content.</p> + * + * <p><b>N.B.</b> Any upstream clients of this class need to be aware that + * this effectively obscures visibility into the length of a server + * response body, since the <code>Content-Length</code> header will + * correspond to the compressed entity length received from the server, + * but the content length experienced by reading the response body may + * be different (hopefully higher!).</p> + * + * <p>That said, this decorator is compatible with the + * <code>CachingHttpClient</code> in that the two decorators can be added + * in either order and still have cacheable responses be cached.</p> + * + * @since 4.2 + * + * @deprecated (4.3) use {@link HttpClientBuilder} + */ +@Deprecated +public class DecompressingHttpClient implements HttpClient { + + private final HttpClient backend; + private final HttpRequestInterceptor acceptEncodingInterceptor; + private final HttpResponseInterceptor contentEncodingInterceptor; + + /** + * Constructs a decorator to ask for and handle compressed + * entities on the fly. + */ + public DecompressingHttpClient() { + this(new DefaultHttpClient()); + } + + /** + * Constructs a decorator to ask for and handle compressed + * entities on the fly. + * @param backend the {@link HttpClient} to use for actually + * issuing requests + */ + public DecompressingHttpClient(final HttpClient backend) { + this(backend, new RequestAcceptEncoding(), new ResponseContentEncoding()); + } + + DecompressingHttpClient(final HttpClient backend, + final HttpRequestInterceptor requestInterceptor, + final HttpResponseInterceptor responseInterceptor) { + this.backend = backend; + this.acceptEncodingInterceptor = requestInterceptor; + this.contentEncodingInterceptor = responseInterceptor; + } + + public HttpParams getParams() { + return backend.getParams(); + } + + public ClientConnectionManager getConnectionManager() { + return backend.getConnectionManager(); + } + + public HttpResponse execute(final HttpUriRequest request) throws IOException, + ClientProtocolException { + return execute(getHttpHost(request), request, (HttpContext)null); + } + + /** + * Gets the HttpClient to issue request. + * + * @return the HttpClient to issue request + */ + public HttpClient getHttpClient() { + return this.backend; + } + + HttpHost getHttpHost(final HttpUriRequest request) { + final URI uri = request.getURI(); + return URIUtils.extractHost(uri); + } + + public HttpResponse execute(final HttpUriRequest request, final HttpContext context) + throws IOException, ClientProtocolException { + return execute(getHttpHost(request), request, context); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request) + throws IOException, ClientProtocolException { + return execute(target, request, (HttpContext)null); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + try { + final HttpContext localContext = context != null ? context : new BasicHttpContext(); + final HttpRequest wrapped; + if (request instanceof HttpEntityEnclosingRequest) { + wrapped = new EntityEnclosingRequestWrapper((HttpEntityEnclosingRequest) request); + } else { + wrapped = new RequestWrapper(request); + } + acceptEncodingInterceptor.process(wrapped, localContext); + final HttpResponse response = backend.execute(target, wrapped, localContext); + try { + contentEncodingInterceptor.process(response, localContext); + if (Boolean.TRUE.equals(localContext.getAttribute(ResponseContentEncoding.UNCOMPRESSED))) { + response.removeHeaders("Content-Length"); + response.removeHeaders("Content-Encoding"); + response.removeHeaders("Content-MD5"); + } + return response; + } catch (final HttpException ex) { + EntityUtils.consume(response.getEntity()); + throw ex; + } catch (final IOException ex) { + EntityUtils.consume(response.getEntity()); + throw ex; + } catch (final RuntimeException ex) { + EntityUtils.consume(response.getEntity()); + throw ex; + } + } catch (final HttpException e) { + throw new ClientProtocolException(e); + } + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(getHttpHost(request), request, responseHandler); + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + return execute(getHttpHost(request), request, responseHandler, context); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(target, request, responseHandler, null); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + final HttpResponse response = execute(target, request, context); + try { + return responseHandler.handleResponse(response); + } finally { + final HttpEntity entity = response.getEntity(); + if (entity != null) { + EntityUtils.consume(entity); + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java new file mode 100644 index 000000000..5a5e67caa --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java @@ -0,0 +1,54 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.ConnectException; +import java.net.SocketTimeoutException; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; + +/** + * This {@link ConnectionBackoffStrategy} backs off either for a raw + * network socket or connection timeout or if the server explicitly + * sends a 503 (Service Unavailable) response. + * + * @since 4.2 + */ +public class DefaultBackoffStrategy implements ConnectionBackoffStrategy { + + public boolean shouldBackoff(final Throwable t) { + return (t instanceof SocketTimeoutException + || t instanceof ConnectException); + } + + public boolean shouldBackoff(final HttpResponse resp) { + return (resp.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java new file mode 100644 index 000000000..a5524bd43 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HeaderElementIterator; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.message.BasicHeaderElementIterator; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of a strategy deciding duration + * that a connection can remain idle. + * + * The default implementation looks solely at the 'Keep-Alive' + * header's timeout token. + * + * @since 4.0 + */ +@Immutable +public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { + + public static final DefaultConnectionKeepAliveStrategy INSTANCE = new DefaultConnectionKeepAliveStrategy(); + + public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) { + Args.notNull(response, "HTTP response"); + 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) * 1000; + } catch(final NumberFormatException ignore) { + } + } + } + return -1; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java new file mode 100644 index 000000000..55e5ab07e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java @@ -0,0 +1,225 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.protocol.RequestAddCookies; +import ch.boye.httpclientandroidlib.client.protocol.RequestAuthCache; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.client.protocol.RequestDefaultHeaders; +import ch.boye.httpclientandroidlib.client.protocol.RequestProxyAuthentication; +import ch.boye.httpclientandroidlib.client.protocol.RequestTargetAuthentication; +import ch.boye.httpclientandroidlib.client.protocol.ResponseProcessCookies; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpConnectionParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.params.SyncBasicHttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.RequestContent; +import ch.boye.httpclientandroidlib.protocol.RequestExpectContinue; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.protocol.RequestUserAgent; + +/** + * Default implementation of {@link ch.boye.httpclientandroidlib.client.HttpClient} pre-configured + * for most common use scenarios. + * <p> + * Please see the Javadoc for {@link #createHttpProcessor()} for the details of the interceptors + * that are set up by default. + * <p> + * Additional interceptors can be added as follows, but + * take care not to add the same interceptor more than once. + * <pre> + * DefaultHttpClient httpclient = new DefaultHttpClient(); + * httpclient.addRequestInterceptor(new RequestAcceptEncoding()); + * httpclient.addResponseInterceptor(new ResponseContentEncoding()); + * </pre> + * <p> + * This class sets up the following parameters if not explicitly set: + * <ul> + * <li>Version: HttpVersion.HTTP_1_1</li> + * <li>ContentCharset: HTTP.DEFAULT_CONTENT_CHARSET</li> + * <li>NoTcpDelay: true</li> + * <li>SocketBufferSize: 8192</li> + * <li>UserAgent: Apache-HttpClient/release (java 1.5)</li> + * </ul> + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> + * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) use {@link HttpClientBuilder}. + */ +@ThreadSafe +@Deprecated +public class DefaultHttpClient extends AbstractHttpClient { + + /** + * Creates a new HTTP client from parameters and a connection manager. + * + * @param params the parameters + * @param conman the connection manager + */ + public DefaultHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + super(conman, params); + } + + + /** + * @since 4.1 + */ + public DefaultHttpClient( + final ClientConnectionManager conman) { + super(conman, null); + } + + + public DefaultHttpClient(final HttpParams params) { + super(null, params); + } + + + public DefaultHttpClient() { + super(null, null); + } + + + /** + * Creates the default set of HttpParams by invoking {@link DefaultHttpClient#setDefaultHttpParams(HttpParams)} + * + * @return a new instance of {@link SyncBasicHttpParams} with the defaults applied to it. + */ + @Override + protected HttpParams createHttpParams() { + final HttpParams params = new SyncBasicHttpParams(); + setDefaultHttpParams(params); + return params; + } + + /** + * Saves the default set of HttpParams in the provided parameter. + * These are: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}: + * 1.1</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_CONTENT_CHARSET}: + * ISO-8859-1</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}: + * true</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}: + * 8192</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}: + * Apache-HttpClient/<release> (java 1.5)</li> + * </ul> + */ + public static void setDefaultHttpParams(final HttpParams params) { + HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(params, HTTP.DEF_CONTENT_CHARSET.name()); + HttpConnectionParams.setTcpNoDelay(params, true); + HttpConnectionParams.setSocketBufferSize(params, 8192); + HttpProtocolParams.setUserAgent(params, HttpClientBuilder.DEFAULT_USER_AGENT); + } + + /** + * Create the processor with the following interceptors: + * <ul> + * <li>{@link RequestDefaultHeaders}</li> + * <li>{@link RequestContent}</li> + * <li>{@link RequestTargetHost}</li> + * <li>{@link RequestClientConnControl}</li> + * <li>{@link RequestUserAgent}</li> + * <li>{@link RequestExpectContinue}</li> + * <li>{@link RequestAddCookies}</li> + * <li>{@link ResponseProcessCookies}</li> + * <li>{@link RequestAuthCache}</li> + * <li>{@link RequestTargetAuthentication}</li> + * <li>{@link RequestProxyAuthentication}</li> + * </ul> + * <p> + * @return the processor with the added interceptors. + */ + @Override + protected BasicHttpProcessor createHttpProcessor() { + final BasicHttpProcessor httpproc = new BasicHttpProcessor(); + httpproc.addInterceptor(new RequestDefaultHeaders()); + // Required protocol interceptors + httpproc.addInterceptor(new RequestContent()); + httpproc.addInterceptor(new RequestTargetHost()); + // Recommended protocol interceptors + httpproc.addInterceptor(new RequestClientConnControl()); + httpproc.addInterceptor(new RequestUserAgent()); + httpproc.addInterceptor(new RequestExpectContinue()); + // HTTP state management interceptors + httpproc.addInterceptor(new RequestAddCookies()); + httpproc.addInterceptor(new ResponseProcessCookies()); + // HTTP authentication interceptors + httpproc.addInterceptor(new RequestAuthCache()); + httpproc.addInterceptor(new RequestTargetAuthentication()); + httpproc.addInterceptor(new RequestProxyAuthentication()); + return httpproc; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java new file mode 100644 index 000000000..1721c8e70 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java @@ -0,0 +1,203 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ConnectException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.net.ssl.SSLException; + +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * The default {@link HttpRequestRetryHandler} used by request executors. + * + * @since 4.0 + */ +@Immutable +public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler { + + public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler(); + + /** the number of times a method will be retried */ + private final int retryCount; + + /** Whether or not methods that have successfully sent their request will be retried */ + private final boolean requestSentRetryEnabled; + + private final Set<Class<? extends IOException>> nonRetriableClasses; + + /** + * Create the request retry handler using the specified IOException classes + * + * @param retryCount how many times to retry; 0 means no retries + * @param requestSentRetryEnabled true if it's OK to retry requests that have been sent + * @param clazzes the IOException types that should not be retried + * @since 4.3 + */ + protected DefaultHttpRequestRetryHandler( + final int retryCount, + final boolean requestSentRetryEnabled, + final Collection<Class<? extends IOException>> clazzes) { + super(); + this.retryCount = retryCount; + this.requestSentRetryEnabled = requestSentRetryEnabled; + this.nonRetriableClasses = new HashSet<Class<? extends IOException>>(); + for (final Class<? extends IOException> clazz: clazzes) { + this.nonRetriableClasses.add(clazz); + } + } + + /** + * Create the request retry handler using the following list of + * non-retriable IOException classes: <br> + * <ul> + * <li>InterruptedIOException</li> + * <li>UnknownHostException</li> + * <li>ConnectException</li> + * <li>SSLException</li> + * </ul> + * @param retryCount how many times to retry; 0 means no retries + * @param requestSentRetryEnabled true if it's OK to retry requests that have been sent + */ + @SuppressWarnings("unchecked") + public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) { + this(retryCount, requestSentRetryEnabled, Arrays.asList( + InterruptedIOException.class, + UnknownHostException.class, + ConnectException.class, + SSLException.class)); + } + + /** + * Create the request retry handler with a retry count of 3, requestSentRetryEnabled false + * and using the following list of non-retriable IOException classes: <br> + * <ul> + * <li>InterruptedIOException</li> + * <li>UnknownHostException</li> + * <li>ConnectException</li> + * <li>SSLException</li> + * </ul> + */ + public DefaultHttpRequestRetryHandler() { + this(3, false); + } + /** + * Used <code>retryCount</code> and <code>requestSentRetryEnabled</code> to determine + * if the given method should be retried. + */ + public boolean retryRequest( + final IOException exception, + final int executionCount, + final HttpContext context) { + Args.notNull(exception, "Exception parameter"); + Args.notNull(context, "HTTP context"); + if (executionCount > this.retryCount) { + // Do not retry if over max retry count + return false; + } + if (this.nonRetriableClasses.contains(exception.getClass())) { + return false; + } else { + for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) { + if (rejectException.isInstance(exception)) { + return false; + } + } + } + final HttpClientContext clientContext = HttpClientContext.adapt(context); + final HttpRequest request = clientContext.getRequest(); + + if(requestIsAborted(request)){ + return false; + } + + if (handleAsIdempotent(request)) { + // Retry if the request is considered idempotent + return true; + } + + if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) { + // Retry if the request has not been sent fully or + // if it's OK to retry methods that have been sent + return true; + } + // otherwise do not retry + return false; + } + + /** + * @return <code>true</code> if this handler will retry methods that have + * successfully sent their request, <code>false</code> otherwise + */ + public boolean isRequestSentRetryEnabled() { + return requestSentRetryEnabled; + } + + /** + * @return the maximum number of times a method will be retried + */ + public int getRetryCount() { + return retryCount; + } + + /** + * @since 4.2 + */ + protected boolean handleAsIdempotent(final HttpRequest request) { + return !(request instanceof HttpEntityEnclosingRequest); + } + + /** + * @since 4.2 + * + * @deprecated (4.3) + */ + @Deprecated + protected boolean requestIsAborted(final HttpRequest request) { + HttpRequest req = request; + if (request instanceof RequestWrapper) { // does not forward request to original + req = ((RequestWrapper) request).getOriginal(); + } + return (req instanceof HttpUriRequest && ((HttpUriRequest)req).isAborted()); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java new file mode 100644 index 000000000..a70057ae5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java @@ -0,0 +1,90 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.List; +import java.util.Map; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.auth.params.AuthPNames; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationHandler} implementation + * for proxy host authentication. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ProxyAuthenticationStrategy} + */ +@Deprecated +@Immutable +public class DefaultProxyAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultProxyAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + final int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(response, "HTTP response"); + final Header[] headers = response.getHeaders(AUTH.PROXY_AUTH); + return parseChallenges(headers); + } + + @Override + protected List<String> getAuthPreferences( + final HttpResponse response, + final HttpContext context) { + @SuppressWarnings("unchecked") + final + List<String> authpref = (List<String>) response.getParams().getParameter( + AuthPNames.PROXY_AUTH_PREF); + if (authpref != null) { + return authpref; + } else { + return super.getAuthPreferences(response, context); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectHandler.java new file mode 100644 index 000000000..6bd58c035 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectHandler.java @@ -0,0 +1,180 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.CircularRedirectException; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.ExecutionContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * Default implementation of {@link RedirectHandler}. + * + * @since 4.0 + * + * @deprecated (4.1) use {@link DefaultRedirectStrategy}. + */ +@Immutable +@Deprecated +public class DefaultRedirectHandler implements RedirectHandler { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; + + public DefaultRedirectHandler() { + super(); + } + + public boolean isRedirectRequested( + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + + final int statusCode = response.getStatusLine().getStatusCode(); + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_TEMPORARY_REDIRECT: + final HttpRequest request = (HttpRequest) context.getAttribute( + ExecutionContext.HTTP_REQUEST); + final String method = request.getRequestLine().getMethod(); + return method.equalsIgnoreCase(HttpGet.METHOD_NAME) + || method.equalsIgnoreCase(HttpHead.METHOD_NAME); + case HttpStatus.SC_SEE_OTHER: + return true; + default: + return false; + } //end of switch + } + + public URI getLocationURI( + final HttpResponse response, + final HttpContext context) throws ProtocolException { + Args.notNull(response, "HTTP response"); + //get the location header to find out where to redirect to + final Header locationHeader = response.getFirstHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + throw new ProtocolException( + "Received redirect response " + response.getStatusLine() + + " but no location header"); + } + final String location = locationHeader.getValue(); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirect requested to location '" + location + "'"); + } + + URI uri; + try { + uri = new URI(location); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid redirect URI: " + location, ex); + } + + final HttpParams params = response.getParams(); + // rfc2616 demands the location value be a complete URI + // Location = "Location" ":" absoluteURI + if (!uri.isAbsolute()) { + if (params.isParameterTrue(ClientPNames.REJECT_RELATIVE_REDIRECT)) { + throw new ProtocolException("Relative redirect location '" + + uri + "' not allowed"); + } + // Adjust location URI + final HttpHost target = (HttpHost) context.getAttribute( + ExecutionContext.HTTP_TARGET_HOST); + Asserts.notNull(target, "Target host"); + + final HttpRequest request = (HttpRequest) context.getAttribute( + ExecutionContext.HTTP_REQUEST); + + try { + final URI requestURI = new URI(request.getRequestLine().getUri()); + final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, true); + uri = URIUtils.resolve(absoluteRequestURI, uri); + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } + + if (params.isParameterFalse(ClientPNames.ALLOW_CIRCULAR_REDIRECTS)) { + + RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute( + REDIRECT_LOCATIONS); + + if (redirectLocations == null) { + redirectLocations = new RedirectLocations(); + context.setAttribute(REDIRECT_LOCATIONS, redirectLocations); + } + + final URI redirectURI; + if (uri.getFragment() != null) { + try { + final HttpHost target = new HttpHost( + uri.getHost(), + uri.getPort(), + uri.getScheme()); + redirectURI = URIUtils.rewriteURI(uri, target, true); + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } else { + redirectURI = uri; + } + + if (redirectLocations.contains(redirectURI)) { + throw new CircularRedirectException("Circular redirect to '" + + redirectURI + "'"); + } else { + redirectLocations.add(redirectURI); + } + } + + return uri; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java new file mode 100644 index 000000000..e7d9315d2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java @@ -0,0 +1,233 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.CircularRedirectException; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.methods.RequestBuilder; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.utils.URIBuilder; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.TextUtils; + +/** + * Default implementation of {@link RedirectStrategy}. This strategy honors the restrictions + * on automatic redirection of entity enclosing methods such as POST and PUT imposed by the + * HTTP specification. <tt>302 Moved Temporarily</tt>, <tt>301 Moved Permanently</tt> and + * <tt>307 Temporary Redirect</tt> status codes will result in an automatic redirect of + * HEAD and GET methods only. POST and PUT methods will not be automatically redirected + * as requiring user confirmation. + * <p/> + * The restriction on automatic redirection of POST methods can be relaxed by using + * {@link LaxRedirectStrategy} instead of {@link DefaultRedirectStrategy}. + * + * @see LaxRedirectStrategy + * @since 4.1 + */ +@Immutable +public class DefaultRedirectStrategy implements RedirectStrategy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * @deprecated (4.3) use {@link ch.boye.httpclientandroidlib.client.protocol.HttpClientContext#REDIRECT_LOCATIONS}. + */ + @Deprecated + public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; + + public static final DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy(); + + /** + * Redirectable methods. + */ + private static final String[] REDIRECT_METHODS = new String[] { + HttpGet.METHOD_NAME, + HttpHead.METHOD_NAME + }; + + public DefaultRedirectStrategy() { + super(); + } + + public boolean isRedirected( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + Args.notNull(request, "HTTP request"); + Args.notNull(response, "HTTP response"); + + final int statusCode = response.getStatusLine().getStatusCode(); + final String method = request.getRequestLine().getMethod(); + final Header locationHeader = response.getFirstHeader("location"); + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + return isRedirectable(method) && locationHeader != null; + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_TEMPORARY_REDIRECT: + return isRedirectable(method); + case HttpStatus.SC_SEE_OTHER: + return true; + default: + return false; + } //end of switch + } + + public URI getLocationURI( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + Args.notNull(request, "HTTP request"); + Args.notNull(response, "HTTP response"); + Args.notNull(context, "HTTP context"); + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + //get the location header to find out where to redirect to + final Header locationHeader = response.getFirstHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + throw new ProtocolException( + "Received redirect response " + response.getStatusLine() + + " but no location header"); + } + final String location = locationHeader.getValue(); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirect requested to location '" + location + "'"); + } + + final RequestConfig config = clientContext.getRequestConfig(); + + URI uri = createLocationURI(location); + + // rfc2616 demands the location value be a complete URI + // Location = "Location" ":" absoluteURI + try { + if (!uri.isAbsolute()) { + if (!config.isRelativeRedirectsAllowed()) { + throw new ProtocolException("Relative redirect location '" + + uri + "' not allowed"); + } + // Adjust location URI + final HttpHost target = clientContext.getTargetHost(); + Asserts.notNull(target, "Target host"); + final URI requestURI = new URI(request.getRequestLine().getUri()); + final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, false); + uri = URIUtils.resolve(absoluteRequestURI, uri); + } + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + + RedirectLocations redirectLocations = (RedirectLocations) clientContext.getAttribute( + HttpClientContext.REDIRECT_LOCATIONS); + if (redirectLocations == null) { + redirectLocations = new RedirectLocations(); + context.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, redirectLocations); + } + if (!config.isCircularRedirectsAllowed()) { + if (redirectLocations.contains(uri)) { + throw new CircularRedirectException("Circular redirect to '" + uri + "'"); + } + } + redirectLocations.add(uri); + return uri; + } + + /** + * @since 4.1 + */ + protected URI createLocationURI(final String location) throws ProtocolException { + try { + final URIBuilder b = new URIBuilder(new URI(location).normalize()); + final String host = b.getHost(); + if (host != null) { + b.setHost(host.toLowerCase(Locale.ENGLISH)); + } + final String path = b.getPath(); + if (TextUtils.isEmpty(path)) { + b.setPath("/"); + } + return b.build(); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid redirect URI: " + location, ex); + } + } + + /** + * @since 4.2 + */ + protected boolean isRedirectable(final String method) { + for (final String m: REDIRECT_METHODS) { + if (m.equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + + public HttpUriRequest getRedirect( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + final URI uri = getLocationURI(request, response, context); + final String method = request.getRequestLine().getMethod(); + if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) { + return new HttpHead(uri); + } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) { + return new HttpGet(uri); + } else { + final int status = response.getStatusLine().getStatusCode(); + if (status == HttpStatus.SC_TEMPORARY_REDIRECT) { + return RequestBuilder.copy(request).setUri(uri).build(); + } else { + return new HttpGet(uri); + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java new file mode 100644 index 000000000..b49fb7f73 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java @@ -0,0 +1,81 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * @deprecated (4.1) do not use + */ +@Immutable +@Deprecated +class DefaultRedirectStrategyAdaptor implements RedirectStrategy { + + private final RedirectHandler handler; + + public DefaultRedirectStrategyAdaptor(final RedirectHandler handler) { + super(); + this.handler = handler; + } + + public boolean isRedirected( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + return this.handler.isRedirectRequested(response, context); + } + + public HttpUriRequest getRedirect( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + final URI uri = this.handler.getLocationURI(response, context); + final String method = request.getRequestLine().getMethod(); + if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) { + return new HttpHead(uri); + } else { + return new HttpGet(uri); + } + } + + public RedirectHandler getHandler() { + return this.handler; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java new file mode 100644 index 000000000..03d9e1e5f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java @@ -0,0 +1,1150 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.NoHttpResponseException; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthProtocolState; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException; +import ch.boye.httpclientandroidlib.client.RedirectException; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.RequestDirector; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.methods.AbortableHttpRequest; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.params.HttpClientParams; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.conn.BasicManagedEntity; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.Scheme; +import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity; +import ch.boye.httpclientandroidlib.impl.auth.BasicScheme; +import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException; +import ch.boye.httpclientandroidlib.message.BasicHttpRequest; +import ch.boye.httpclientandroidlib.params.HttpConnectionParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.protocol.ExecutionContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * Default implementation of {@link RequestDirector}. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> + * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) + */ +@Deprecated +@NotThreadSafe // e.g. managedConn +public class DefaultRequestDirector implements RequestDirector { + + public HttpClientAndroidLog log; + + /** The connection manager. */ + protected final ClientConnectionManager connManager; + + /** The route planner. */ + protected final HttpRoutePlanner routePlanner; + + /** The connection re-use strategy. */ + protected final ConnectionReuseStrategy reuseStrategy; + + /** The keep-alive duration strategy. */ + protected final ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The request executor. */ + protected final HttpRequestExecutor requestExec; + + /** The HTTP protocol processor. */ + protected final HttpProcessor httpProcessor; + + /** The request retry handler. */ + protected final HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + @Deprecated + protected final RedirectHandler redirectHandler; + + /** The redirect strategy. */ + protected final RedirectStrategy redirectStrategy; + + /** The target authentication handler. */ + @Deprecated + protected final AuthenticationHandler targetAuthHandler; + + /** The target authentication handler. */ + protected final AuthenticationStrategy targetAuthStrategy; + + /** The proxy authentication handler. */ + @Deprecated + protected final AuthenticationHandler proxyAuthHandler; + + /** The proxy authentication handler. */ + protected final AuthenticationStrategy proxyAuthStrategy; + + /** The user token handler. */ + protected final UserTokenHandler userTokenHandler; + + /** The HTTP parameters. */ + protected final HttpParams params; + + /** The currently allocated connection. */ + protected ManagedClientConnection managedConn; + + protected final AuthState targetAuthState; + + protected final AuthState proxyAuthState; + + private final HttpAuthenticator authenticator; + + private int execCount; + + private int redirectCount; + + private final int maxRedirects; + + private HttpHost virtualHost; + + @Deprecated + public DefaultRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + this(new HttpClientAndroidLog(DefaultRequestDirector.class), + requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler, + new DefaultRedirectStrategyAdaptor(redirectHandler), + new AuthenticationStrategyAdaptor(targetAuthHandler), + new AuthenticationStrategyAdaptor(proxyAuthHandler), + userTokenHandler, + params); + } + + + @Deprecated + public DefaultRequestDirector( + final HttpClientAndroidLog log, + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + this(new HttpClientAndroidLog(DefaultRequestDirector.class), + requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler, + redirectStrategy, + new AuthenticationStrategyAdaptor(targetAuthHandler), + new AuthenticationStrategyAdaptor(proxyAuthHandler), + userTokenHandler, + params); + } + + /** + * @since 4.2 + */ + public DefaultRequestDirector( + final HttpClientAndroidLog log, + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationStrategy targetAuthStrategy, + final AuthenticationStrategy proxyAuthStrategy, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + + Args.notNull(log, "Log"); + Args.notNull(requestExec, "Request executor"); + Args.notNull(conman, "Client connection manager"); + Args.notNull(reustrat, "Connection reuse strategy"); + Args.notNull(kastrat, "Connection keep alive strategy"); + Args.notNull(rouplan, "Route planner"); + Args.notNull(httpProcessor, "HTTP protocol processor"); + Args.notNull(retryHandler, "HTTP request retry handler"); + Args.notNull(redirectStrategy, "Redirect strategy"); + Args.notNull(targetAuthStrategy, "Target authentication strategy"); + Args.notNull(proxyAuthStrategy, "Proxy authentication strategy"); + Args.notNull(userTokenHandler, "User token handler"); + Args.notNull(params, "HTTP parameters"); + this.log = log; + this.authenticator = new HttpAuthenticator(log); + this.requestExec = requestExec; + this.connManager = conman; + this.reuseStrategy = reustrat; + this.keepAliveStrategy = kastrat; + this.routePlanner = rouplan; + this.httpProcessor = httpProcessor; + this.retryHandler = retryHandler; + this.redirectStrategy = redirectStrategy; + this.targetAuthStrategy = targetAuthStrategy; + this.proxyAuthStrategy = proxyAuthStrategy; + this.userTokenHandler = userTokenHandler; + this.params = params; + + if (redirectStrategy instanceof DefaultRedirectStrategyAdaptor) { + this.redirectHandler = ((DefaultRedirectStrategyAdaptor) redirectStrategy).getHandler(); + } else { + this.redirectHandler = null; + } + if (targetAuthStrategy instanceof AuthenticationStrategyAdaptor) { + this.targetAuthHandler = ((AuthenticationStrategyAdaptor) targetAuthStrategy).getHandler(); + } else { + this.targetAuthHandler = null; + } + if (proxyAuthStrategy instanceof AuthenticationStrategyAdaptor) { + this.proxyAuthHandler = ((AuthenticationStrategyAdaptor) proxyAuthStrategy).getHandler(); + } else { + this.proxyAuthHandler = null; + } + + this.managedConn = null; + + this.execCount = 0; + this.redirectCount = 0; + this.targetAuthState = new AuthState(); + this.proxyAuthState = new AuthState(); + this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100); + } + + + private RequestWrapper wrapRequest( + final HttpRequest request) throws ProtocolException { + if (request instanceof HttpEntityEnclosingRequest) { + return new EntityEnclosingRequestWrapper( + (HttpEntityEnclosingRequest) request); + } else { + return new RequestWrapper( + request); + } + } + + + protected void rewriteRequestURI( + final RequestWrapper request, + final HttpRoute route) throws ProtocolException { + try { + + URI uri = request.getURI(); + if (route.getProxyHost() != null && !route.isTunnelled()) { + // Make sure the request URI is absolute + if (!uri.isAbsolute()) { + final HttpHost target = route.getTargetHost(); + uri = URIUtils.rewriteURI(uri, target, true); + } else { + uri = URIUtils.rewriteURI(uri); + } + } else { + // Make sure the request URI is relative + if (uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, null, true); + } else { + uri = URIUtils.rewriteURI(uri); + } + } + request.setURI(uri); + + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid URI: " + + request.getRequestLine().getUri(), ex); + } + } + + + // non-javadoc, see interface ClientRequestDirector + public HttpResponse execute(final HttpHost targetHost, final HttpRequest request, + final HttpContext context) + throws HttpException, IOException { + + context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState); + context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState); + + HttpHost target = targetHost; + + final HttpRequest orig = request; + final RequestWrapper origWrapper = wrapRequest(orig); + origWrapper.setParams(params); + final HttpRoute origRoute = determineRoute(target, origWrapper, context); + + virtualHost = (HttpHost) origWrapper.getParams().getParameter(ClientPNames.VIRTUAL_HOST); + + // HTTPCLIENT-1092 - add the port if necessary + if (virtualHost != null && virtualHost.getPort() == -1) { + final HttpHost host = (target != null) ? target : origRoute.getTargetHost(); + final int port = host.getPort(); + if (port != -1){ + virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName()); + } + } + + RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute); + + boolean reuse = false; + boolean done = false; + try { + HttpResponse response = null; + while (!done) { + // In this loop, the RoutedRequest may be replaced by a + // followup request and route. The request and route passed + // in the method arguments will be replaced. The original + // request is still available in 'orig'. + + final RequestWrapper wrapper = roureq.getRequest(); + final HttpRoute route = roureq.getRoute(); + response = null; + + // See if we have a user token bound to the execution context + Object userToken = context.getAttribute(ClientContext.USER_TOKEN); + + // Allocate connection if needed + if (managedConn == null) { + final ClientConnectionRequest connRequest = connManager.requestConnection( + route, userToken); + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setConnectionRequest(connRequest); + } + + final long timeout = HttpClientParams.getConnectionManagerTimeout(params); + try { + managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS); + } catch(final InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new InterruptedIOException(); + } + + if (HttpConnectionParams.isStaleCheckingEnabled(params)) { + // validate connection + if (managedConn.isOpen()) { + this.log.debug("Stale connection check"); + if (managedConn.isStale()) { + this.log.debug("Stale connection detected"); + managedConn.close(); + } + } + } + } + + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn); + } + + try { + tryConnect(roureq, context); + } catch (final TunnelRefusedException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage()); + } + response = ex.getResponse(); + break; + } + + final String userinfo = wrapper.getURI().getUserInfo(); + if (userinfo != null) { + targetAuthState.update( + new BasicScheme(), new UsernamePasswordCredentials(userinfo)); + } + + // Get target. Even if there's virtual host, we may need the target to set the port. + if (virtualHost != null) { + target = virtualHost; + } else { + final URI requestURI = wrapper.getURI(); + if (requestURI.isAbsolute()) { + target = URIUtils.extractHost(requestURI); + } + } + if (target == null) { + target = route.getTargetHost(); + } + + // Reset headers on the request wrapper + wrapper.resetHeaders(); + // Re-write request URI if needed + rewriteRequestURI(wrapper, route); + + // Populate the execution context + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target); + context.setAttribute(ClientContext.ROUTE, route); + context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn); + + // Run request protocol interceptors + requestExec.preProcess(wrapper, httpProcessor, context); + + response = tryExecute(roureq, context); + if (response == null) { + // Need to start over + continue; + } + + // Run response protocol interceptors + response.setParams(params); + requestExec.postProcess(response, httpProcessor, context); + + + // The connection is in or can be brought to a re-usable state. + reuse = reuseStrategy.keepAlive(response, context); + if (reuse) { + // Set the idle duration of this connection + final long duration = keepAliveStrategy.getKeepAliveDuration(response, context); + if (this.log.isDebugEnabled()) { + final String s; + if (duration > 0) { + s = "for " + duration + " " + TimeUnit.MILLISECONDS; + } else { + s = "indefinitely"; + } + this.log.debug("Connection can be kept alive " + s); + } + managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS); + } + + final RoutedRequest followup = handleResponse(roureq, response, context); + if (followup == null) { + done = true; + } else { + if (reuse) { + // Make sure the response body is fully consumed, if present + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + // entity consumed above is not an auto-release entity, + // need to mark the connection re-usable explicitly + managedConn.markReusable(); + } else { + managedConn.close(); + if (proxyAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0 + && proxyAuthState.getAuthScheme() != null + && proxyAuthState.getAuthScheme().isConnectionBased()) { + this.log.debug("Resetting proxy auth state"); + proxyAuthState.reset(); + } + if (targetAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0 + && targetAuthState.getAuthScheme() != null + && targetAuthState.getAuthScheme().isConnectionBased()) { + this.log.debug("Resetting target auth state"); + targetAuthState.reset(); + } + } + // check if we can use the same connection for the followup + if (!followup.getRoute().equals(roureq.getRoute())) { + releaseConnection(); + } + roureq = followup; + } + + if (managedConn != null) { + if (userToken == null) { + userToken = userTokenHandler.getUserToken(context); + context.setAttribute(ClientContext.USER_TOKEN, userToken); + } + if (userToken != null) { + managedConn.setState(userToken); + } + } + + } // while not done + + + // check for entity, release connection if possible + if ((response == null) || (response.getEntity() == null) || + !response.getEntity().isStreaming()) { + // connection not needed and (assumed to be) in re-usable state + if (reuse) { + managedConn.markReusable(); + } + releaseConnection(); + } else { + // install an auto-release entity + HttpEntity entity = response.getEntity(); + entity = new BasicManagedEntity(entity, managedConn, reuse); + response.setEntity(entity); + } + + return response; + + } catch (final ConnectionShutdownException ex) { + final InterruptedIOException ioex = new InterruptedIOException( + "Connection has been shut down"); + ioex.initCause(ex); + throw ioex; + } catch (final HttpException ex) { + abortConnection(); + throw ex; + } catch (final IOException ex) { + abortConnection(); + throw ex; + } catch (final RuntimeException ex) { + abortConnection(); + throw ex; + } + } // execute + + /** + * Establish connection either directly or through a tunnel and retry in case of + * a recoverable I/O failure + */ + private void tryConnect( + final RoutedRequest req, final HttpContext context) throws HttpException, IOException { + final HttpRoute route = req.getRoute(); + final HttpRequest wrapper = req.getRequest(); + + int connectCount = 0; + for (;;) { + context.setAttribute(ExecutionContext.HTTP_REQUEST, wrapper); + // Increment connect count + connectCount++; + try { + if (!managedConn.isOpen()) { + managedConn.open(route, context, params); + } else { + managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params)); + } + establishRoute(route, context); + break; + } catch (final IOException ex) { + try { + managedConn.close(); + } catch (final IOException ignore) { + } + if (retryHandler.retryRequest(ex, connectCount, context)) { + if (this.log.isInfoEnabled()) { + this.log.info("I/O exception ("+ ex.getClass().getName() + + ") caught when connecting to " + + route + + ": " + + ex.getMessage()); + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + this.log.info("Retrying connect to " + route); + } + } else { + throw ex; + } + } + } + } + + /** + * Execute request and retry in case of a recoverable I/O failure + */ + private HttpResponse tryExecute( + final RoutedRequest req, final HttpContext context) throws HttpException, IOException { + final RequestWrapper wrapper = req.getRequest(); + final HttpRoute route = req.getRoute(); + HttpResponse response = null; + + Exception retryReason = null; + for (;;) { + // Increment total exec count (with redirects) + execCount++; + // Increment exec count for this particular request + wrapper.incrementExecCount(); + if (!wrapper.isRepeatable()) { + this.log.debug("Cannot retry non-repeatable request"); + if (retryReason != null) { + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity. The cause lists the " + + "reason the original request failed.", retryReason); + } else { + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity."); + } + } + + try { + if (!managedConn.isOpen()) { + // If we have a direct route to the target host + // just re-open connection and re-try the request + if (!route.isTunnelled()) { + this.log.debug("Reopening the direct connection."); + managedConn.open(route, context, params); + } else { + // otherwise give up + this.log.debug("Proxied connection. Need to start over."); + break; + } + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Attempt " + execCount + " to execute request"); + } + response = requestExec.execute(wrapper, managedConn, context); + break; + + } catch (final IOException ex) { + this.log.debug("Closing the connection."); + try { + managedConn.close(); + } catch (final IOException ignore) { + } + if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) { + if (this.log.isInfoEnabled()) { + this.log.info("I/O exception ("+ ex.getClass().getName() + + ") caught when processing request to " + + route + + ": " + + ex.getMessage()); + } + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + if (this.log.isInfoEnabled()) { + this.log.info("Retrying request to " + route); + } + retryReason = ex; + } else { + if (ex instanceof NoHttpResponseException) { + final NoHttpResponseException updatedex = new NoHttpResponseException( + route.getTargetHost().toHostString() + " failed to respond"); + updatedex.setStackTrace(ex.getStackTrace()); + throw updatedex; + } else { + throw ex; + } + } + } + } + return response; + } + + /** + * Returns the connection back to the connection manager + * and prepares for retrieving a new connection during + * the next request. + */ + protected void releaseConnection() { + // Release the connection through the ManagedConnection instead of the + // ConnectionManager directly. This lets the connection control how + // it is released. + try { + managedConn.releaseConnection(); + } catch(final IOException ignored) { + this.log.debug("IOException releasing connection", ignored); + } + managedConn = null; + } + + /** + * Determines the route for a request. + * Called by {@link #execute} + * to determine the route for either the original or a followup request. + * + * @param targetHost the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context to use for the execution, + * never <code>null</code> + * + * @return the route the request should take + * + * @throws HttpException in case of a problem + */ + protected HttpRoute determineRoute(final HttpHost targetHost, + final HttpRequest request, + final HttpContext context) + throws HttpException { + return this.routePlanner.determineRoute( + targetHost != null ? targetHost : (HttpHost) request.getParams() + .getParameter(ClientPNames.DEFAULT_HOST), + request, context); + } + + + /** + * Establishes the target route. + * + * @param route the route to establish + * @param context the context for the request execution + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected void establishRoute(final HttpRoute route, final HttpContext context) + throws HttpException, IOException { + + final HttpRouteDirector rowdy = new BasicRouteDirector(); + int step; + do { + final HttpRoute fact = managedConn.getRoute(); + step = rowdy.nextStep(route, fact); + + switch (step) { + + case HttpRouteDirector.CONNECT_TARGET: + case HttpRouteDirector.CONNECT_PROXY: + managedConn.open(route, context, this.params); + break; + + case HttpRouteDirector.TUNNEL_TARGET: { + final boolean secure = createTunnelToTarget(route, context); + this.log.debug("Tunnel to target created."); + managedConn.tunnelTarget(secure, this.params); + } break; + + case HttpRouteDirector.TUNNEL_PROXY: { + // The most simple example for this case is a proxy chain + // of two proxies, where P1 must be tunnelled to P2. + // route: Source -> P1 -> P2 -> Target (3 hops) + // fact: Source -> P1 -> Target (2 hops) + final int hop = fact.getHopCount()-1; // the hop to establish + final boolean secure = createTunnelToProxy(route, hop, context); + this.log.debug("Tunnel to proxy created."); + managedConn.tunnelProxy(route.getHopTarget(hop), + secure, this.params); + } break; + + + case HttpRouteDirector.LAYER_PROTOCOL: + managedConn.layerProtocol(context, this.params); + break; + + case HttpRouteDirector.UNREACHABLE: + throw new HttpException("Unable to establish route: " + + "planned = " + route + "; current = " + fact); + case HttpRouteDirector.COMPLETE: + // do nothing + break; + default: + throw new IllegalStateException("Unknown step indicator " + + step + " from RouteDirector."); + } + + } while (step > HttpRouteDirector.COMPLETE); + + } // establishConnection + + + /** + * Creates a tunnel to the target server. + * The connection must be established to the (last) proxy. + * A CONNECT request for tunnelling through the proxy will + * be created and sent, the response received and checked. + * This method does <i>not</i> update the connection with + * information about the tunnel, that is left to the caller. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return <code>true</code> if the tunnelled route is secure, + * <code>false</code> otherwise. + * The implementation here always returns <code>false</code>, + * but derived classes may override. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToTarget(final HttpRoute route, + final HttpContext context) + throws HttpException, IOException { + + final HttpHost proxy = route.getProxyHost(); + final HttpHost target = route.getTargetHost(); + HttpResponse response = null; + + for (;;) { + if (!this.managedConn.isOpen()) { + this.managedConn.open(route, context, this.params); + } + + final HttpRequest connect = createConnectRequest(route, context); + connect.setParams(this.params); + + // Populate the execution context + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target); + context.setAttribute(ClientContext.ROUTE, route); + context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy); + context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn); + context.setAttribute(ExecutionContext.HTTP_REQUEST, connect); + + this.requestExec.preProcess(connect, this.httpProcessor, context); + + response = this.requestExec.execute(connect, this.managedConn, context); + + response.setParams(this.params); + this.requestExec.postProcess(response, this.httpProcessor, context); + + final int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + + if (HttpClientParams.isAuthenticating(this.params)) { + if (this.authenticator.isAuthenticationRequested(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + if (this.authenticator.authenticate(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + this.log.debug("Connection kept alive"); + // Consume response content + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + } else { + this.managedConn.close(); + } + } else { + break; + } + } else { + break; + } + } + } + + final int status = response.getStatusLine().getStatusCode(); + + if (status > 299) { + + // Buffer response content + final HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + this.managedConn.close(); + throw new TunnelRefusedException("CONNECT refused by proxy: " + + response.getStatusLine(), response); + } + + this.managedConn.markReusable(); + + // How to decide on security of the tunnelled connection? + // The socket factory knows only about the segment to the proxy. + // Even if that is secure, the hop to the target may be insecure. + // Leave it to derived classes, consider insecure by default here. + return false; + + } // createTunnelToTarget + + + + /** + * Creates a tunnel to an intermediate proxy. + * This method is <i>not</i> implemented in this class. + * It just throws an exception here. + * + * @param route the route to establish + * @param hop the hop in the route to establish now. + * <code>route.getHopTarget(hop)</code> + * will return the proxy to tunnel to. + * @param context the context for request execution + * + * @return <code>true</code> if the partially tunnelled connection + * is secure, <code>false</code> otherwise. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToProxy(final HttpRoute route, final int hop, + final HttpContext context) + throws HttpException, IOException { + + // Have a look at createTunnelToTarget and replicate the parts + // you need in a custom derived class. If your proxies don't require + // authentication, it is not too hard. But for the stock version of + // HttpClient, we cannot make such simplifying assumptions and would + // have to include proxy authentication code. The HttpComponents team + // is currently not in a position to support rarely used code of this + // complexity. Feel free to submit patches that refactor the code in + // createTunnelToTarget to facilitate re-use for proxy tunnelling. + + throw new HttpException("Proxy chains are not supported."); + } + + + + /** + * Creates the CONNECT request for tunnelling. + * Called by {@link #createTunnelToTarget createTunnelToTarget}. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return the CONNECT request for tunnelling + */ + protected HttpRequest createConnectRequest(final HttpRoute route, + final HttpContext context) { + // see RFC 2817, section 5.2 and + // INTERNET-DRAFT: Tunneling TCP based protocols through + // Web proxy servers + + final HttpHost target = route.getTargetHost(); + + final String host = target.getHostName(); + int port = target.getPort(); + if (port < 0) { + final Scheme scheme = connManager.getSchemeRegistry(). + getScheme(target.getSchemeName()); + port = scheme.getDefaultPort(); + } + + final StringBuilder buffer = new StringBuilder(host.length() + 6); + buffer.append(host); + buffer.append(':'); + buffer.append(Integer.toString(port)); + + final String authority = buffer.toString(); + final ProtocolVersion ver = HttpProtocolParams.getVersion(params); + final HttpRequest req = new BasicHttpRequest + ("CONNECT", authority, ver); + + return req; + } + + + /** + * Analyzes a response to check need for a followup. + * + * @param roureq the request and route. + * @param response the response to analayze + * @param context the context used for the current request execution + * + * @return the followup request and route if there is a followup, or + * <code>null</code> if the response should be returned as is + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected RoutedRequest handleResponse(final RoutedRequest roureq, + final HttpResponse response, + final HttpContext context) + throws HttpException, IOException { + + final HttpRoute route = roureq.getRoute(); + final RequestWrapper request = roureq.getRequest(); + + final HttpParams params = request.getParams(); + + if (HttpClientParams.isAuthenticating(params)) { + HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); + if (target == null) { + target = route.getTargetHost(); + } + if (target.getPort() < 0) { + final Scheme scheme = connManager.getSchemeRegistry().getScheme(target); + target = new HttpHost(target.getHostName(), scheme.getDefaultPort(), target.getSchemeName()); + } + + final boolean targetAuthRequested = this.authenticator.isAuthenticationRequested( + target, response, this.targetAuthStrategy, targetAuthState, context); + + HttpHost proxy = route.getProxyHost(); + // if proxy is not set use target host instead + if (proxy == null) { + proxy = route.getTargetHost(); + } + final boolean proxyAuthRequested = this.authenticator.isAuthenticationRequested( + proxy, response, this.proxyAuthStrategy, proxyAuthState, context); + + if (targetAuthRequested) { + if (this.authenticator.authenticate(target, response, + this.targetAuthStrategy, this.targetAuthState, context)) { + // Re-try the same request via the same route + return roureq; + } + } + if (proxyAuthRequested) { + if (this.authenticator.authenticate(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + // Re-try the same request via the same route + return roureq; + } + } + } + + if (HttpClientParams.isRedirecting(params) && + this.redirectStrategy.isRedirected(request, response, context)) { + + if (redirectCount >= maxRedirects) { + throw new RedirectException("Maximum redirects (" + + maxRedirects + ") exceeded"); + } + redirectCount++; + + // Virtual host cannot be used any longer + virtualHost = null; + + final HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context); + final HttpRequest orig = request.getOriginal(); + redirect.setHeaders(orig.getAllHeaders()); + + final URI uri = redirect.getURI(); + final HttpHost newTarget = URIUtils.extractHost(uri); + if (newTarget == null) { + throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri); + } + + // Reset auth states if redirecting to another host + if (!route.getTargetHost().equals(newTarget)) { + this.log.debug("Resetting target auth state"); + targetAuthState.reset(); + final AuthScheme authScheme = proxyAuthState.getAuthScheme(); + if (authScheme != null && authScheme.isConnectionBased()) { + this.log.debug("Resetting proxy auth state"); + proxyAuthState.reset(); + } + } + + final RequestWrapper wrapper = wrapRequest(redirect); + wrapper.setParams(params); + + final HttpRoute newRoute = determineRoute(newTarget, wrapper, context); + final RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute); + + if (this.log.isDebugEnabled()) { + this.log.debug("Redirecting to '" + uri + "' via " + newRoute); + } + + return newRequest; + } + + return null; + } // handleResponse + + + /** + * Shuts down the connection. + * This method is called from a <code>catch</code> block in + * {@link #execute execute} during exception handling. + */ + private void abortConnection() { + final ManagedClientConnection mcc = managedConn; + if (mcc != null) { + // we got here as the result of an exception + // no response will be returned, release the connection + managedConn = null; + try { + mcc.abortConnection(); + } catch (final IOException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + } + // ensure the connection manager properly releases this connection + try { + mcc.releaseConnection(); + } catch(final IOException ignored) { + this.log.debug("Error releasing connection", ignored); + } + } + } // abortConnection + + +} // class DefaultClientRequestDirector diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java new file mode 100644 index 000000000..8e7279aab --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java @@ -0,0 +1,80 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.ServiceUnavailableRetryStrategy; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of the {@link ServiceUnavailableRetryStrategy} interface. + * that retries <code>503</code> (Service Unavailable) responses for a fixed number of times + * at a fixed interval. + * + * @since 4.2 + */ +@Immutable +public class DefaultServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy { + + /** + * Maximum number of allowed retries if the server responds with a HTTP code + * in our retry code list. Default value is 1. + */ + private final int maxRetries; + + /** + * Retry interval between subsequent requests, in milliseconds. Default + * value is 1 second. + */ + private final long retryInterval; + + public DefaultServiceUnavailableRetryStrategy(final int maxRetries, final int retryInterval) { + super(); + Args.positive(maxRetries, "Max retries"); + Args.positive(retryInterval, "Retry interval"); + this.maxRetries = maxRetries; + this.retryInterval = retryInterval; + } + + public DefaultServiceUnavailableRetryStrategy() { + this(1, 1000); + } + + public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) { + return executionCount <= maxRetries && + response.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE; + } + + public long getRetryInterval() { + return retryInterval; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java new file mode 100644 index 000000000..a2a57e1d5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java @@ -0,0 +1,91 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.List; +import java.util.Map; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.auth.params.AuthPNames; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationHandler} implementation + * for target host authentication. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link TargetAuthenticationStrategy} + */ +@Deprecated +@Immutable +public class DefaultTargetAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultTargetAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + final int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_UNAUTHORIZED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(response, "HTTP response"); + final Header[] headers = response.getHeaders(AUTH.WWW_AUTH); + return parseChallenges(headers); + } + + @Override + protected List<String> getAuthPreferences( + final HttpResponse response, + final HttpContext context) { + @SuppressWarnings("unchecked") + final + List<String> authpref = (List<String>) response.getParams().getParameter( + AuthPNames.TARGET_AUTH_PREF); + if (authpref != null) { + return authpref; + } else { + return super.getAuthPreferences(response, context); + } + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java new file mode 100644 index 000000000..c6d2a8daa --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java @@ -0,0 +1,101 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.security.Principal; + +import javax.net.ssl.SSLSession; + +import ch.boye.httpclientandroidlib.HttpConnection; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * Default implementation of {@link UserTokenHandler}. This class will use + * an instance of {@link Principal} as a state object for HTTP connections, + * if it can be obtained from the given execution context. This helps ensure + * persistent connections created with a particular user identity within + * a particular security context can be reused by the same user only. + * <p> + * DefaultUserTokenHandler will use the user principle of connection + * based authentication schemes such as NTLM or that of the SSL session + * with the client authentication turned on. If both are unavailable, + * <code>null</code> token will be returned. + * + * @since 4.0 + */ +@Immutable +public class DefaultUserTokenHandler implements UserTokenHandler { + + public static final DefaultUserTokenHandler INSTANCE = new DefaultUserTokenHandler(); + + public Object getUserToken(final HttpContext context) { + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + Principal userPrincipal = null; + + final AuthState targetAuthState = clientContext.getTargetAuthState(); + if (targetAuthState != null) { + userPrincipal = getAuthPrincipal(targetAuthState); + if (userPrincipal == null) { + final AuthState proxyAuthState = clientContext.getProxyAuthState(); + userPrincipal = getAuthPrincipal(proxyAuthState); + } + } + + if (userPrincipal == null) { + final HttpConnection conn = clientContext.getConnection(); + if (conn.isOpen() && conn instanceof ManagedHttpClientConnection) { + final SSLSession sslsession = ((ManagedHttpClientConnection) conn).getSSLSession(); + if (sslsession != null) { + userPrincipal = sslsession.getLocalPrincipal(); + } + } + } + + return userPrincipal; + } + + private static Principal getAuthPrincipal(final AuthState authState) { + final AuthScheme scheme = authState.getAuthScheme(); + if (scheme != null && scheme.isComplete() && scheme.isConnectionBased()) { + final Credentials creds = authState.getCredentials(); + if (creds != null) { + return creds.getUserPrincipal(); + } + } + return null; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java new file mode 100644 index 000000000..45fbbe0a6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java @@ -0,0 +1,113 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.entity.HttpEntityWrapper; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * A wrapper class for {@link HttpEntityEnclosingRequest}s that can + * be used to change properties of the current request without + * modifying the original object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @since 4.0 + * + * @deprecated (4.3) do not use. + */ +@Deprecated +@NotThreadSafe // e.g. [gs]etEntity() +public class EntityEnclosingRequestWrapper extends RequestWrapper + implements HttpEntityEnclosingRequest { + + private HttpEntity entity; + private boolean consumed; + + public EntityEnclosingRequestWrapper(final HttpEntityEnclosingRequest request) + throws ProtocolException { + super(request); + setEntity(request.getEntity()); + } + + public HttpEntity getEntity() { + return this.entity; + } + + public void setEntity(final HttpEntity entity) { + this.entity = entity != null ? new EntityWrapper(entity) : null; + this.consumed = false; + } + + public boolean expectContinue() { + final Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); + return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); + } + + @Override + public boolean isRepeatable() { + return this.entity == null || this.entity.isRepeatable() || !this.consumed; + } + + class EntityWrapper extends HttpEntityWrapper { + + EntityWrapper(final HttpEntity entity) { + super(entity); + } + + @Override + public void consumeContent() throws IOException { + consumed = true; + super.consumeContent(); + } + + @Override + public InputStream getContent() throws IOException { + consumed = true; + return super.getContent(); + } + + @Override + public void writeTo(final OutputStream outstream) throws IOException { + consumed = true; + super.writeTo(outstream); + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java new file mode 100644 index 000000000..888e7e3a5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java @@ -0,0 +1,156 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Collection of different counters used to gather metrics for {@link FutureRequestExecutionService}. + */ +public final class FutureRequestExecutionMetrics { + + private final AtomicLong activeConnections = new AtomicLong(); + private final AtomicLong scheduledConnections = new AtomicLong(); + private final DurationCounter successfulConnections = new DurationCounter(); + private final DurationCounter failedConnections = new DurationCounter(); + private final DurationCounter requests = new DurationCounter(); + private final DurationCounter tasks = new DurationCounter(); + + FutureRequestExecutionMetrics() { + } + + AtomicLong getActiveConnections() { + return activeConnections; + } + + AtomicLong getScheduledConnections() { + return scheduledConnections; + } + + DurationCounter getSuccessfulConnections() { + return successfulConnections; + } + + DurationCounter getFailedConnections() { + return failedConnections; + } + + DurationCounter getRequests() { + return requests; + } + + DurationCounter getTasks() { + return tasks; + } + + public long getActiveConnectionCount() { + return activeConnections.get(); + } + + public long getScheduledConnectionCount() { + return scheduledConnections.get(); + } + + public long getSuccessfulConnectionCount() { + return successfulConnections.count(); + } + + public long getSuccessfulConnectionAverageDuration() { + return successfulConnections.averageDuration(); + } + + public long getFailedConnectionCount() { + return failedConnections.count(); + } + + public long getFailedConnectionAverageDuration() { + return failedConnections.averageDuration(); + } + + public long getRequestCount() { + return requests.count(); + } + + public long getRequestAverageDuration() { + return requests.averageDuration(); + } + + public long getTaskCount() { + return tasks.count(); + } + + public long getTaskAverageDuration() { + return tasks.averageDuration(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("[activeConnections=").append(activeConnections) + .append(", scheduledConnections=").append(scheduledConnections) + .append(", successfulConnections=").append(successfulConnections) + .append(", failedConnections=").append(failedConnections) + .append(", requests=").append(requests) + .append(", tasks=").append(tasks) + .append("]"); + return builder.toString(); + } + + /** + * A counter that can measure duration and number of events. + */ + static class DurationCounter { + + private final AtomicLong count = new AtomicLong(0); + private final AtomicLong cumulativeDuration = new AtomicLong(0); + + public void increment(final long startTime) { + count.incrementAndGet(); + cumulativeDuration.addAndGet(System.currentTimeMillis() - startTime); + } + + public long count() { + return count.get(); + } + + public long averageDuration() { + final long counter = count.get(); + return counter > 0 ? cumulativeDuration.get() / counter : 0; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("[count=").append(count()) + .append(", averageDuration=").append(averageDuration()) + .append("]"); + return builder.toString(); + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java new file mode 100644 index 000000000..26fa9dba3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java @@ -0,0 +1,142 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.concurrent.FutureCallback; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * HttpAsyncClientWithFuture wraps calls to execute with a {@link HttpRequestFutureTask} + * and schedules them using the provided executor service. Scheduled calls may be cancelled. + */ +@ThreadSafe +public class FutureRequestExecutionService implements Closeable { + + private final HttpClient httpclient; + private final ExecutorService executorService; + private final FutureRequestExecutionMetrics metrics = new FutureRequestExecutionMetrics(); + private final AtomicBoolean closed = new AtomicBoolean(false); + + /** + * Create a new FutureRequestExecutionService. + * + * @param httpclient + * you should tune your httpclient instance to match your needs. You should + * align the max number of connections in the pool and the number of threads + * in the executor; it doesn't make sense to have more threads than connections + * and if you have less connections than threads, the threads will just end up + * blocking on getting a connection from the pool. + * @param executorService + * any executorService will do here. E.g. + * {@link java.util.concurrent.Executors#newFixedThreadPool(int)} + */ + public FutureRequestExecutionService( + final HttpClient httpclient, + final ExecutorService executorService) { + this.httpclient = httpclient; + this.executorService = executorService; + } + + /** + * Schedule a request for execution. + * + * @param <T> + * + * @param request + * request to execute + * @param responseHandler + * handler that will process the response. + * @return HttpAsyncClientFutureTask for the scheduled request. + * @throws InterruptedException + */ + public <T> HttpRequestFutureTask<T> execute( + final HttpUriRequest request, + final HttpContext context, + final ResponseHandler<T> responseHandler) { + return execute(request, context, responseHandler, null); + } + + /** + * Schedule a request for execution. + * + * @param <T> + * + * @param request + * request to execute + * @param context + * optional context; use null if not needed. + * @param responseHandler + * handler that will process the response. + * @param callback + * callback handler that will be called when the request is scheduled, + * started, completed, failed, or cancelled. + * @return HttpAsyncClientFutureTask for the scheduled request. + * @throws InterruptedException + */ + public <T> HttpRequestFutureTask<T> execute( + final HttpUriRequest request, + final HttpContext context, + final ResponseHandler<T> responseHandler, + final FutureCallback<T> callback) { + if(closed.get()) { + throw new IllegalStateException("Close has been called on this httpclient instance."); + } + metrics.getScheduledConnections().incrementAndGet(); + final HttpRequestTaskCallable<T> callable = new HttpRequestTaskCallable<T>( + httpclient, request, context, responseHandler, callback, metrics); + final HttpRequestFutureTask<T> httpRequestFutureTask = new HttpRequestFutureTask<T>( + request, callable); + executorService.execute(httpRequestFutureTask); + + return httpRequestFutureTask; + } + + /** + * @return metrics gathered for this instance. + * @see FutureRequestExecutionMetrics + */ + public FutureRequestExecutionMetrics metrics() { + return metrics; + } + + public void close() throws IOException { + closed.set(true); + executorService.shutdownNow(); + if (httpclient instanceof Closeable) { + ((Closeable) httpclient).close(); + } + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java new file mode 100644 index 000000000..745ae4735 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java @@ -0,0 +1,61 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * @deprecated (4.3) reserved for internal use. + * + */ +@Deprecated +public class HttpAuthenticator extends ch.boye.httpclientandroidlib.impl.auth.HttpAuthenticator { + + public HttpAuthenticator(final HttpClientAndroidLog log) { + super(log); + } + + public HttpAuthenticator() { + super(); + } + + public boolean authenticate ( + final HttpHost host, + final HttpResponse response, + final AuthenticationStrategy authStrategy, + final AuthState authState, + final HttpContext context) { + return handleAuthChallenge(host, response, authStrategy, authState, context); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java new file mode 100644 index 000000000..00313e668 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java @@ -0,0 +1,954 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.net.ProxySelector; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequestInterceptor; +import ch.boye.httpclientandroidlib.HttpResponseInterceptor; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.BackoffManager; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.ServiceUnavailableRetryStrategy; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.client.config.CookieSpecs; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.protocol.RequestAcceptEncoding; +import ch.boye.httpclientandroidlib.client.protocol.RequestAddCookies; +import ch.boye.httpclientandroidlib.client.protocol.RequestAuthCache; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.client.protocol.RequestDefaultHeaders; +import ch.boye.httpclientandroidlib.client.protocol.RequestExpectContinue; +import ch.boye.httpclientandroidlib.client.protocol.ResponseContentEncoding; +import ch.boye.httpclientandroidlib.client.protocol.ResponseProcessCookies; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.config.RegistryBuilder; +import ch.boye.httpclientandroidlib.config.SocketConfig; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.socket.PlainConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.ssl.SSLConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.ssl.SSLContexts; +import ch.boye.httpclientandroidlib.conn.ssl.X509HostnameVerifier; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.NoConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.auth.BasicSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.DigestSchemeFactory; +/* KerberosSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.auth.NTLMSchemeFactory; +/* SPNegoSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.conn.DefaultProxyRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.DefaultRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.DefaultSchemePortResolver; +import ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.SystemDefaultRoutePlanner; +import ch.boye.httpclientandroidlib.impl.cookie.BestMatchSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.BrowserCompatSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.IgnoreSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.NetscapeDraftSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2109SpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2965SpecFactory; +import ch.boye.httpclientandroidlib.impl.execchain.BackoffStrategyExec; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; +import ch.boye.httpclientandroidlib.impl.execchain.MainClientExec; +import ch.boye.httpclientandroidlib.impl.execchain.ProtocolExec; +import ch.boye.httpclientandroidlib.impl.execchain.RedirectExec; +import ch.boye.httpclientandroidlib.impl.execchain.RetryExec; +import ch.boye.httpclientandroidlib.impl.execchain.ServiceUnavailableRetryExec; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpProcessorBuilder; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.RequestContent; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.protocol.RequestUserAgent; +import ch.boye.httpclientandroidlib.util.TextUtils; +import ch.boye.httpclientandroidlib.util.VersionInfo; + +/** + * Builder for {@link CloseableHttpClient} instances. + * <p/> + * When a particular component is not explicitly this class will + * use its default implementation. System properties will be taken + * into account when configuring the default implementations when + * {@link #useSystemProperties()} method is called prior to calling + * {@link #build()}. + * <ul> + * <li>ssl.TrustManagerFactory.algorithm</li> + * <li>javax.net.ssl.trustStoreType</li> + * <li>javax.net.ssl.trustStore</li> + * <li>javax.net.ssl.trustStoreProvider</li> + * <li>javax.net.ssl.trustStorePassword</li> + * <li>ssl.KeyManagerFactory.algorithm</li> + * <li>javax.net.ssl.keyStoreType</li> + * <li>javax.net.ssl.keyStore</li> + * <li>javax.net.ssl.keyStoreProvider</li> + * <li>javax.net.ssl.keyStorePassword</li> + * <li>https.protocols</li> + * <li>https.cipherSuites</li> + * <li>http.proxyHost</li> + * <li>http.proxyPort</li> + * <li>http.nonProxyHosts</li> + * <li>http.keepAlive</li> + * <li>http.maxConnections</li> + * <li>http.agent</li> + * </ul> + * <p/> + * Please note that some settings used by this class can be mutually + * exclusive and may not apply when building {@link CloseableHttpClient} + * instances. + * + * @since 4.3 + */ +@NotThreadSafe +public class HttpClientBuilder { + + private HttpRequestExecutor requestExec; + private X509HostnameVerifier hostnameVerifier; + private LayeredConnectionSocketFactory sslSocketFactory; + private SSLContext sslcontext; + private HttpClientConnectionManager connManager; + private SchemePortResolver schemePortResolver; + private ConnectionReuseStrategy reuseStrategy; + private ConnectionKeepAliveStrategy keepAliveStrategy; + private AuthenticationStrategy targetAuthStrategy; + private AuthenticationStrategy proxyAuthStrategy; + private UserTokenHandler userTokenHandler; + private HttpProcessor httpprocessor; + + private LinkedList<HttpRequestInterceptor> requestFirst; + private LinkedList<HttpRequestInterceptor> requestLast; + private LinkedList<HttpResponseInterceptor> responseFirst; + private LinkedList<HttpResponseInterceptor> responseLast; + + private HttpRequestRetryHandler retryHandler; + private HttpRoutePlanner routePlanner; + private RedirectStrategy redirectStrategy; + private ConnectionBackoffStrategy connectionBackoffStrategy; + private BackoffManager backoffManager; + private ServiceUnavailableRetryStrategy serviceUnavailStrategy; + private Lookup<AuthSchemeProvider> authSchemeRegistry; + private Lookup<CookieSpecProvider> cookieSpecRegistry; + private CookieStore cookieStore; + private CredentialsProvider credentialsProvider; + private String userAgent; + private HttpHost proxy; + private Collection<? extends Header> defaultHeaders; + private SocketConfig defaultSocketConfig; + private ConnectionConfig defaultConnectionConfig; + private RequestConfig defaultRequestConfig; + + private boolean systemProperties; + private boolean redirectHandlingDisabled; + private boolean automaticRetriesDisabled; + private boolean contentCompressionDisabled; + private boolean cookieManagementDisabled; + private boolean authCachingDisabled; + private boolean connectionStateDisabled; + + private int maxConnTotal = 0; + private int maxConnPerRoute = 0; + + private List<Closeable> closeables; + + static final String DEFAULT_USER_AGENT; + static { + final VersionInfo vi = VersionInfo.loadVersionInfo + ("ch.boye.httpclientandroidlib.client", HttpClientBuilder.class.getClassLoader()); + final String release = (vi != null) ? + vi.getRelease() : VersionInfo.UNAVAILABLE; + DEFAULT_USER_AGENT = "Apache-HttpClient/" + release + " (java 1.5)"; + } + + public static HttpClientBuilder create() { + return new HttpClientBuilder(); + } + + protected HttpClientBuilder() { + super(); + } + + /** + * Assigns {@link HttpRequestExecutor} instance. + */ + public final HttpClientBuilder setRequestExecutor(final HttpRequestExecutor requestExec) { + this.requestExec = requestExec; + return this; + } + + /** + * Assigns {@link X509HostnameVerifier} instance. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} and the {@link #setSSLSocketFactory( + * ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory)} methods. + */ + public final HttpClientBuilder setHostnameVerifier(final X509HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + return this; + } + + /** + * Assigns {@link SSLContext} instance. + * <p/> + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} and the {@link #setSSLSocketFactory( + * ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory)} methods. + */ + public final HttpClientBuilder setSslcontext(final SSLContext sslcontext) { + this.sslcontext = sslcontext; + return this; + } + + /** + * Assigns {@link LayeredConnectionSocketFactory} instance. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setSSLSocketFactory( + final LayeredConnectionSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + return this; + } + + /** + * Assigns maximum total connection value. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setMaxConnTotal(final int maxConnTotal) { + this.maxConnTotal = maxConnTotal; + return this; + } + + /** + * Assigns maximum connection per route value. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setMaxConnPerRoute(final int maxConnPerRoute) { + this.maxConnPerRoute = maxConnPerRoute; + return this; + } + + /** + * Assigns default {@link SocketConfig}. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setDefaultSocketConfig(final SocketConfig config) { + this.defaultSocketConfig = config; + return this; + } + + /** + * Assigns default {@link ConnectionConfig}. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setDefaultConnectionConfig(final ConnectionConfig config) { + this.defaultConnectionConfig = config; + return this; + } + + /** + * Assigns {@link HttpClientConnectionManager} instance. + */ + public final HttpClientBuilder setConnectionManager( + final HttpClientConnectionManager connManager) { + this.connManager = connManager; + return this; + } + + /** + * Assigns {@link ConnectionReuseStrategy} instance. + */ + public final HttpClientBuilder setConnectionReuseStrategy( + final ConnectionReuseStrategy reuseStrategy) { + this.reuseStrategy = reuseStrategy; + return this; + } + + /** + * Assigns {@link ConnectionKeepAliveStrategy} instance. + */ + public final HttpClientBuilder setKeepAliveStrategy( + final ConnectionKeepAliveStrategy keepAliveStrategy) { + this.keepAliveStrategy = keepAliveStrategy; + return this; + } + + /** + * Assigns {@link AuthenticationStrategy} instance for proxy + * authentication. + */ + public final HttpClientBuilder setTargetAuthenticationStrategy( + final AuthenticationStrategy targetAuthStrategy) { + this.targetAuthStrategy = targetAuthStrategy; + return this; + } + + /** + * Assigns {@link AuthenticationStrategy} instance for target + * host authentication. + */ + public final HttpClientBuilder setProxyAuthenticationStrategy( + final AuthenticationStrategy proxyAuthStrategy) { + this.proxyAuthStrategy = proxyAuthStrategy; + return this; + } + + /** + * Assigns {@link UserTokenHandler} instance. + * <p/> + * Please note this value can be overridden by the {@link #disableConnectionState()} + * method. + */ + public final HttpClientBuilder setUserTokenHandler(final UserTokenHandler userTokenHandler) { + this.userTokenHandler = userTokenHandler; + return this; + } + + /** + * Disables connection state tracking. + */ + public final HttpClientBuilder disableConnectionState() { + connectionStateDisabled = true; + return this; + } + + /** + * Assigns {@link SchemePortResolver} instance. + */ + public final HttpClientBuilder setSchemePortResolver( + final SchemePortResolver schemePortResolver) { + this.schemePortResolver = schemePortResolver; + return this; + } + + /** + * Assigns <tt>User-Agent</tt> value. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder setUserAgent(final String userAgent) { + this.userAgent = userAgent; + return this; + } + + /** + * Assigns default request header values. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder setDefaultHeaders(final Collection<? extends Header> defaultHeaders) { + this.defaultHeaders = defaultHeaders; + return this; + } + + /** + * Adds this protocol interceptor to the head of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorFirst(final HttpResponseInterceptor itcp) { + if (itcp == null) { + return this; + } + if (responseFirst == null) { + responseFirst = new LinkedList<HttpResponseInterceptor>(); + } + responseFirst.addFirst(itcp); + return this; + } + + /** + * Adds this protocol interceptor to the tail of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorLast(final HttpResponseInterceptor itcp) { + if (itcp == null) { + return this; + } + if (responseLast == null) { + responseLast = new LinkedList<HttpResponseInterceptor>(); + } + responseLast.addLast(itcp); + return this; + } + + /** + * Adds this protocol interceptor to the head of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorFirst(final HttpRequestInterceptor itcp) { + if (itcp == null) { + return this; + } + if (requestFirst == null) { + requestFirst = new LinkedList<HttpRequestInterceptor>(); + } + requestFirst.addFirst(itcp); + return this; + } + + /** + * Adds this protocol interceptor to the tail of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorLast(final HttpRequestInterceptor itcp) { + if (itcp == null) { + return this; + } + if (requestLast == null) { + requestLast = new LinkedList<HttpRequestInterceptor>(); + } + requestLast.addLast(itcp); + return this; + } + + /** + * Disables state (cookie) management. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder disableCookieManagement() { + this.cookieManagementDisabled = true; + return this; + } + + /** + * Disables automatic content decompression. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder disableContentCompression() { + contentCompressionDisabled = true; + return this; + } + + /** + * Disables authentication scheme caching. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder disableAuthCaching() { + this.authCachingDisabled = true; + return this; + } + + /** + * Assigns {@link HttpProcessor} instance. + */ + public final HttpClientBuilder setHttpProcessor(final HttpProcessor httpprocessor) { + this.httpprocessor = httpprocessor; + return this; + } + + /** + * Assigns {@link HttpRequestRetryHandler} instance. + * <p/> + * Please note this value can be overridden by the {@link #disableAutomaticRetries()} + * method. + */ + public final HttpClientBuilder setRetryHandler(final HttpRequestRetryHandler retryHandler) { + this.retryHandler = retryHandler; + return this; + } + + /** + * Disables automatic request recovery and re-execution. + */ + public final HttpClientBuilder disableAutomaticRetries() { + automaticRetriesDisabled = true; + return this; + } + + /** + * Assigns default proxy value. + * <p/> + * Please note this value can be overridden by the {@link #setRoutePlanner( + * ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner)} method. + */ + public final HttpClientBuilder setProxy(final HttpHost proxy) { + this.proxy = proxy; + return this; + } + + /** + * Assigns {@link HttpRoutePlanner} instance. + */ + public final HttpClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + return this; + } + + /** + * Assigns {@link RedirectStrategy} instance. + * <p/> + * Please note this value can be overridden by the {@link #disableRedirectHandling()} + * method. +` */ + public final HttpClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) { + this.redirectStrategy = redirectStrategy; + return this; + } + + /** + * Disables automatic redirect handling. + */ + public final HttpClientBuilder disableRedirectHandling() { + redirectHandlingDisabled = true; + return this; + } + + /** + * Assigns {@link ConnectionBackoffStrategy} instance. + */ + public final HttpClientBuilder setConnectionBackoffStrategy( + final ConnectionBackoffStrategy connectionBackoffStrategy) { + this.connectionBackoffStrategy = connectionBackoffStrategy; + return this; + } + + /** + * Assigns {@link BackoffManager} instance. + */ + public final HttpClientBuilder setBackoffManager(final BackoffManager backoffManager) { + this.backoffManager = backoffManager; + return this; + } + + /** + * Assigns {@link ServiceUnavailableRetryStrategy} instance. + */ + public final HttpClientBuilder setServiceUnavailableRetryStrategy( + final ServiceUnavailableRetryStrategy serviceUnavailStrategy) { + this.serviceUnavailStrategy = serviceUnavailStrategy; + return this; + } + + /** + * Assigns default {@link CookieStore} instance which will be used for + * request execution if not explicitly set in the client execution context. + */ + public final HttpClientBuilder setDefaultCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + return this; + } + + /** + * Assigns default {@link CredentialsProvider} instance which will be used + * for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultCredentialsProvider( + final CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Assigns default {@link ch.boye.httpclientandroidlib.auth.AuthScheme} registry which will + * be used for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultAuthSchemeRegistry( + final Lookup<AuthSchemeProvider> authSchemeRegistry) { + this.authSchemeRegistry = authSchemeRegistry; + return this; + } + + /** + * Assigns default {@link ch.boye.httpclientandroidlib.cookie.CookieSpec} registry which will + * be used for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultCookieSpecRegistry( + final Lookup<CookieSpecProvider> cookieSpecRegistry) { + this.cookieSpecRegistry = cookieSpecRegistry; + return this; + } + + /** + * Assigns default {@link RequestConfig} instance which will be used + * for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultRequestConfig(final RequestConfig config) { + this.defaultRequestConfig = config; + return this; + } + + /** + * Use system properties when creating and configuring default + * implementations. + */ + public final HttpClientBuilder useSystemProperties() { + systemProperties = true; + return this; + } + + /** + * For internal use. + */ + protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) { + return mainExec; + } + + /** + * For internal use. + */ + protected ClientExecChain decorateProtocolExec(final ClientExecChain protocolExec) { + return protocolExec; + } + + /** + * For internal use. + */ + protected void addCloseable(final Closeable closeable) { + if (closeable == null) { + return; + } + if (closeables == null) { + closeables = new ArrayList<Closeable>(); + } + closeables.add(closeable); + } + + private static String[] split(final String s) { + if (TextUtils.isBlank(s)) { + return null; + } + return s.split(" *, *"); + } + + public CloseableHttpClient build() { + // Create main request executor + HttpRequestExecutor requestExec = this.requestExec; + if (requestExec == null) { + requestExec = new HttpRequestExecutor(); + } + HttpClientConnectionManager connManager = this.connManager; + if (connManager == null) { + LayeredConnectionSocketFactory sslSocketFactory = this.sslSocketFactory; + if (sslSocketFactory == null) { + final String[] supportedProtocols = systemProperties ? split( + System.getProperty("https.protocols")) : null; + final String[] supportedCipherSuites = systemProperties ? split( + System.getProperty("https.cipherSuites")) : null; + X509HostnameVerifier hostnameVerifier = this.hostnameVerifier; + if (hostnameVerifier == null) { + hostnameVerifier = SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; + } + if (sslcontext != null) { + sslSocketFactory = new SSLConnectionSocketFactory( + sslcontext, supportedProtocols, supportedCipherSuites, hostnameVerifier); + } else { + if (systemProperties) { + sslSocketFactory = new SSLConnectionSocketFactory( + (SSLSocketFactory) SSLSocketFactory.getDefault(), + supportedProtocols, supportedCipherSuites, hostnameVerifier); + } else { + sslSocketFactory = new SSLConnectionSocketFactory( + SSLContexts.createDefault(), + hostnameVerifier); + } + } + } + @SuppressWarnings("resource") + final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager( + RegistryBuilder.<ConnectionSocketFactory>create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build()); + if (defaultSocketConfig != null) { + poolingmgr.setDefaultSocketConfig(defaultSocketConfig); + } + if (defaultConnectionConfig != null) { + poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig); + } + if (systemProperties) { + String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + s = System.getProperty("http.maxConnections", "5"); + final int max = Integer.parseInt(s); + poolingmgr.setDefaultMaxPerRoute(max); + poolingmgr.setMaxTotal(2 * max); + } + } + if (maxConnTotal > 0) { + poolingmgr.setMaxTotal(maxConnTotal); + } + if (maxConnPerRoute > 0) { + poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute); + } + connManager = poolingmgr; + } + ConnectionReuseStrategy reuseStrategy = this.reuseStrategy; + if (reuseStrategy == null) { + if (systemProperties) { + final String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE; + } else { + reuseStrategy = NoConnectionReuseStrategy.INSTANCE; + } + } else { + reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE; + } + } + ConnectionKeepAliveStrategy keepAliveStrategy = this.keepAliveStrategy; + if (keepAliveStrategy == null) { + keepAliveStrategy = DefaultConnectionKeepAliveStrategy.INSTANCE; + } + AuthenticationStrategy targetAuthStrategy = this.targetAuthStrategy; + if (targetAuthStrategy == null) { + targetAuthStrategy = TargetAuthenticationStrategy.INSTANCE; + } + AuthenticationStrategy proxyAuthStrategy = this.proxyAuthStrategy; + if (proxyAuthStrategy == null) { + proxyAuthStrategy = ProxyAuthenticationStrategy.INSTANCE; + } + UserTokenHandler userTokenHandler = this.userTokenHandler; + if (userTokenHandler == null) { + if (!connectionStateDisabled) { + userTokenHandler = DefaultUserTokenHandler.INSTANCE; + } else { + userTokenHandler = NoopUserTokenHandler.INSTANCE; + } + } + ClientExecChain execChain = new MainClientExec( + requestExec, + connManager, + reuseStrategy, + keepAliveStrategy, + targetAuthStrategy, + proxyAuthStrategy, + userTokenHandler); + + execChain = decorateMainExec(execChain); + + HttpProcessor httpprocessor = this.httpprocessor; + if (httpprocessor == null) { + + String userAgent = this.userAgent; + if (userAgent == null) { + if (systemProperties) { + userAgent = System.getProperty("http.agent"); + } + if (userAgent == null) { + userAgent = DEFAULT_USER_AGENT; + } + } + + final HttpProcessorBuilder b = HttpProcessorBuilder.create(); + if (requestFirst != null) { + for (final HttpRequestInterceptor i: requestFirst) { + b.addFirst(i); + } + } + if (responseFirst != null) { + for (final HttpResponseInterceptor i: responseFirst) { + b.addFirst(i); + } + } + b.addAll( + new RequestDefaultHeaders(defaultHeaders), + new RequestContent(), + new RequestTargetHost(), + new RequestClientConnControl(), + new RequestUserAgent(userAgent), + new RequestExpectContinue()); + if (!cookieManagementDisabled) { + b.add(new RequestAddCookies()); + } + if (!contentCompressionDisabled) { + b.add(new RequestAcceptEncoding()); + } + if (!authCachingDisabled) { + b.add(new RequestAuthCache()); + } + if (!cookieManagementDisabled) { + b.add(new ResponseProcessCookies()); + } + if (!contentCompressionDisabled) { + b.add(new ResponseContentEncoding()); + } + if (requestLast != null) { + for (final HttpRequestInterceptor i: requestLast) { + b.addLast(i); + } + } + if (responseLast != null) { + for (final HttpResponseInterceptor i: responseLast) { + b.addLast(i); + } + } + httpprocessor = b.build(); + } + execChain = new ProtocolExec(execChain, httpprocessor); + + execChain = decorateProtocolExec(execChain); + + // Add request retry executor, if not disabled + if (!automaticRetriesDisabled) { + HttpRequestRetryHandler retryHandler = this.retryHandler; + if (retryHandler == null) { + retryHandler = DefaultHttpRequestRetryHandler.INSTANCE; + } + execChain = new RetryExec(execChain, retryHandler); + } + + HttpRoutePlanner routePlanner = this.routePlanner; + if (routePlanner == null) { + SchemePortResolver schemePortResolver = this.schemePortResolver; + if (schemePortResolver == null) { + schemePortResolver = DefaultSchemePortResolver.INSTANCE; + } + if (proxy != null) { + routePlanner = new DefaultProxyRoutePlanner(proxy, schemePortResolver); + } else if (systemProperties) { + routePlanner = new SystemDefaultRoutePlanner( + schemePortResolver, ProxySelector.getDefault()); + } else { + routePlanner = new DefaultRoutePlanner(schemePortResolver); + } + } + // Add redirect executor, if not disabled + if (!redirectHandlingDisabled) { + RedirectStrategy redirectStrategy = this.redirectStrategy; + if (redirectStrategy == null) { + redirectStrategy = DefaultRedirectStrategy.INSTANCE; + } + execChain = new RedirectExec(execChain, routePlanner, redirectStrategy); + } + + // Optionally, add service unavailable retry executor + final ServiceUnavailableRetryStrategy serviceUnavailStrategy = this.serviceUnavailStrategy; + if (serviceUnavailStrategy != null) { + execChain = new ServiceUnavailableRetryExec(execChain, serviceUnavailStrategy); + } + // Optionally, add connection back-off executor + final BackoffManager backoffManager = this.backoffManager; + final ConnectionBackoffStrategy connectionBackoffStrategy = this.connectionBackoffStrategy; + if (backoffManager != null && connectionBackoffStrategy != null) { + execChain = new BackoffStrategyExec(execChain, connectionBackoffStrategy, backoffManager); + } + + Lookup<AuthSchemeProvider> authSchemeRegistry = this.authSchemeRegistry; + if (authSchemeRegistry == null) { + authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create() + .register(AuthSchemes.BASIC, new BasicSchemeFactory()) + .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) + .register(AuthSchemes.NTLM, new NTLMSchemeFactory()) + /* SPNegoSchemeFactory removed by HttpClient for Android script. */ + /* KerberosSchemeFactory removed by HttpClient for Android script. */ + .build(); + } + Lookup<CookieSpecProvider> cookieSpecRegistry = this.cookieSpecRegistry; + if (cookieSpecRegistry == null) { + cookieSpecRegistry = RegistryBuilder.<CookieSpecProvider>create() + .register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory()) + .register(CookieSpecs.STANDARD, new RFC2965SpecFactory()) + .register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory()) + .register(CookieSpecs.NETSCAPE, new NetscapeDraftSpecFactory()) + .register(CookieSpecs.IGNORE_COOKIES, new IgnoreSpecFactory()) + .register("rfc2109", new RFC2109SpecFactory()) + .register("rfc2965", new RFC2965SpecFactory()) + .build(); + } + + CookieStore defaultCookieStore = this.cookieStore; + if (defaultCookieStore == null) { + defaultCookieStore = new BasicCookieStore(); + } + + CredentialsProvider defaultCredentialsProvider = this.credentialsProvider; + if (defaultCredentialsProvider == null) { + if (systemProperties) { + defaultCredentialsProvider = new SystemDefaultCredentialsProvider(); + } else { + defaultCredentialsProvider = new BasicCredentialsProvider(); + } + } + + return new InternalHttpClient( + execChain, + connManager, + routePlanner, + cookieSpecRegistry, + authSchemeRegistry, + defaultCookieStore, + defaultCredentialsProvider, + defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT, + closeables != null ? new ArrayList<Closeable>(closeables) : null); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClients.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClients.java new file mode 100644 index 000000000..294ac636b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClients.java @@ -0,0 +1,85 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager; + +/** + * Factory methods for {@link CloseableHttpClient} instances. + * @since 4.3 + */ +@Immutable +public class HttpClients { + + private HttpClients() { + super(); + } + + /** + * Creates builder object for construction of custom + * {@link CloseableHttpClient} instances. + */ + public static HttpClientBuilder custom() { + return HttpClientBuilder.create(); + } + + /** + * Creates {@link CloseableHttpClient} instance with default + * configuration. + */ + public static CloseableHttpClient createDefault() { + return HttpClientBuilder.create().build(); + } + + /** + * Creates {@link CloseableHttpClient} instance with default + * configuration based on ssytem properties. + */ + public static CloseableHttpClient createSystem() { + return HttpClientBuilder.create().useSystemProperties().build(); + } + + /** + * Creates {@link CloseableHttpClient} instance that implements + * the most basic HTTP protocol support. + */ + public static CloseableHttpClient createMinimal() { + return new MinimalHttpClient(new PoolingHttpClientConnectionManager()); + } + + /** + * Creates {@link CloseableHttpClient} instance that implements + * the most basic HTTP protocol support. + */ + public static CloseableHttpClient createMinimal(final HttpClientConnectionManager connManager) { + return new MinimalHttpClient(connManager); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java new file mode 100644 index 000000000..b5f932403 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java @@ -0,0 +1,118 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.concurrent.FutureTask; + +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; + +/** + * FutureTask implementation that wraps a HttpAsyncClientCallable and exposes various task + * specific metrics. + * + * @param <V> + */ +public class HttpRequestFutureTask<V> extends FutureTask<V> { + + private final HttpUriRequest request; + private final HttpRequestTaskCallable<V> callable; + + public HttpRequestFutureTask( + final HttpUriRequest request, + final HttpRequestTaskCallable<V> httpCallable) { + super(httpCallable); + this.request = request; + this.callable = httpCallable; + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.FutureTask#cancel(boolean) + */ + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + callable.cancel(); + if (mayInterruptIfRunning) { + request.abort(); + } + return super.cancel(mayInterruptIfRunning); + } + + /** + * @return the time in millis the task was scheduled. + */ + public long scheduledTime() { + return callable.getScheduled(); + } + + /** + * @return the time in millis the task was started. + */ + public long startedTime() { + return callable.getStarted(); + } + + /** + * @return the time in millis the task was finished/cancelled. + */ + public long endedTime() { + if (isDone()) { + return callable.getEnded(); + } else { + throw new IllegalStateException("Task is not done yet"); + } + } + + /** + * @return the time in millis it took to make the request (excluding the time it was + * scheduled to be executed). + */ + public long requestDuration() { + if (isDone()) { + return endedTime() - startedTime(); + } else { + throw new IllegalStateException("Task is not done yet"); + } + } + + /** + * @return the time in millis it took to execute the task from the moment it was scheduled. + */ + public long taskDuration() { + if (isDone()) { + return endedTime() - scheduledTime(); + } else { + throw new IllegalStateException("Task is not done yet"); + } + } + + @Override + public String toString() { + return request.getRequestLine().getUri(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java new file mode 100644 index 000000000..42e95f0aa --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java @@ -0,0 +1,119 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.concurrent.FutureCallback; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +class HttpRequestTaskCallable<V> implements Callable<V> { + + private final HttpUriRequest request; + private final HttpClient httpclient; + private final AtomicBoolean cancelled = new AtomicBoolean(false); + + private final long scheduled = System.currentTimeMillis(); + private long started = -1; + private long ended = -1; + + private final HttpContext context; + private final ResponseHandler<V> responseHandler; + private final FutureCallback<V> callback; + + private final FutureRequestExecutionMetrics metrics; + + HttpRequestTaskCallable( + final HttpClient httpClient, + final HttpUriRequest request, + final HttpContext context, + final ResponseHandler<V> responseHandler, + final FutureCallback<V> callback, + final FutureRequestExecutionMetrics metrics) { + this.httpclient = httpClient; + this.responseHandler = responseHandler; + this.request = request; + this.context = context; + this.callback = callback; + this.metrics = metrics; + } + + public long getScheduled() { + return scheduled; + } + + public long getStarted() { + return started; + } + + public long getEnded() { + return ended; + } + + public V call() throws Exception { + if (!cancelled.get()) { + try { + metrics.getActiveConnections().incrementAndGet(); + started = System.currentTimeMillis(); + try { + metrics.getScheduledConnections().decrementAndGet(); + final V result = httpclient.execute(request, responseHandler, context); + ended = System.currentTimeMillis(); + metrics.getSuccessfulConnections().increment(started); + if (callback != null) { + callback.completed(result); + } + return result; + } catch (final Exception e) { + metrics.getFailedConnections().increment(started); + ended = System.currentTimeMillis(); + if (callback != null) { + callback.failed(e); + } + throw e; + } + } finally { + metrics.getRequests().increment(started); + metrics.getTasks().increment(started); + metrics.getActiveConnections().decrementAndGet(); + } + } else { + throw new IllegalStateException("call has been cancelled for request " + request.getURI()); + } + } + + public void cancel() { + cancelled.set(true); + if (callback != null) { + callback.cancelled(); + } + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java new file mode 100644 index 000000000..c445fe181 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java @@ -0,0 +1,242 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.Configurable; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.params.HttpClientParamConfig; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpParamsNames; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Internal class. + * + * @since 4.3 + */ +@ThreadSafe +@SuppressWarnings("deprecation") +class InternalHttpClient extends CloseableHttpClient { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final ClientExecChain execChain; + private final HttpClientConnectionManager connManager; + private final HttpRoutePlanner routePlanner; + private final Lookup<CookieSpecProvider> cookieSpecRegistry; + private final Lookup<AuthSchemeProvider> authSchemeRegistry; + private final CookieStore cookieStore; + private final CredentialsProvider credentialsProvider; + private final RequestConfig defaultConfig; + private final List<Closeable> closeables; + + public InternalHttpClient( + final ClientExecChain execChain, + final HttpClientConnectionManager connManager, + final HttpRoutePlanner routePlanner, + final Lookup<CookieSpecProvider> cookieSpecRegistry, + final Lookup<AuthSchemeProvider> authSchemeRegistry, + final CookieStore cookieStore, + final CredentialsProvider credentialsProvider, + final RequestConfig defaultConfig, + final List<Closeable> closeables) { + super(); + Args.notNull(execChain, "HTTP client exec chain"); + Args.notNull(connManager, "HTTP connection manager"); + Args.notNull(routePlanner, "HTTP route planner"); + this.execChain = execChain; + this.connManager = connManager; + this.routePlanner = routePlanner; + this.cookieSpecRegistry = cookieSpecRegistry; + this.authSchemeRegistry = authSchemeRegistry; + this.cookieStore = cookieStore; + this.credentialsProvider = credentialsProvider; + this.defaultConfig = defaultConfig; + this.closeables = closeables; + } + + private HttpRoute determineRoute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws HttpException { + HttpHost host = target; + if (host == null) { + host = (HttpHost) request.getParams().getParameter(ClientPNames.DEFAULT_HOST); + } + return this.routePlanner.determineRoute(host, request, context); + } + + private void setupContext(final HttpClientContext context) { + if (context.getAttribute(HttpClientContext.TARGET_AUTH_STATE) == null) { + context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, new AuthState()); + } + if (context.getAttribute(HttpClientContext.PROXY_AUTH_STATE) == null) { + context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, new AuthState()); + } + if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) { + context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) { + context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, this.cookieSpecRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) { + context.setAttribute(HttpClientContext.COOKIE_STORE, this.cookieStore); + } + if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) { + context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credentialsProvider); + } + if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { + context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.defaultConfig); + } + } + + @Override + protected CloseableHttpResponse doExecute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + Args.notNull(request, "HTTP request"); + HttpExecutionAware execAware = null; + if (request instanceof HttpExecutionAware) { + execAware = (HttpExecutionAware) request; + } + try { + final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request); + final HttpClientContext localcontext = HttpClientContext.adapt( + context != null ? context : new BasicHttpContext()); + RequestConfig config = null; + if (request instanceof Configurable) { + config = ((Configurable) request).getConfig(); + } + if (config == null) { + final HttpParams params = request.getParams(); + if (params instanceof HttpParamsNames) { + if (!((HttpParamsNames) params).getNames().isEmpty()) { + config = HttpClientParamConfig.getRequestConfig(params); + } + } else { + config = HttpClientParamConfig.getRequestConfig(params); + } + } + if (config != null) { + localcontext.setRequestConfig(config); + } + setupContext(localcontext); + final HttpRoute route = determineRoute(target, wrapper, localcontext); + return this.execChain.execute(route, wrapper, localcontext, execAware); + } catch (final HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } + + public void close() { + this.connManager.shutdown(); + if (this.closeables != null) { + for (final Closeable closeable: this.closeables) { + try { + closeable.close(); + } catch (final IOException ex) { + this.log.error(ex.getMessage(), ex); + } + } + } + } + + public HttpParams getParams() { + throw new UnsupportedOperationException(); + } + + public ClientConnectionManager getConnectionManager() { + + return new ClientConnectionManager() { + + public void shutdown() { + connManager.shutdown(); + } + + public ClientConnectionRequest requestConnection( + final HttpRoute route, final Object state) { + throw new UnsupportedOperationException(); + } + + public void releaseConnection( + final ManagedClientConnection conn, + final long validDuration, final TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + public SchemeRegistry getSchemeRegistry() { + throw new UnsupportedOperationException(); + } + + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + connManager.closeIdleConnections(idletime, tunit); + } + + public void closeExpiredConnections() { + connManager.closeExpiredConnections(); + } + + }; + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java new file mode 100644 index 000000000..47688e642 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java @@ -0,0 +1,65 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.methods.HttpPost; + +/** + * Lax {@link ch.boye.httpclientandroidlib.client.RedirectStrategy} implementation + * that automatically redirects all HEAD, GET and POST requests. + * This strategy relaxes restrictions on automatic redirection of + * POST methods imposed by the HTTP specification. + * + * @since 4.2 + */ +@Immutable +public class LaxRedirectStrategy extends DefaultRedirectStrategy { + + /** + * Redirectable methods. + */ + private static final String[] REDIRECT_METHODS = new String[] { + HttpGet.METHOD_NAME, + HttpPost.METHOD_NAME, + HttpHead.METHOD_NAME + }; + + @Override + protected boolean isRedirectable(final String method) { + for (final String m: REDIRECT_METHODS) { + if (m.equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java new file mode 100644 index 000000000..8b0c21127 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java @@ -0,0 +1,156 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.Configurable; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.execchain.MinimalClientExec; +import ch.boye.httpclientandroidlib.params.BasicHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Internal class. + * + * @since 4.3 + */ +@ThreadSafe +@SuppressWarnings("deprecation") +class MinimalHttpClient extends CloseableHttpClient { + + private final HttpClientConnectionManager connManager; + private final MinimalClientExec requestExecutor; + private final HttpParams params; + + public MinimalHttpClient( + final HttpClientConnectionManager connManager) { + super(); + this.connManager = Args.notNull(connManager, "HTTP connection manager"); + this.requestExecutor = new MinimalClientExec( + new HttpRequestExecutor(), + connManager, + DefaultConnectionReuseStrategy.INSTANCE, + DefaultConnectionKeepAliveStrategy.INSTANCE); + this.params = new BasicHttpParams(); + } + + @Override + protected CloseableHttpResponse doExecute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + Args.notNull(target, "Target host"); + Args.notNull(request, "HTTP request"); + HttpExecutionAware execAware = null; + if (request instanceof HttpExecutionAware) { + execAware = (HttpExecutionAware) request; + } + try { + final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request); + final HttpClientContext localcontext = HttpClientContext.adapt( + context != null ? context : new BasicHttpContext()); + final HttpRoute route = new HttpRoute(target); + RequestConfig config = null; + if (request instanceof Configurable) { + config = ((Configurable) request).getConfig(); + } + if (config != null) { + localcontext.setRequestConfig(config); + } + return this.requestExecutor.execute(route, wrapper, localcontext, execAware); + } catch (final HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } + + public HttpParams getParams() { + return this.params; + } + + public void close() { + this.connManager.shutdown(); + } + + public ClientConnectionManager getConnectionManager() { + + return new ClientConnectionManager() { + + public void shutdown() { + connManager.shutdown(); + } + + public ClientConnectionRequest requestConnection( + final HttpRoute route, final Object state) { + throw new UnsupportedOperationException(); + } + + public void releaseConnection( + final ManagedClientConnection conn, + final long validDuration, final TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + public SchemeRegistry getSchemeRegistry() { + throw new UnsupportedOperationException(); + } + + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + connManager.closeIdleConnections(idletime, tunit); + } + + public void closeExpiredConnections() { + connManager.closeExpiredConnections(); + } + + }; + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java new file mode 100644 index 000000000..1e8eb3bde --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * Noop implementation of {@link UserTokenHandler} that always returns <code>null</code>. + * + * @since 4.3 + */ +@Immutable +public class NoopUserTokenHandler implements UserTokenHandler { + + public static final NoopUserTokenHandler INSTANCE = new NoopUserTokenHandler(); + + public Object getUserToken(final HttpContext context) { + return null; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java new file mode 100644 index 000000000..8cad78d75 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; + +/** + * This is a {@link ConnectionBackoffStrategy} that never backs off, + * for compatibility with existing behavior. + * + * @since 4.2 + */ +public class NullBackoffStrategy implements ConnectionBackoffStrategy { + + public boolean shouldBackoff(final Throwable t) { + return false; + } + + public boolean shouldBackoff(final HttpResponse resp) { + return false; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java new file mode 100644 index 000000000..636143630 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationStrategy} implementation + * for proxy host authentication. + * + * @since 4.2 + */ +@Immutable +public class ProxyAuthenticationStrategy extends AuthenticationStrategyImpl { + + public static final ProxyAuthenticationStrategy INSTANCE = new ProxyAuthenticationStrategy(); + + public ProxyAuthenticationStrategy() { + super(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, AUTH.PROXY_AUTH); + } + + @Override + Collection<String> getPreferredAuthSchemes(final RequestConfig config) { + return config.getProxyPreferredAuthSchemes(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyClient.java new file mode 100644 index 000000000..e1211bc4b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyClient.java @@ -0,0 +1,254 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.net.Socket; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.AuthSchemeRegistry; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.params.HttpClientParamConfig; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.conn.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.RouteInfo.LayerType; +import ch.boye.httpclientandroidlib.conn.routing.RouteInfo.TunnelType; +import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.auth.BasicSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.DigestSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.HttpAuthenticator; +/* KerberosSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.auth.NTLMSchemeFactory; +/* SPNegoSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.conn.ManagedHttpClientConnectionFactory; +import ch.boye.httpclientandroidlib.impl.execchain.TunnelRefusedException; +import ch.boye.httpclientandroidlib.message.BasicHttpRequest; +import ch.boye.httpclientandroidlib.params.BasicHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParamConfig; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpCoreContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.ImmutableHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.protocol.RequestUserAgent; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * ProxyClient can be used to establish a tunnel via an HTTP proxy. + */ +@SuppressWarnings("deprecation") +public class ProxyClient { + + private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; + private final ConnectionConfig connectionConfig; + private final RequestConfig requestConfig; + private final HttpProcessor httpProcessor; + private final HttpRequestExecutor requestExec; + private final ProxyAuthenticationStrategy proxyAuthStrategy; + private final HttpAuthenticator authenticator; + private final AuthState proxyAuthState; + private final AuthSchemeRegistry authSchemeRegistry; + private final ConnectionReuseStrategy reuseStrategy; + + /** + * @since 4.3 + */ + public ProxyClient( + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, + final ConnectionConfig connectionConfig, + final RequestConfig requestConfig) { + super(); + this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; + this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; + this.requestConfig = requestConfig != null ? requestConfig : RequestConfig.DEFAULT; + this.httpProcessor = new ImmutableHttpProcessor( + new RequestTargetHost(), new RequestClientConnControl(), new RequestUserAgent()); + this.requestExec = new HttpRequestExecutor(); + this.proxyAuthStrategy = new ProxyAuthenticationStrategy(); + this.authenticator = new HttpAuthenticator(); + this.proxyAuthState = new AuthState(); + this.authSchemeRegistry = new AuthSchemeRegistry(); + this.authSchemeRegistry.register(AuthSchemes.BASIC, new BasicSchemeFactory()); + this.authSchemeRegistry.register(AuthSchemes.DIGEST, new DigestSchemeFactory()); + this.authSchemeRegistry.register(AuthSchemes.NTLM, new NTLMSchemeFactory()); + /* SPNegoSchemeFactory removed by HttpClient for Android script. */ + /* KerberosSchemeFactory removed by HttpClient for Android script. */ + this.reuseStrategy = new DefaultConnectionReuseStrategy(); + } + + /** + * @deprecated (4.3) use {@link ProxyClient#ProxyClient(HttpConnectionFactory, ConnectionConfig, RequestConfig)} + */ + @Deprecated + public ProxyClient(final HttpParams params) { + this(null, + HttpParamConfig.getConnectionConfig(params), + HttpClientParamConfig.getRequestConfig(params)); + } + + /** + * @since 4.3 + */ + public ProxyClient(final RequestConfig requestConfig) { + this(null, null, requestConfig); + } + + public ProxyClient() { + this(null, null, null); + } + + /** + * @deprecated (4.3) do not use. + */ + @Deprecated + public HttpParams getParams() { + return new BasicHttpParams(); + } + + /** + * @deprecated (4.3) do not use. + */ + @Deprecated + public AuthSchemeRegistry getAuthSchemeRegistry() { + return this.authSchemeRegistry; + } + + public Socket tunnel( + final HttpHost proxy, + final HttpHost target, + final Credentials credentials) throws IOException, HttpException { + Args.notNull(proxy, "Proxy host"); + Args.notNull(target, "Target host"); + Args.notNull(credentials, "Credentials"); + HttpHost host = target; + if (host.getPort() <= 0) { + host = new HttpHost(host.getHostName(), 80, host.getSchemeName()); + } + final HttpRoute route = new HttpRoute( + host, + this.requestConfig.getLocalAddress(), + proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN); + + final ManagedHttpClientConnection conn = this.connFactory.create( + route, this.connectionConfig); + final HttpContext context = new BasicHttpContext(); + HttpResponse response; + + final HttpRequest connect = new BasicHttpRequest( + "CONNECT", host.toHostString(), HttpVersion.HTTP_1_1); + + final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(proxy), credentials); + + // Populate the execution context + context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target); + context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); + context.setAttribute(HttpCoreContext.HTTP_REQUEST, connect); + context.setAttribute(HttpClientContext.HTTP_ROUTE, route); + context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, this.proxyAuthState); + context.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider); + context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry); + context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.requestConfig); + + this.requestExec.preProcess(connect, this.httpProcessor, context); + + for (;;) { + if (!conn.isOpen()) { + final Socket socket = new Socket(proxy.getHostName(), proxy.getPort()); + conn.bind(socket); + } + + this.authenticator.generateAuthResponse(connect, this.proxyAuthState, context); + + response = this.requestExec.execute(connect, conn, context); + + final int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + if (this.authenticator.isAuthenticationRequested(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + if (this.authenticator.handleAuthChallenge(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + // Consume response content + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + } else { + conn.close(); + } + // discard previous auth header + connect.removeHeaders(AUTH.PROXY_AUTH_RESP); + } else { + break; + } + } else { + break; + } + } + + final int status = response.getStatusLine().getStatusCode(); + + if (status > 299) { + + // Buffer response content + final HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + conn.close(); + throw new TunnelRefusedException("CONNECT refused by proxy: " + + response.getStatusLine(), response); + } + return conn.getSocket(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java new file mode 100644 index 000000000..872dc167f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java @@ -0,0 +1,227 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; + +/** + * This class represents a collection of {@link java.net.URI}s used + * as redirect locations. + * + * @since 4.0 + */ +@NotThreadSafe // HashSet/ArrayList are not synch. +public class RedirectLocations extends AbstractList<Object> { + + private final Set<URI> unique; + private final List<URI> all; + + public RedirectLocations() { + super(); + this.unique = new HashSet<URI>(); + this.all = new ArrayList<URI>(); + } + + /** + * Test if the URI is present in the collection. + */ + public boolean contains(final URI uri) { + return this.unique.contains(uri); + } + + /** + * Adds a new URI to the collection. + */ + public void add(final URI uri) { + this.unique.add(uri); + this.all.add(uri); + } + + /** + * Removes a URI from the collection. + */ + public boolean remove(final URI uri) { + final boolean removed = this.unique.remove(uri); + if (removed) { + final Iterator<URI> it = this.all.iterator(); + while (it.hasNext()) { + final URI current = it.next(); + if (current.equals(uri)) { + it.remove(); + } + } + } + return removed; + } + + /** + * Returns all redirect {@link URI}s in the order they were added to the collection. + * + * @return list of all URIs + * + * @since 4.1 + */ + public List<URI> getAll() { + return new ArrayList<URI>(this.all); + } + + /** + * Returns the URI at the specified position in this list. + * + * @param index + * index of the location to return + * @return the URI at the specified position in this list + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index >= size()</tt>) + * @since 4.3 + */ + @Override + public URI get(final int index) { + return this.all.get(index); + } + + /** + * Returns the number of elements in this list. If this list contains more + * than <tt>Integer.MAX_VALUE</tt> elements, returns + * <tt>Integer.MAX_VALUE</tt>. + * + * @return the number of elements in this list + * @since 4.3 + */ + @Override + public int size() { + return this.all.size(); + } + + /** + * Replaces the URI at the specified position in this list with the + * specified element (must be a URI). + * + * @param index + * index of the element to replace + * @param element + * URI to be stored at the specified position + * @return the URI previously at the specified position + * @throws UnsupportedOperationException + * if the <tt>set</tt> operation is not supported by this list + * @throws ClassCastException + * if the element is not a {@link URI} + * @throws NullPointerException + * if the specified element is null and this list does not + * permit null elements + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index >= size()</tt>) + * @since 4.3 + */ + @Override + public Object set(final int index, final Object element) { + final URI removed = this.all.set(index, (URI) element); + this.unique.remove(removed); + this.unique.add((URI) element); + if (this.all.size() != this.unique.size()) { + this.unique.addAll(this.all); + } + return removed; + } + + /** + * Inserts the specified element at the specified position in this list + * (must be a URI). Shifts the URI currently at that position (if any) and + * any subsequent URIs to the right (adds one to their indices). + * + * @param index + * index at which the specified element is to be inserted + * @param element + * URI to be inserted + * @throws UnsupportedOperationException + * if the <tt>add</tt> operation is not supported by this list + * @throws ClassCastException + * if the element is not a {@link URI} + * @throws NullPointerException + * if the specified element is null and this list does not + * permit null elements + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index > size()</tt>) + * @since 4.3 + */ + @Override + public void add(final int index, final Object element) { + this.all.add(index, (URI) element); + this.unique.add((URI) element); + } + + /** + * Removes the URI at the specified position in this list. Shifts any + * subsequent URIs to the left (subtracts one from their indices). Returns + * the URI that was removed from the list. + * + * @param index + * the index of the URI to be removed + * @return the URI previously at the specified position + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index >= size()</tt>) + * @since 4.3 + */ + @Override + public URI remove(final int index) { + final URI removed = this.all.remove(index); + this.unique.remove(removed); + if (this.all.size() != this.unique.size()) { + this.unique.addAll(this.all); + } + return removed; + } + + /** + * Returns <tt>true</tt> if this collection contains the specified element. + * More formally, returns <tt>true</tt> if and only if this collection + * contains at least one element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt>. + * + * @param o element whose presence in this collection is to be tested + * @return <tt>true</tt> if this collection contains the specified + * element + */ + @Override + public boolean contains(final Object o) { + return this.unique.contains(o); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java new file mode 100644 index 000000000..3577b61a6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java @@ -0,0 +1,164 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.RequestLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.message.AbstractHttpMessage; +import ch.boye.httpclientandroidlib.message.BasicRequestLine; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * A wrapper class for {@link HttpRequest}s that can be used to change + * properties of the current request without modifying the original + * object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @since 4.0 + * + * @deprecated (4.3) do not use. + */ +@NotThreadSafe +@Deprecated +public class RequestWrapper extends AbstractHttpMessage implements HttpUriRequest { + + private final HttpRequest original; + + private URI uri; + private String method; + private ProtocolVersion version; + private int execCount; + + public RequestWrapper(final HttpRequest request) throws ProtocolException { + super(); + Args.notNull(request, "HTTP request"); + this.original = request; + setParams(request.getParams()); + setHeaders(request.getAllHeaders()); + // Make a copy of the original URI + if (request instanceof HttpUriRequest) { + this.uri = ((HttpUriRequest) request).getURI(); + this.method = ((HttpUriRequest) request).getMethod(); + this.version = null; + } else { + final RequestLine requestLine = request.getRequestLine(); + try { + this.uri = new URI(requestLine.getUri()); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid request URI: " + + requestLine.getUri(), ex); + } + this.method = requestLine.getMethod(); + this.version = request.getProtocolVersion(); + } + this.execCount = 0; + } + + public void resetHeaders() { + // Make a copy of original headers + this.headergroup.clear(); + setHeaders(this.original.getAllHeaders()); + } + + public String getMethod() { + return this.method; + } + + public void setMethod(final String method) { + Args.notNull(method, "Method name"); + this.method = method; + } + + public ProtocolVersion getProtocolVersion() { + if (this.version == null) { + this.version = HttpProtocolParams.getVersion(getParams()); + } + return this.version; + } + + public void setProtocolVersion(final ProtocolVersion version) { + this.version = version; + } + + + public URI getURI() { + return this.uri; + } + + public void setURI(final URI uri) { + this.uri = uri; + } + + public RequestLine getRequestLine() { + final String method = getMethod(); + final ProtocolVersion ver = getProtocolVersion(); + String uritext = null; + if (uri != null) { + uritext = uri.toASCIIString(); + } + if (uritext == null || uritext.length() == 0) { + uritext = "/"; + } + return new BasicRequestLine(method, uritext, ver); + } + + public void abort() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean isAborted() { + return false; + } + + public HttpRequest getOriginal() { + return this.original; + } + + public boolean isRepeatable() { + return true; + } + + public int getExecCount() { + return this.execCount; + } + + public void incrementExecCount() { + this.execCount++; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java new file mode 100644 index 000000000..2e5ff76e9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; + +/** + * A request with the route along which it should be sent. + * + * @since 4.0 + * + * @deprecated (4.3) do not use. + */ +@Deprecated +@NotThreadSafe // RequestWrapper is @NotThreadSafe +public class RoutedRequest { + + protected final RequestWrapper request; // @NotThreadSafe + protected final HttpRoute route; // @Immutable + + /** + * Creates a new routed request. + * + * @param req the request + * @param route the route + */ + public RoutedRequest(final RequestWrapper req, final HttpRoute route) { + super(); + this.request = req; + this.route = route; + } + + public final RequestWrapper getRequest() { + return request; + } + + public final HttpRoute getRoute() { + return route; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java new file mode 100644 index 000000000..ec0ce7e00 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java @@ -0,0 +1,80 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * {@link ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler} which assumes + * that all requested HTTP methods which should be idempotent according + * to RFC-2616 are in fact idempotent and can be retried. + * <p/> + * According to RFC-2616 section 9.1.2 the idempotent HTTP methods are: + * GET, HEAD, PUT, DELETE, OPTIONS, and TRACE + * + * @since 4.2 + */ +@Immutable +public class StandardHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler { + + private final Map<String, Boolean> idempotentMethods; + + /** + * Default constructor + */ + public StandardHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) { + super(retryCount, requestSentRetryEnabled); + this.idempotentMethods = new ConcurrentHashMap<String, Boolean>(); + this.idempotentMethods.put("GET", Boolean.TRUE); + this.idempotentMethods.put("HEAD", Boolean.TRUE); + this.idempotentMethods.put("PUT", Boolean.TRUE); + this.idempotentMethods.put("DELETE", Boolean.TRUE); + this.idempotentMethods.put("OPTIONS", Boolean.TRUE); + this.idempotentMethods.put("TRACE", Boolean.TRUE); + } + + /** + * Default constructor + */ + public StandardHttpRequestRetryHandler() { + this(3, false); + } + + @Override + protected boolean handleAsIdempotent(final HttpRequest request) { + final String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH); + final Boolean b = this.idempotentMethods.get(method); + return b != null && b.booleanValue(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemClock.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemClock.java new file mode 100644 index 000000000..b037af841 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemClock.java @@ -0,0 +1,40 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +/** + * The actual system clock. + * + * @since 4.2 + */ +class SystemClock implements Clock { + + public long getCurrentTime() { + return System.currentTimeMillis(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java new file mode 100644 index 000000000..1327b71dc --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java @@ -0,0 +1,145 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.NTCredentials; +import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Implementation of {@link CredentialsProvider} backed by standard + * JRE {@link Authenticator}. + * + * @since 4.3 + */ +@ThreadSafe +public class SystemDefaultCredentialsProvider implements CredentialsProvider { + + private static final Map<String, String> SCHEME_MAP; + + static { + SCHEME_MAP = new ConcurrentHashMap<String, String>(); + SCHEME_MAP.put(AuthSchemes.BASIC.toUpperCase(Locale.ENGLISH), "Basic"); + SCHEME_MAP.put(AuthSchemes.DIGEST.toUpperCase(Locale.ENGLISH), "Digest"); + SCHEME_MAP.put(AuthSchemes.NTLM.toUpperCase(Locale.ENGLISH), "NTLM"); + SCHEME_MAP.put(AuthSchemes.SPNEGO.toUpperCase(Locale.ENGLISH), "SPNEGO"); + SCHEME_MAP.put(AuthSchemes.KERBEROS.toUpperCase(Locale.ENGLISH), "Kerberos"); + } + + private static String translateScheme(final String key) { + if (key == null) { + return null; + } + final String s = SCHEME_MAP.get(key); + return s != null ? s : key; + } + + private final BasicCredentialsProvider internal; + + /** + * Default constructor. + */ + public SystemDefaultCredentialsProvider() { + super(); + this.internal = new BasicCredentialsProvider(); + } + + public void setCredentials(final AuthScope authscope, final Credentials credentials) { + internal.setCredentials(authscope, credentials); + } + + private static PasswordAuthentication getSystemCreds( + final AuthScope authscope, + final Authenticator.RequestorType requestorType) { + final String hostname = authscope.getHost(); + final int port = authscope.getPort(); + final String protocol = port == 443 ? "https" : "http"; + return Authenticator.requestPasswordAuthentication( + hostname, + null, + port, + protocol, + null, + translateScheme(authscope.getScheme()), + null, + requestorType); + } + + public Credentials getCredentials(final AuthScope authscope) { + Args.notNull(authscope, "Auth scope"); + final Credentials localcreds = internal.getCredentials(authscope); + if (localcreds != null) { + return localcreds; + } + if (authscope.getHost() != null) { + PasswordAuthentication systemcreds = getSystemCreds( + authscope, Authenticator.RequestorType.SERVER); + if (systemcreds == null) { + systemcreds = getSystemCreds( + authscope, Authenticator.RequestorType.PROXY); + } + if (systemcreds != null) { + final String domain = System.getProperty("http.auth.ntlm.domain"); + if (domain != null) { + return new NTCredentials( + systemcreds.getUserName(), + new String(systemcreds.getPassword()), + null, domain); + } else { + if (AuthSchemes.NTLM.equalsIgnoreCase(authscope.getScheme())) { + // Domian may be specified in a fully qualified user name + return new NTCredentials( + systemcreds.getUserName(), + new String(systemcreds.getPassword()), + null, null); + } else { + return new UsernamePasswordCredentials( + systemcreds.getUserName(), + new String(systemcreds.getPassword())); + } + } + } + } + return null; + } + + public void clear() { + internal.clear(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java new file mode 100644 index 000000000..0806537ed --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java @@ -0,0 +1,149 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.ProxySelector; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.NoConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.conn.PoolingClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.ProxySelectorRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.SchemeRegistryFactory; +import ch.boye.httpclientandroidlib.params.HttpParams; + +/** + * An extension of {@link DefaultHttpClient} pre-configured using system properties. + * <p> + * The following system properties are taken into account by this class: + * <ul> + * <li>ssl.TrustManagerFactory.algorithm</li> + * <li>javax.net.ssl.trustStoreType</li> + * <li>javax.net.ssl.trustStore</li> + * <li>javax.net.ssl.trustStoreProvider</li> + * <li>javax.net.ssl.trustStorePassword</li> + * <li>java.home</li> + * <li>ssl.KeyManagerFactory.algorithm</li> + * <li>javax.net.ssl.keyStoreType</li> + * <li>javax.net.ssl.keyStore</li> + * <li>javax.net.ssl.keyStoreProvider</li> + * <li>javax.net.ssl.keyStorePassword</li> + * <li>http.proxyHost</li> + * <li>http.proxyPort</li> + * <li>http.nonProxyHosts</li> + * <li>http.keepAlive</li> + * <li>http.maxConnections</li> + * </ul> + * <p> + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> + * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li> + * </ul> + * </p> + * + * @since 4.2 + * + * @deprecated (4.3) use {@link HttpClientBuilder} + */ +@ThreadSafe +@Deprecated +public class SystemDefaultHttpClient extends DefaultHttpClient { + + public SystemDefaultHttpClient(final HttpParams params) { + super(null, params); + } + + public SystemDefaultHttpClient() { + super(null, null); + } + + @Override + protected ClientConnectionManager createClientConnectionManager() { + final PoolingClientConnectionManager connmgr = new PoolingClientConnectionManager( + SchemeRegistryFactory.createSystemDefault()); + String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + s = System.getProperty("http.maxConnections", "5"); + final int max = Integer.parseInt(s); + connmgr.setDefaultMaxPerRoute(max); + connmgr.setMaxTotal(2 * max); + } + return connmgr; + } + + @Override + protected HttpRoutePlanner createHttpRoutePlanner() { + return new ProxySelectorRoutePlanner(getConnectionManager().getSchemeRegistry(), + ProxySelector.getDefault()); + } + + @Override + protected ConnectionReuseStrategy createConnectionReuseStrategy() { + final String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + return new DefaultConnectionReuseStrategy(); + } else { + return new NoConnectionReuseStrategy(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java new file mode 100644 index 000000000..486167dc1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationStrategy} implementation + * for proxy host authentication. + * + * @since 4.2 + */ +@Immutable +public class TargetAuthenticationStrategy extends AuthenticationStrategyImpl { + + public static final TargetAuthenticationStrategy INSTANCE = new TargetAuthenticationStrategy(); + + public TargetAuthenticationStrategy() { + super(HttpStatus.SC_UNAUTHORIZED, AUTH.WWW_AUTH); + } + + @Override + Collection<String> getPreferredAuthSchemes(final RequestConfig config) { + return config.getTargetPreferredAuthSchemes(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java new file mode 100644 index 000000000..97edac1f0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java @@ -0,0 +1,58 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * Signals that the tunnel request was rejected by the proxy host. + * + * @since 4.0 + * + * @deprecated (4.3) reserved for internal use. + */ +@Deprecated +@Immutable +public class TunnelRefusedException extends HttpException { + + private static final long serialVersionUID = -8646722842745617323L; + + private final HttpResponse response; + + public TunnelRefusedException(final String message, final HttpResponse response) { + super(message); + this.response = response; + } + + public HttpResponse getResponse() { + return this.response; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java new file mode 100644 index 000000000..60ad6ca1f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java @@ -0,0 +1,178 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; + +/** + * Class used to represent an asynchronous revalidation event, such as with + * "stale-while-revalidate" + */ +class AsynchronousValidationRequest implements Runnable { + private final AsynchronousValidator parent; + private final CachingExec cachingExec; + private final HttpRoute route; + private final HttpRequestWrapper request; + private final HttpClientContext context; + private final HttpExecutionAware execAware; + private final HttpCacheEntry cacheEntry; + private final String identifier; + private final int consecutiveFailedAttempts; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Used internally by {@link AsynchronousValidator} to schedule a + * revalidation. + * @param request + * @param context + * @param cacheEntry + * @param identifier + * @param consecutiveFailedAttempts + */ + AsynchronousValidationRequest( + final AsynchronousValidator parent, + final CachingExec cachingExec, + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry cacheEntry, + final String identifier, + final int consecutiveFailedAttempts) { + this.parent = parent; + this.cachingExec = cachingExec; + this.route = route; + this.request = request; + this.context = context; + this.execAware = execAware; + this.cacheEntry = cacheEntry; + this.identifier = identifier; + this.consecutiveFailedAttempts = consecutiveFailedAttempts; + } + + public void run() { + try { + if (revalidateCacheEntry()) { + parent.jobSuccessful(identifier); + } else { + parent.jobFailed(identifier); + } + } finally { + parent.markComplete(identifier); + } + } + + /** + * Revalidate the cache entry and return if the operation was successful. + * Success means a connection to the server was established and replay did + * not indicate a server error. + * @return <code>true</code> if the cache entry was successfully validated; + * otherwise <code>false</code> + */ + protected boolean revalidateCacheEntry() { + try { + final CloseableHttpResponse httpResponse = cachingExec.revalidateCacheEntry(route, request, context, execAware, cacheEntry); + try { + final int statusCode = httpResponse.getStatusLine().getStatusCode(); + return isNotServerError(statusCode) && isNotStale(httpResponse); + } finally { + httpResponse.close(); + } + } catch (final IOException ioe) { + log.debug("Asynchronous revalidation failed due to I/O error", ioe); + return false; + } catch (final HttpException pe) { + log.error("HTTP protocol exception during asynchronous revalidation", pe); + return false; + } catch (final RuntimeException re) { + log.error("RuntimeException thrown during asynchronous revalidation: " + re); + return false; + } + } + + /** + * Return whether the status code indicates a server error or not. + * @param statusCode the status code to be checked + * @return if the status code indicates a server error or not + */ + private boolean isNotServerError(final int statusCode) { + return statusCode < 500; + } + + /** + * Try to detect if the returned response is generated from a stale cache entry. + * @param httpResponse the response to be checked + * @return whether the response is stale or not + */ + private boolean isNotStale(final HttpResponse httpResponse) { + final Header[] warnings = httpResponse.getHeaders(HeaderConstants.WARNING); + if (warnings != null) + { + for (final Header warning : warnings) + { + /** + * warn-codes + * 110 = Response is stale + * 111 = Revalidation failed + */ + final String warningValue = warning.getValue(); + if (warningValue.startsWith("110") || warningValue.startsWith("111")) + { + return false; + } + } + } + return true; + } + + String getIdentifier() { + return identifier; + } + + /** + * The number of consecutively failed revalidation attempts. + * @return the number of consecutively failed revalidation attempts. + */ + public int getConsecutiveFailedAttempts() { + return consecutiveFailedAttempts; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java new file mode 100644 index 000000000..05765a236 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java @@ -0,0 +1,150 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.RejectedExecutionException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; + +/** + * Class used for asynchronous revalidations to be used when the "stale- + * while-revalidate" directive is present + */ +class AsynchronousValidator implements Closeable { + private final SchedulingStrategy schedulingStrategy; + private final Set<String> queued; + private final CacheKeyGenerator cacheKeyGenerator; + private final FailureCache failureCache; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Create AsynchronousValidator which will make revalidation requests + * using an {@link ImmediateSchedulingStrategy}. Its thread + * pool will be configured according to the given {@link CacheConfig}. + * @param config specifies thread pool settings. See + * {@link CacheConfig#getAsynchronousWorkersMax()}, + * {@link CacheConfig#getAsynchronousWorkersCore()}, + * {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()}, + * and {@link CacheConfig#getRevalidationQueueSize()}. + */ + public AsynchronousValidator(final CacheConfig config) { + this(new ImmediateSchedulingStrategy(config)); + } + + /** + * Create AsynchronousValidator which will make revalidation requests + * using the supplied {@link SchedulingStrategy}. Closing the validator + * will also close the given schedulingStrategy. + * @param schedulingStrategy used to maintain a pool of worker threads and + * schedules when requests are executed + */ + AsynchronousValidator(final SchedulingStrategy schedulingStrategy) { + this.schedulingStrategy = schedulingStrategy; + this.queued = new HashSet<String>(); + this.cacheKeyGenerator = new CacheKeyGenerator(); + this.failureCache = new DefaultFailureCache(); + } + + public void close() throws IOException { + schedulingStrategy.close(); + } + + /** + * Schedules an asynchronous revalidation + */ + public synchronized void revalidateCacheEntry( + final CachingExec cachingExec, + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry entry) { + // getVariantURI will fall back on getURI if no variants exist + final String uri = cacheKeyGenerator.getVariantURI(context.getTargetHost(), request, entry); + + if (!queued.contains(uri)) { + final int consecutiveFailedAttempts = failureCache.getErrorCount(uri); + final AsynchronousValidationRequest revalidationRequest = + new AsynchronousValidationRequest( + this, cachingExec, route, request, context, execAware, entry, uri, consecutiveFailedAttempts); + + try { + schedulingStrategy.schedule(revalidationRequest); + queued.add(uri); + } catch (final RejectedExecutionException ree) { + log.debug("Revalidation for [" + uri + "] not scheduled: " + ree); + } + } + } + + /** + * Removes an identifier from the internal list of revalidation jobs in + * progress. This is meant to be called by + * {@link AsynchronousValidationRequest#run()} once the revalidation is + * complete, using the identifier passed in during constructions. + * @param identifier + */ + synchronized void markComplete(final String identifier) { + queued.remove(identifier); + } + + /** + * The revalidation job was successful thus the number of consecutive + * failed attempts will be reset to zero. Should be called by + * {@link AsynchronousValidationRequest#run()}. + * @param identifier the revalidation job's unique identifier + */ + void jobSuccessful(final String identifier) { + failureCache.resetErrorCount(identifier); + } + + /** + * The revalidation job did fail and thus the number of consecutive failed + * attempts will be increased. Should be called by + * {@link AsynchronousValidationRequest#run()}. + * @param identifier the revalidation job's unique identifier + */ + void jobFailed(final String identifier) { + failureCache.increaseErrorCount(identifier); + } + + Set<String> getScheduledIdentifiers() { + return Collections.unmodifiableSet(queued); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java new file mode 100644 index 000000000..4347e173d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java @@ -0,0 +1,376 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateException; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.entity.ByteArrayEntity; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +class BasicHttpCache implements HttpCache { + private static final Set<String> safeRequestMethods = new HashSet<String>( + Arrays.asList(HeaderConstants.HEAD_METHOD, + HeaderConstants.GET_METHOD, HeaderConstants.OPTIONS_METHOD, + HeaderConstants.TRACE_METHOD)); + + private final CacheKeyGenerator uriExtractor; + private final ResourceFactory resourceFactory; + private final long maxObjectSizeBytes; + private final CacheEntryUpdater cacheEntryUpdater; + private final CachedHttpResponseGenerator responseGenerator; + private final HttpCacheInvalidator cacheInvalidator; + private final HttpCacheStorage storage; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + public BasicHttpCache( + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config, + final CacheKeyGenerator uriExtractor, + final HttpCacheInvalidator cacheInvalidator) { + this.resourceFactory = resourceFactory; + this.uriExtractor = uriExtractor; + this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory); + this.maxObjectSizeBytes = config.getMaxObjectSize(); + this.responseGenerator = new CachedHttpResponseGenerator(); + this.storage = storage; + this.cacheInvalidator = cacheInvalidator; + } + + public BasicHttpCache( + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config, + final CacheKeyGenerator uriExtractor) { + this( resourceFactory, storage, config, uriExtractor, + new CacheInvalidator(uriExtractor, storage)); + } + + public BasicHttpCache( + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config) { + this( resourceFactory, storage, config, new CacheKeyGenerator()); + } + + public BasicHttpCache(final CacheConfig config) { + this(new HeapResourceFactory(), new BasicHttpCacheStorage(config), config); + } + + public BasicHttpCache() { + this(CacheConfig.DEFAULT); + } + + public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) + throws IOException { + if (!safeRequestMethods.contains(request.getRequestLine().getMethod())) { + final String uri = uriExtractor.getURI(host, request); + storage.removeEntry(uri); + } + } + + public void flushInvalidatedCacheEntriesFor(final HttpHost host, final HttpRequest request, final HttpResponse response) { + if (!safeRequestMethods.contains(request.getRequestLine().getMethod())) { + cacheInvalidator.flushInvalidatedCacheEntries(host, request, response); + } + } + + void storeInCache( + final HttpHost target, final HttpRequest request, final HttpCacheEntry entry) throws IOException { + if (entry.hasVariants()) { + storeVariantEntry(target, request, entry); + } else { + storeNonVariantEntry(target, request, entry); + } + } + + void storeNonVariantEntry( + final HttpHost target, final HttpRequest req, final HttpCacheEntry entry) throws IOException { + final String uri = uriExtractor.getURI(target, req); + storage.putEntry(uri, entry); + } + + void storeVariantEntry( + final HttpHost target, + final HttpRequest req, + final HttpCacheEntry entry) throws IOException { + final String parentURI = uriExtractor.getURI(target, req); + final String variantURI = uriExtractor.getVariantURI(target, req, entry); + storage.putEntry(variantURI, entry); + + final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { + + public HttpCacheEntry update(final HttpCacheEntry existing) throws IOException { + return doGetUpdatedParentEntry( + req.getRequestLine().getUri(), existing, entry, + uriExtractor.getVariantKey(req, entry), + variantURI); + } + + }; + + try { + storage.updateEntry(parentURI, callback); + } catch (final HttpCacheUpdateException e) { + log.warn("Could not update key [" + parentURI + "]", e); + } + } + + public void reuseVariantEntryFor(final HttpHost target, final HttpRequest req, + final Variant variant) throws IOException { + final String parentCacheKey = uriExtractor.getURI(target, req); + final HttpCacheEntry entry = variant.getEntry(); + final String variantKey = uriExtractor.getVariantKey(req, entry); + final String variantCacheKey = variant.getCacheKey(); + + final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { + public HttpCacheEntry update(final HttpCacheEntry existing) + throws IOException { + return doGetUpdatedParentEntry(req.getRequestLine().getUri(), + existing, entry, variantKey, variantCacheKey); + } + }; + + try { + storage.updateEntry(parentCacheKey, callback); + } catch (final HttpCacheUpdateException e) { + log.warn("Could not update key [" + parentCacheKey + "]", e); + } + } + + boolean isIncompleteResponse(final HttpResponse resp, final Resource resource) { + final int status = resp.getStatusLine().getStatusCode(); + if (status != HttpStatus.SC_OK + && status != HttpStatus.SC_PARTIAL_CONTENT) { + return false; + } + final Header hdr = resp.getFirstHeader(HTTP.CONTENT_LEN); + if (hdr == null) { + return false; + } + final int contentLength; + try { + contentLength = Integer.parseInt(hdr.getValue()); + } catch (final NumberFormatException nfe) { + return false; + } + return (resource.length() < contentLength); + } + + CloseableHttpResponse generateIncompleteResponseError( + final HttpResponse response, final Resource resource) { + final int contentLength = Integer.parseInt(response.getFirstHeader(HTTP.CONTENT_LEN).getValue()); + final HttpResponse error = + new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); + error.setHeader("Content-Type","text/plain;charset=UTF-8"); + final String msg = String.format("Received incomplete response " + + "with Content-Length %d but actual body length %d", + contentLength, resource.length()); + final byte[] msgBytes = msg.getBytes(); + error.setHeader("Content-Length", Integer.toString(msgBytes.length)); + error.setEntity(new ByteArrayEntity(msgBytes)); + return Proxies.enhanceResponse(error); + } + + HttpCacheEntry doGetUpdatedParentEntry( + final String requestId, + final HttpCacheEntry existing, + final HttpCacheEntry entry, + final String variantKey, + final String variantCacheKey) throws IOException { + HttpCacheEntry src = existing; + if (src == null) { + src = entry; + } + + Resource resource = null; + if (src.getResource() != null) { + resource = resourceFactory.copy(requestId, src.getResource()); + } + final Map<String,String> variantMap = new HashMap<String,String>(src.getVariantMap()); + variantMap.put(variantKey, variantCacheKey); + return new HttpCacheEntry( + src.getRequestDate(), + src.getResponseDate(), + src.getStatusLine(), + src.getAllHeaders(), + resource, + variantMap); + } + + public HttpCacheEntry updateCacheEntry(final HttpHost target, final HttpRequest request, + final HttpCacheEntry stale, final HttpResponse originResponse, + final Date requestSent, final Date responseReceived) throws IOException { + final HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry( + request.getRequestLine().getUri(), + stale, + requestSent, + responseReceived, + originResponse); + storeInCache(target, request, updatedEntry); + return updatedEntry; + } + + public HttpCacheEntry updateVariantCacheEntry(final HttpHost target, final HttpRequest request, + final HttpCacheEntry stale, final HttpResponse originResponse, + final Date requestSent, final Date responseReceived, final String cacheKey) throws IOException { + final HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry( + request.getRequestLine().getUri(), + stale, + requestSent, + responseReceived, + originResponse); + storage.putEntry(cacheKey, updatedEntry); + return updatedEntry; + } + + public HttpResponse cacheAndReturnResponse(final HttpHost host, final HttpRequest request, + final HttpResponse originResponse, final Date requestSent, final Date responseReceived) + throws IOException { + return cacheAndReturnResponse(host, request, + Proxies.enhanceResponse(originResponse), requestSent, + responseReceived); + } + + public CloseableHttpResponse cacheAndReturnResponse( + final HttpHost host, + final HttpRequest request, + final CloseableHttpResponse originResponse, + final Date requestSent, + final Date responseReceived) throws IOException { + + boolean closeOriginResponse = true; + final SizeLimitedResponseReader responseReader = getResponseReader(request, originResponse); + try { + responseReader.readResponse(); + + if (responseReader.isLimitReached()) { + closeOriginResponse = false; + return responseReader.getReconstructedResponse(); + } + + final Resource resource = responseReader.getResource(); + if (isIncompleteResponse(originResponse, resource)) { + return generateIncompleteResponseError(originResponse, resource); + } + + final HttpCacheEntry entry = new HttpCacheEntry( + requestSent, + responseReceived, + originResponse.getStatusLine(), + originResponse.getAllHeaders(), + resource); + storeInCache(host, request, entry); + return responseGenerator.generateResponse(entry); + } finally { + if (closeOriginResponse) { + originResponse.close(); + } + } + } + + SizeLimitedResponseReader getResponseReader(final HttpRequest request, + final CloseableHttpResponse backEndResponse) { + return new SizeLimitedResponseReader( + resourceFactory, maxObjectSizeBytes, request, backEndResponse); + } + + public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) throws IOException { + final HttpCacheEntry root = storage.getEntry(uriExtractor.getURI(host, request)); + if (root == null) { + return null; + } + if (!root.hasVariants()) { + return root; + } + final String variantCacheKey = root.getVariantMap().get(uriExtractor.getVariantKey(request, root)); + if (variantCacheKey == null) { + return null; + } + return storage.getEntry(variantCacheKey); + } + + public void flushInvalidatedCacheEntriesFor(final HttpHost host, + final HttpRequest request) throws IOException { + cacheInvalidator.flushInvalidatedCacheEntries(host, request); + } + + public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request) + throws IOException { + final Map<String,Variant> variants = new HashMap<String,Variant>(); + final HttpCacheEntry root = storage.getEntry(uriExtractor.getURI(host, request)); + if (root == null || !root.hasVariants()) { + return variants; + } + for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) { + final String variantKey = variant.getKey(); + final String variantCacheKey = variant.getValue(); + addVariantWithEtag(variantKey, variantCacheKey, variants); + } + return variants; + } + + private void addVariantWithEtag(final String variantKey, + final String variantCacheKey, final Map<String, Variant> variants) + throws IOException { + final HttpCacheEntry entry = storage.getEntry(variantCacheKey); + if (entry == null) { + return; + } + final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG); + if (etagHeader == null) { + return; + } + variants.put(etagHeader.getValue(), new Variant(variantKey, variantCacheKey, entry)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java new file mode 100644 index 000000000..225730858 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java @@ -0,0 +1,96 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback; + +/** + * Basic {@link HttpCacheStorage} implementation backed by an instance of + * {@link java.util.LinkedHashMap}. In other words, cache entries and + * the cached response bodies are held in-memory. This cache does NOT + * deallocate resources associated with the cache entries; it is intended + * for use with {@link HeapResource} and similar. This is the default cache + * storage backend used by {@link CachingHttpClients}. + * + * @since 4.1 + */ +@ThreadSafe +public class BasicHttpCacheStorage implements HttpCacheStorage { + + private final CacheMap entries; + + public BasicHttpCacheStorage(final CacheConfig config) { + super(); + this.entries = new CacheMap(config.getMaxCacheEntries()); + } + + /** + * Places a HttpCacheEntry in the cache + * + * @param url + * Url to use as the cache key + * @param entry + * HttpCacheEntry to place in the cache + */ + public synchronized void putEntry(final String url, final HttpCacheEntry entry) throws IOException { + entries.put(url, entry); + } + + /** + * Gets an entry from the cache, if it exists + * + * @param url + * Url that is the cache key + * @return HttpCacheEntry if one exists, or null for cache miss + */ + public synchronized HttpCacheEntry getEntry(final String url) throws IOException { + return entries.get(url); + } + + /** + * Removes a HttpCacheEntry from the cache + * + * @param url + * Url that is the cache key + */ + public synchronized void removeEntry(final String url) throws IOException { + entries.remove(url); + } + + public synchronized void updateEntry( + final String url, + final HttpCacheUpdateCallback callback) throws IOException { + final HttpCacheEntry existingEntry = entries.get(url); + entries.put(url, callback.update(existingEntry)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java new file mode 100644 index 000000000..defdc3999 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java @@ -0,0 +1,86 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Formatter; +import java.util.Locale; + +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +/** + * Should produce reasonably unique tokens. + */ +@ThreadSafe +class BasicIdGenerator { + + private final String hostname; + private final SecureRandom rnd; + + @GuardedBy("this") + private long count; + + public BasicIdGenerator() { + super(); + String hostname; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (final UnknownHostException ex) { + hostname = "localhost"; + } + this.hostname = hostname; + try { + this.rnd = SecureRandom.getInstance("SHA1PRNG"); + } catch (final NoSuchAlgorithmException ex) { + throw new Error(ex); + } + this.rnd.setSeed(System.currentTimeMillis()); + } + + public synchronized void generate(final StringBuilder buffer) { + this.count++; + final int rndnum = this.rnd.nextInt(); + buffer.append(System.currentTimeMillis()); + buffer.append('.'); + final Formatter formatter = new Formatter(buffer, Locale.US); + formatter.format("%1$016x-%2$08x", this.count, rndnum); + formatter.close(); + buffer.append('.'); + buffer.append(this.hostname); + } + + public String generate() { + final StringBuilder buffer = new StringBuilder(); + generate(buffer); + return buffer.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java new file mode 100644 index 000000000..964ea2719 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java @@ -0,0 +1,764 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <p>Java Beans-style configuration for a {@link CachingHttpClient}. Any class + * in the caching module that has configuration options should take a + * {@link CacheConfig} argument in one of its constructors. A + * {@code CacheConfig} instance has sane and conservative defaults, so the + * easiest way to specify options is to get an instance and then set just + * the options you want to modify from their defaults.</p> + * + * <p><b>N.B.</b> This class is only for caching-specific configuration; to + * configure the behavior of the rest of the client, configure the + * {@link ch.boye.httpclientandroidlib.client.HttpClient} used as the "backend" + * for the {@code CachingHttpClient}.</p> + * + * <p>Cache configuration can be grouped into the following categories:</p> + * + * <p><b>Cache size.</b> If the backend storage supports these limits, you + * can specify the {@link CacheConfig#getMaxCacheEntries maximum number of + * cache entries} as well as the {@link CacheConfig#getMaxObjectSizeBytes + * maximum cacheable response body size}.</p> + * + * <p><b>Public/private caching.</b> By default, the caching module considers + * itself to be a shared (public) cache, and will not, for example, cache + * responses to requests with {@code Authorization} headers or responses + * marked with {@code Cache-Control: private}. If, however, the cache + * is only going to be used by one logical "user" (behaving similarly to a + * browser cache), then you will want to {@link + * CacheConfig#setSharedCache(boolean) turn off the shared cache setting}.</p> + * + * <p><b>303 caching</b>. RFC2616 explicitly disallows caching 303 responses; + * however, the HTTPbis working group says they can be cached + * if explicitly indicated in the response headers and permitted by the request method. + * (They also indicate that disallowing 303 caching is actually an unintended + * spec error in RFC2616). + * This behavior is off by default, to err on the side of a conservative + * adherence to the existing standard, but you may want to + * {@link Builder#setAllow303Caching(boolean) enable it}. + * + * <p><b>Weak ETags on PUT/DELETE If-Match requests</b>. RFC2616 explicitly + * prohibits the use of weak validators in non-GET requests, however, the + * HTTPbis working group says while the limitation for weak validators on ranged + * requests makes sense, weak ETag validation is useful on full non-GET + * requests; e.g., PUT with If-Match. This behavior is off by default, to err on + * the side of a conservative adherence to the existing standard, but you may + * want to {@link Builder#setWeakETagOnPutDeleteAllowed(boolean) enable it}. + * + * <p><b>Heuristic caching</b>. Per RFC2616, a cache may cache certain cache + * entries even if no explicit cache control headers are set by the origin. + * This behavior is off by default, but you may want to turn this on if you + * are working with an origin that doesn't set proper headers but where you + * still want to cache the responses. You will want to {@link + * CacheConfig#setHeuristicCachingEnabled(boolean) enable heuristic caching}, + * then specify either a {@link CacheConfig#getHeuristicDefaultLifetime() + * default freshness lifetime} and/or a {@link + * CacheConfig#setHeuristicCoefficient(float) fraction of the time since + * the resource was last modified}. See Sections + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.2"> + * 13.2.2</a> and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4"> + * 13.2.4</a> of the HTTP/1.1 RFC for more details on heuristic caching.</p> + * + * <p><b>Background validation</b>. The cache module supports the + * {@code stale-while-revalidate} directive of + * <a href="http://tools.ietf.org/html/rfc5861">RFC5861</a>, which allows + * certain cache entry revalidations to happen in the background. You may + * want to tweak the settings for the {@link + * CacheConfig#getAsynchronousWorkersCore() minimum} and {@link + * CacheConfig#getAsynchronousWorkersMax() maximum} number of background + * worker threads, as well as the {@link + * CacheConfig#getAsynchronousWorkerIdleLifetimeSecs() maximum time they + * can be idle before being reclaimed}. You can also control the {@link + * CacheConfig#getRevalidationQueueSize() size of the queue} used for + * revalidations when there aren't enough workers to keep up with demand.</b> + */ +public class CacheConfig implements Cloneable { + + /** Default setting for the maximum object size that will be + * cached, in bytes. + */ + public final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192; + + /** Default setting for the maximum number of cache entries + * that will be retained. + */ + public final static int DEFAULT_MAX_CACHE_ENTRIES = 1000; + + /** Default setting for the number of retries on a failed + * cache update + */ + public final static int DEFAULT_MAX_UPDATE_RETRIES = 1; + + /** Default setting for 303 caching + */ + public final static boolean DEFAULT_303_CACHING_ENABLED = false; + + /** Default setting to allow weak tags on PUT/DELETE methods + */ + public final static boolean DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED = false; + + /** Default setting for heuristic caching + */ + public final static boolean DEFAULT_HEURISTIC_CACHING_ENABLED = false; + + /** Default coefficient used to heuristically determine freshness + * lifetime from the Last-Modified time of a cache entry. + */ + public final static float DEFAULT_HEURISTIC_COEFFICIENT = 0.1f; + + /** Default lifetime in seconds to be assumed when we cannot calculate + * freshness heuristically. + */ + public final static long DEFAULT_HEURISTIC_LIFETIME = 0; + + /** Default number of worker threads to allow for background revalidations + * resulting from the stale-while-revalidate directive. + */ + public static final int DEFAULT_ASYNCHRONOUS_WORKERS_MAX = 1; + + /** Default minimum number of worker threads to allow for background + * revalidations resulting from the stale-while-revalidate directive. + */ + public static final int DEFAULT_ASYNCHRONOUS_WORKERS_CORE = 1; + + /** Default maximum idle lifetime for a background revalidation thread + * before it gets reclaimed. + */ + public static final int DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS = 60; + + /** Default maximum queue length for background revalidation requests. + */ + public static final int DEFAULT_REVALIDATION_QUEUE_SIZE = 100; + + public static final CacheConfig DEFAULT = new Builder().build(); + + // TODO: make final + private long maxObjectSize; + private int maxCacheEntries; + private int maxUpdateRetries; + private boolean allow303Caching; + private boolean weakETagOnPutDeleteAllowed; + private boolean heuristicCachingEnabled; + private float heuristicCoefficient; + private long heuristicDefaultLifetime; + private boolean isSharedCache; + private int asynchronousWorkersMax; + private int asynchronousWorkersCore; + private int asynchronousWorkerIdleLifetimeSecs; + private int revalidationQueueSize; + private boolean neverCacheHTTP10ResponsesWithQuery; + + /** + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public CacheConfig() { + super(); + this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES; + this.maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES; + this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES; + this.allow303Caching = DEFAULT_303_CACHING_ENABLED; + this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED; + this.heuristicCachingEnabled = DEFAULT_HEURISTIC_CACHING_ENABLED; + this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT; + this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME; + this.isSharedCache = true; + this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX; + this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE; + this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS; + this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE; + } + + CacheConfig( + final long maxObjectSize, + final int maxCacheEntries, + final int maxUpdateRetries, + final boolean allow303Caching, + final boolean weakETagOnPutDeleteAllowed, + final boolean heuristicCachingEnabled, + final float heuristicCoefficient, + final long heuristicDefaultLifetime, + final boolean isSharedCache, + final int asynchronousWorkersMax, + final int asynchronousWorkersCore, + final int asynchronousWorkerIdleLifetimeSecs, + final int revalidationQueueSize, + final boolean neverCacheHTTP10ResponsesWithQuery) { + super(); + this.maxObjectSize = maxObjectSize; + this.maxCacheEntries = maxCacheEntries; + this.maxUpdateRetries = maxUpdateRetries; + this.allow303Caching = allow303Caching; + this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed; + this.heuristicCachingEnabled = heuristicCachingEnabled; + this.heuristicCoefficient = heuristicCoefficient; + this.heuristicDefaultLifetime = heuristicDefaultLifetime; + this.isSharedCache = isSharedCache; + this.asynchronousWorkersMax = asynchronousWorkersMax; + this.asynchronousWorkersCore = asynchronousWorkersCore; + this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs; + this.revalidationQueueSize = revalidationQueueSize; + } + + /** + * Returns the current maximum response body size that will be cached. + * @return size in bytes + * + * @deprecated (4.2) use {@link #getMaxObjectSize()} + */ + @Deprecated + public int getMaxObjectSizeBytes() { + return maxObjectSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxObjectSize; + } + + /** + * Specifies the maximum response body size that will be eligible for caching. + * @param maxObjectSizeBytes size in bytes + * + * @deprecated (4.2) use {@link Builder}. + */ + @Deprecated + public void setMaxObjectSizeBytes(final int maxObjectSizeBytes) { + if (maxObjectSizeBytes > Integer.MAX_VALUE) { + this.maxObjectSize = Integer.MAX_VALUE; + } else { + this.maxObjectSize = maxObjectSizeBytes; + } + } + + /** + * Returns the current maximum response body size that will be cached. + * @return size in bytes + * + * @since 4.2 + */ + public long getMaxObjectSize() { + return maxObjectSize; + } + + /** + * Specifies the maximum response body size that will be eligible for caching. + * @param maxObjectSize size in bytes + * + * @since 4.2 + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setMaxObjectSize(final long maxObjectSize) { + this.maxObjectSize = maxObjectSize; + } + + /** + * Returns whether the cache will never cache HTTP 1.0 responses with a query string or not. + * @return {@code true} to not cache query string responses, {@code false} to cache if explicit cache headers are + * found + */ + public boolean isNeverCacheHTTP10ResponsesWithQuery() { + return neverCacheHTTP10ResponsesWithQuery; + } + + /** + * Returns the maximum number of cache entries the cache will retain. + */ + public int getMaxCacheEntries() { + return maxCacheEntries; + } + + /** + * Sets the maximum number of cache entries the cache will retain. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setMaxCacheEntries(final int maxCacheEntries) { + this.maxCacheEntries = maxCacheEntries; + } + + /** + * Returns the number of times to retry a cache update on failure + */ + public int getMaxUpdateRetries(){ + return maxUpdateRetries; + } + + /** + * Sets the number of times to retry a cache update on failure + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setMaxUpdateRetries(final int maxUpdateRetries){ + this.maxUpdateRetries = maxUpdateRetries; + } + + /** + * Returns whether 303 caching is enabled. + * @return {@code true} if it is enabled. + */ + public boolean is303CachingEnabled() { + return allow303Caching; + } + + /** + * Returns whether weak etags is allowed with PUT/DELETE methods. + * @return {@code true} if it is allowed. + */ + public boolean isWeakETagOnPutDeleteAllowed() { + return weakETagOnPutDeleteAllowed; + } + + /** + * Returns whether heuristic caching is enabled. + * @return {@code true} if it is enabled. + */ + public boolean isHeuristicCachingEnabled() { + return heuristicCachingEnabled; + } + + /** + * Enables or disables heuristic caching. + * @param heuristicCachingEnabled should be {@code true} to + * permit heuristic caching, {@code false} to disable it. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setHeuristicCachingEnabled(final boolean heuristicCachingEnabled) { + this.heuristicCachingEnabled = heuristicCachingEnabled; + } + + /** + * Returns lifetime coefficient used in heuristic freshness caching. + */ + public float getHeuristicCoefficient() { + return heuristicCoefficient; + } + + /** + * Sets coefficient to be used in heuristic freshness caching. This is + * interpreted as the fraction of the time between the {@code Last-Modified} + * and {@code Date} headers of a cached response during which the cached + * response will be considered heuristically fresh. + * @param heuristicCoefficient should be between {@code 0.0} and + * {@code 1.0}. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setHeuristicCoefficient(final float heuristicCoefficient) { + this.heuristicCoefficient = heuristicCoefficient; + } + + /** + * Get the default lifetime to be used if heuristic freshness calculation is + * not possible. + */ + public long getHeuristicDefaultLifetime() { + return heuristicDefaultLifetime; + } + + /** + * Sets default lifetime in seconds to be used if heuristic freshness + * calculation is not possible. Explicit cache control directives on + * either the request or origin response will override this, as will + * the heuristic {@code Last-Modified} freshness calculation if it is + * available. + * @param heuristicDefaultLifetimeSecs is the number of seconds to + * consider a cache-eligible response fresh in the absence of other + * information. Set this to {@code 0} to disable this style of + * heuristic caching. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setHeuristicDefaultLifetime(final long heuristicDefaultLifetimeSecs) { + this.heuristicDefaultLifetime = heuristicDefaultLifetimeSecs; + } + + /** + * Returns whether the cache will behave as a shared cache or not. + * @return {@code true} for a shared cache, {@code false} for a non- + * shared (private) cache + */ + public boolean isSharedCache() { + return isSharedCache; + } + + /** + * Sets whether the cache should behave as a shared cache or not. + * @param isSharedCache true to behave as a shared cache, false to + * behave as a non-shared (private) cache. To have the cache + * behave like a browser cache, you want to set this to {@code false}. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setSharedCache(final boolean isSharedCache) { + this.isSharedCache = isSharedCache; + } + + /** + * Returns the maximum number of threads to allow for background + * revalidations due to the {@code stale-while-revalidate} directive. A + * value of 0 means background revalidations are disabled. + */ + public int getAsynchronousWorkersMax() { + return asynchronousWorkersMax; + } + + /** + * Sets the maximum number of threads to allow for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param max number of threads; a value of 0 disables background + * revalidations. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setAsynchronousWorkersMax(final int max) { + this.asynchronousWorkersMax = max; + } + + /** + * Returns the minimum number of threads to keep alive for background + * revalidations due to the {@code stale-while-revalidate} directive. + */ + public int getAsynchronousWorkersCore() { + return asynchronousWorkersCore; + } + + /** + * Sets the minimum number of threads to keep alive for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param min should be greater than zero and less than or equal + * to <code>getAsynchronousWorkersMax()</code> + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setAsynchronousWorkersCore(final int min) { + this.asynchronousWorkersCore = min; + } + + /** + * Returns the current maximum idle lifetime in seconds for a + * background revalidation worker thread. If a worker thread is idle + * for this long, and there are more than the core number of worker + * threads alive, the worker will be reclaimed. + */ + public int getAsynchronousWorkerIdleLifetimeSecs() { + return asynchronousWorkerIdleLifetimeSecs; + } + + /** + * Sets the current maximum idle lifetime in seconds for a + * background revalidation worker thread. If a worker thread is idle + * for this long, and there are more than the core number of worker + * threads alive, the worker will be reclaimed. + * @param secs idle lifetime in seconds + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setAsynchronousWorkerIdleLifetimeSecs(final int secs) { + this.asynchronousWorkerIdleLifetimeSecs = secs; + } + + /** + * Returns the current maximum queue size for background revalidations. + */ + public int getRevalidationQueueSize() { + return revalidationQueueSize; + } + + /** + * Sets the current maximum queue size for background revalidations. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setRevalidationQueueSize(final int size) { + this.revalidationQueueSize = size; + } + + @Override + protected CacheConfig clone() throws CloneNotSupportedException { + return (CacheConfig) super.clone(); + } + + public static Builder custom() { + return new Builder(); + } + + public static Builder copy(final CacheConfig config) { + Args.notNull(config, "Cache config"); + return new Builder() + .setMaxObjectSize(config.getMaxObjectSize()) + .setMaxCacheEntries(config.getMaxCacheEntries()) + .setMaxUpdateRetries(config.getMaxUpdateRetries()) + .setHeuristicCachingEnabled(config.isHeuristicCachingEnabled()) + .setHeuristicCoefficient(config.getHeuristicCoefficient()) + .setHeuristicDefaultLifetime(config.getHeuristicDefaultLifetime()) + .setSharedCache(config.isSharedCache()) + .setAsynchronousWorkersMax(config.getAsynchronousWorkersMax()) + .setAsynchronousWorkersCore(config.getAsynchronousWorkersCore()) + .setAsynchronousWorkerIdleLifetimeSecs(config.getAsynchronousWorkerIdleLifetimeSecs()) + .setRevalidationQueueSize(config.getRevalidationQueueSize()) + .setNeverCacheHTTP10ResponsesWithQueryString(config.isNeverCacheHTTP10ResponsesWithQuery()); + } + + + public static class Builder { + + private long maxObjectSize; + private int maxCacheEntries; + private int maxUpdateRetries; + private boolean allow303Caching; + private boolean weakETagOnPutDeleteAllowed; + private boolean heuristicCachingEnabled; + private float heuristicCoefficient; + private long heuristicDefaultLifetime; + private boolean isSharedCache; + private int asynchronousWorkersMax; + private int asynchronousWorkersCore; + private int asynchronousWorkerIdleLifetimeSecs; + private int revalidationQueueSize; + private boolean neverCacheHTTP10ResponsesWithQuery; + + Builder() { + this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES; + this.maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES; + this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES; + this.allow303Caching = DEFAULT_303_CACHING_ENABLED; + this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED; + this.heuristicCachingEnabled = false; + this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT; + this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME; + this.isSharedCache = true; + this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX; + this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE; + this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS; + this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE; + } + + /** + * Specifies the maximum response body size that will be eligible for caching. + * @param maxObjectSize size in bytes + */ + public Builder setMaxObjectSize(final long maxObjectSize) { + this.maxObjectSize = maxObjectSize; + return this; + } + + /** + * Sets the maximum number of cache entries the cache will retain. + */ + public Builder setMaxCacheEntries(final int maxCacheEntries) { + this.maxCacheEntries = maxCacheEntries; + return this; + } + + /** + * Sets the number of times to retry a cache update on failure + */ + public Builder setMaxUpdateRetries(final int maxUpdateRetries) { + this.maxUpdateRetries = maxUpdateRetries; + return this; + } + + /** + * Enables or disables 303 caching. + * @param allow303Caching should be {@code true} to + * permit 303 caching, {@code false} to disable it. + */ + public Builder setAllow303Caching(final boolean allow303Caching) { + this.allow303Caching = allow303Caching; + return this; + } + + /** + * Allows or disallows weak etags to be used with PUT/DELETE If-Match requests. + * @param weakETagOnPutDeleteAllowed should be {@code true} to + * permit weak etags, {@code false} to reject them. + */ + public Builder setWeakETagOnPutDeleteAllowed(final boolean weakETagOnPutDeleteAllowed) { + this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed; + return this; + } + + /** + * Enables or disables heuristic caching. + * @param heuristicCachingEnabled should be {@code true} to + * permit heuristic caching, {@code false} to enable it. + */ + public Builder setHeuristicCachingEnabled(final boolean heuristicCachingEnabled) { + this.heuristicCachingEnabled = heuristicCachingEnabled; + return this; + } + + /** + * Sets coefficient to be used in heuristic freshness caching. This is + * interpreted as the fraction of the time between the {@code Last-Modified} + * and {@code Date} headers of a cached response during which the cached + * response will be considered heuristically fresh. + * @param heuristicCoefficient should be between {@code 0.0} and + * {@code 1.0}. + */ + public Builder setHeuristicCoefficient(final float heuristicCoefficient) { + this.heuristicCoefficient = heuristicCoefficient; + return this; + } + + /** + * Sets default lifetime in seconds to be used if heuristic freshness + * calculation is not possible. Explicit cache control directives on + * either the request or origin response will override this, as will + * the heuristic {@code Last-Modified} freshness calculation if it is + * available. + * @param heuristicDefaultLifetime is the number of seconds to + * consider a cache-eligible response fresh in the absence of other + * information. Set this to {@code 0} to disable this style of + * heuristic caching. + */ + public Builder setHeuristicDefaultLifetime(final long heuristicDefaultLifetime) { + this.heuristicDefaultLifetime = heuristicDefaultLifetime; + return this; + } + + /** + * Sets whether the cache should behave as a shared cache or not. + * @param isSharedCache true to behave as a shared cache, false to + * behave as a non-shared (private) cache. To have the cache + * behave like a browser cache, you want to set this to {@code false}. + */ + public Builder setSharedCache(final boolean isSharedCache) { + this.isSharedCache = isSharedCache; + return this; + } + + /** + * Sets the maximum number of threads to allow for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param asynchronousWorkersMax number of threads; a value of 0 disables background + * revalidations. + */ + public Builder setAsynchronousWorkersMax(final int asynchronousWorkersMax) { + this.asynchronousWorkersMax = asynchronousWorkersMax; + return this; + } + + /** + * Sets the minimum number of threads to keep alive for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param asynchronousWorkersCore should be greater than zero and less than or equal + * to <code>getAsynchronousWorkersMax()</code> + */ + public Builder setAsynchronousWorkersCore(final int asynchronousWorkersCore) { + this.asynchronousWorkersCore = asynchronousWorkersCore; + return this; + } + + /** + * Sets the current maximum idle lifetime in seconds for a + * background revalidation worker thread. If a worker thread is idle + * for this long, and there are more than the core number of worker + * threads alive, the worker will be reclaimed. + * @param asynchronousWorkerIdleLifetimeSecs idle lifetime in seconds + */ + public Builder setAsynchronousWorkerIdleLifetimeSecs(final int asynchronousWorkerIdleLifetimeSecs) { + this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs; + return this; + } + + /** + * Sets the current maximum queue size for background revalidations. + */ + public Builder setRevalidationQueueSize(final int revalidationQueueSize) { + this.revalidationQueueSize = revalidationQueueSize; + return this; + } + + /** + * Sets whether the cache should never cache HTTP 1.0 responses with a query string or not. + * @param neverCacheHTTP10ResponsesWithQuery true to never cache responses with a query + * string, false to cache if explicit cache headers are found. Set this to {@code true} + * to better emulate IE, which also never caches responses, regardless of what caching + * headers may be present. + */ + public Builder setNeverCacheHTTP10ResponsesWithQueryString( + final boolean neverCacheHTTP10ResponsesWithQuery) { + this.neverCacheHTTP10ResponsesWithQuery = neverCacheHTTP10ResponsesWithQuery; + return this; + } + + public CacheConfig build() { + return new CacheConfig( + maxObjectSize, + maxCacheEntries, + maxUpdateRetries, + allow303Caching, + weakETagOnPutDeleteAllowed, + heuristicCachingEnabled, + heuristicCoefficient, + heuristicDefaultLifetime, + isSharedCache, + asynchronousWorkersMax, + asynchronousWorkersCore, + asynchronousWorkerIdleLifetimeSecs, + revalidationQueueSize, + neverCacheHTTP10ResponsesWithQuery); + } + + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("[maxObjectSize=").append(this.maxObjectSize) + .append(", maxCacheEntries=").append(this.maxCacheEntries) + .append(", maxUpdateRetries=").append(this.maxUpdateRetries) + .append(", 303CachingEnabled=").append(this.allow303Caching) + .append(", weakETagOnPutDeleteAllowed=").append(this.weakETagOnPutDeleteAllowed) + .append(", heuristicCachingEnabled=").append(this.heuristicCachingEnabled) + .append(", heuristicCoefficient=").append(this.heuristicCoefficient) + .append(", heuristicDefaultLifetime=").append(this.heuristicDefaultLifetime) + .append(", isSharedCache=").append(this.isSharedCache) + .append(", asynchronousWorkersMax=").append(this.asynchronousWorkersMax) + .append(", asynchronousWorkersCore=").append(this.asynchronousWorkersCore) + .append(", asynchronousWorkerIdleLifetimeSecs=").append(this.asynchronousWorkerIdleLifetimeSecs) + .append(", revalidationQueueSize=").append(this.revalidationQueueSize) + .append(", neverCacheHTTP10ResponsesWithQuery=").append(this.neverCacheHTTP10ResponsesWithQuery) + .append("]"); + return builder.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java new file mode 100644 index 000000000..1906166d1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java @@ -0,0 +1,99 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; + +@Immutable +class CacheEntity implements HttpEntity, Serializable { + + private static final long serialVersionUID = -3467082284120936233L; + + private final HttpCacheEntry cacheEntry; + + public CacheEntity(final HttpCacheEntry cacheEntry) { + super(); + this.cacheEntry = cacheEntry; + } + + public Header getContentType() { + return this.cacheEntry.getFirstHeader(HTTP.CONTENT_TYPE); + } + + public Header getContentEncoding() { + return this.cacheEntry.getFirstHeader(HTTP.CONTENT_ENCODING); + } + + public boolean isChunked() { + return false; + } + + public boolean isRepeatable() { + return true; + } + + public long getContentLength() { + return this.cacheEntry.getResource().length(); + } + + public InputStream getContent() throws IOException { + return this.cacheEntry.getResource().getInputStream(); + } + + public void writeTo(final OutputStream outstream) throws IOException { + Args.notNull(outstream, "Output stream"); + final InputStream instream = this.cacheEntry.getResource().getInputStream(); + try { + IOUtils.copy(instream, outstream); + } finally { + instream.close(); + } + } + + public boolean isStreaming() { + return false; + } + + public void consumeContent() throws IOException { + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java new file mode 100644 index 000000000..381a7508d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java @@ -0,0 +1,173 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.ListIterator; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Update a {@link HttpCacheEntry} with new or updated information based on the latest + * 304 status response from the Server. Use the {@link HttpResponse} to perform + * the update. + * + * @since 4.1 + */ +@Immutable +class CacheEntryUpdater { + + private final ResourceFactory resourceFactory; + + CacheEntryUpdater() { + this(new HeapResourceFactory()); + } + + CacheEntryUpdater(final ResourceFactory resourceFactory) { + super(); + this.resourceFactory = resourceFactory; + } + + /** + * Update the entry with the new information from the response. Should only be used for + * 304 responses. + * + * @param requestId + * @param entry The cache Entry to be updated + * @param requestDate When the request was performed + * @param responseDate When the response was gotten + * @param response The HttpResponse from the backend server call + * @return HttpCacheEntry an updated version of the cache entry + * @throws java.io.IOException if something bad happens while trying to read the body from the original entry + */ + public HttpCacheEntry updateCacheEntry( + final String requestId, + final HttpCacheEntry entry, + final Date requestDate, + final Date responseDate, + final HttpResponse response) throws IOException { + Args.check(response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED, + "Response must have 304 status code"); + final Header[] mergedHeaders = mergeHeaders(entry, response); + Resource resource = null; + if (entry.getResource() != null) { + resource = resourceFactory.copy(requestId, entry.getResource()); + } + return new HttpCacheEntry( + requestDate, + responseDate, + entry.getStatusLine(), + mergedHeaders, + resource); + } + + protected Header[] mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) { + + if (entryAndResponseHaveDateHeader(entry, response) + && entryDateHeaderNewerThenResponse(entry, response)) { + // Don't merge headers, keep the entry's headers as they are newer. + return entry.getAllHeaders(); + } + + final List<Header> cacheEntryHeaderList = new ArrayList<Header>(Arrays.asList(entry + .getAllHeaders())); + removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response); + removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry); + cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders())); + + return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]); + } + + private void removeCacheHeadersThatMatchResponse(final List<Header> cacheEntryHeaderList, + final HttpResponse response) { + for (final Header responseHeader : response.getAllHeaders()) { + final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator(); + + while (cacheEntryHeaderListIter.hasNext()) { + final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName(); + + if (cacheEntryHeaderName.equals(responseHeader.getName())) { + cacheEntryHeaderListIter.remove(); + } + } + } + } + + private void removeCacheEntry1xxWarnings(final List<Header> cacheEntryHeaderList, final HttpCacheEntry entry) { + final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator(); + + while (cacheEntryHeaderListIter.hasNext()) { + final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName(); + + if (HeaderConstants.WARNING.equals(cacheEntryHeaderName)) { + for (final Header cacheEntryWarning : entry.getHeaders(HeaderConstants.WARNING)) { + if (cacheEntryWarning.getValue().startsWith("1")) { + cacheEntryHeaderListIter.remove(); + } + } + } + } + } + + private boolean entryDateHeaderNewerThenResponse(final HttpCacheEntry entry, final HttpResponse response) { + final Date entryDate = DateUtils.parseDate(entry.getFirstHeader(HTTP.DATE_HEADER) + .getValue()); + final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER) + .getValue()); + if (entryDate == null || responseDate == null) { + return false; + } + if (!entryDate.after(responseDate)) { + return false; + } + return true; + } + + private boolean entryAndResponseHaveDateHeader(final HttpCacheEntry entry, final HttpResponse response) { + if (entry.getFirstHeader(HTTP.DATE_HEADER) != null + && response.getFirstHeader(HTTP.DATE_HEADER) != null) { + return true; + } + + return false; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java new file mode 100644 index 000000000..19e62869e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java @@ -0,0 +1,288 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Date; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * Given a particular HttpRequest, flush any cache entries that this request + * would invalidate. + * + * @since 4.1 + */ +@Immutable +class CacheInvalidator implements HttpCacheInvalidator { + + private final HttpCacheStorage storage; + private final CacheKeyGenerator cacheKeyGenerator; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Create a new {@link CacheInvalidator} for a given {@link HttpCache} and + * {@link CacheKeyGenerator}. + * + * @param uriExtractor Provides identifiers for the keys to store cache entries + * @param storage the cache to store items away in + */ + public CacheInvalidator( + final CacheKeyGenerator uriExtractor, + final HttpCacheStorage storage) { + this.cacheKeyGenerator = uriExtractor; + this.storage = storage; + } + + /** + * Remove cache entries from the cache that are no longer fresh or + * have been invalidated in some way. + * + * @param host The backend host we are talking to + * @param req The HttpRequest to that host + */ + public void flushInvalidatedCacheEntries(final HttpHost host, final HttpRequest req) { + if (requestShouldNotBeCached(req)) { + log.debug("Request should not be cached"); + + final String theUri = cacheKeyGenerator.getURI(host, req); + + final HttpCacheEntry parent = getEntry(theUri); + + log.debug("parent entry: " + parent); + + if (parent != null) { + for (final String variantURI : parent.getVariantMap().values()) { + flushEntry(variantURI); + } + flushEntry(theUri); + } + final URL reqURL = getAbsoluteURL(theUri); + if (reqURL == null) { + log.error("Couldn't transform request into valid URL"); + return; + } + final Header clHdr = req.getFirstHeader("Content-Location"); + if (clHdr != null) { + final String contentLocation = clHdr.getValue(); + if (!flushAbsoluteUriFromSameHost(reqURL, contentLocation)) { + flushRelativeUriFromSameHost(reqURL, contentLocation); + } + } + final Header lHdr = req.getFirstHeader("Location"); + if (lHdr != null) { + flushAbsoluteUriFromSameHost(reqURL, lHdr.getValue()); + } + } + } + + private void flushEntry(final String uri) { + try { + storage.removeEntry(uri); + } catch (final IOException ioe) { + log.warn("unable to flush cache entry", ioe); + } + } + + private HttpCacheEntry getEntry(final String theUri) { + try { + return storage.getEntry(theUri); + } catch (final IOException ioe) { + log.warn("could not retrieve entry from storage", ioe); + } + return null; + } + + protected void flushUriIfSameHost(final URL requestURL, final URL targetURL) { + final URL canonicalTarget = getAbsoluteURL(cacheKeyGenerator.canonicalizeUri(targetURL.toString())); + if (canonicalTarget == null) { + return; + } + if (canonicalTarget.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) { + flushEntry(canonicalTarget.toString()); + } + } + + protected void flushRelativeUriFromSameHost(final URL reqURL, final String relUri) { + final URL relURL = getRelativeURL(reqURL, relUri); + if (relURL == null) { + return; + } + flushUriIfSameHost(reqURL, relURL); + } + + + protected boolean flushAbsoluteUriFromSameHost(final URL reqURL, final String uri) { + final URL absURL = getAbsoluteURL(uri); + if (absURL == null) { + return false; + } + flushUriIfSameHost(reqURL,absURL); + return true; + } + + private URL getAbsoluteURL(final String uri) { + URL absURL = null; + try { + absURL = new URL(uri); + } catch (final MalformedURLException mue) { + // nop + } + return absURL; + } + + private URL getRelativeURL(final URL reqURL, final String relUri) { + URL relURL = null; + try { + relURL = new URL(reqURL,relUri); + } catch (final MalformedURLException e) { + // nop + } + return relURL; + } + + protected boolean requestShouldNotBeCached(final HttpRequest req) { + final String method = req.getRequestLine().getMethod(); + return notGetOrHeadRequest(method); + } + + private boolean notGetOrHeadRequest(final String method) { + return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD + .equals(method)); + } + + /** Flushes entries that were invalidated by the given response + * received for the given host/request pair. + */ + public void flushInvalidatedCacheEntries(final HttpHost host, + final HttpRequest request, final HttpResponse response) { + final int status = response.getStatusLine().getStatusCode(); + if (status < 200 || status > 299) { + return; + } + final URL reqURL = getAbsoluteURL(cacheKeyGenerator.getURI(host, request)); + if (reqURL == null) { + return; + } + final URL contentLocation = getContentLocationURL(reqURL, response); + if (contentLocation != null) { + flushLocationCacheEntry(reqURL, response, contentLocation); + } + final URL location = getLocationURL(reqURL, response); + if (location != null) { + flushLocationCacheEntry(reqURL, response, location); + } + } + + private void flushLocationCacheEntry(final URL reqURL, + final HttpResponse response, final URL location) { + final String cacheKey = cacheKeyGenerator.canonicalizeUri(location.toString()); + final HttpCacheEntry entry = getEntry(cacheKey); + if (entry == null) { + return; + } + + // do not invalidate if response is strictly older than entry + // or if the etags match + + if (responseDateOlderThanEntryDate(response, entry)) { + return; + } + if (!responseAndEntryEtagsDiffer(response, entry)) { + return; + } + + flushUriIfSameHost(reqURL, location); + } + + private URL getContentLocationURL(final URL reqURL, final HttpResponse response) { + final Header clHeader = response.getFirstHeader("Content-Location"); + if (clHeader == null) { + return null; + } + final String contentLocation = clHeader.getValue(); + final URL canonURL = getAbsoluteURL(contentLocation); + if (canonURL != null) { + return canonURL; + } + return getRelativeURL(reqURL, contentLocation); + } + + private URL getLocationURL(final URL reqURL, final HttpResponse response) { + final Header clHeader = response.getFirstHeader("Location"); + if (clHeader == null) { + return null; + } + final String location = clHeader.getValue(); + final URL canonURL = getAbsoluteURL(location); + if (canonURL != null) { + return canonURL; + } + return getRelativeURL(reqURL, location); + } + + private boolean responseAndEntryEtagsDiffer(final HttpResponse response, + final HttpCacheEntry entry) { + final Header entryEtag = entry.getFirstHeader(HeaderConstants.ETAG); + final Header responseEtag = response.getFirstHeader(HeaderConstants.ETAG); + if (entryEtag == null || responseEtag == null) { + return false; + } + return (!entryEtag.getValue().equals(responseEtag.getValue())); + } + + private boolean responseDateOlderThanEntryDate(final HttpResponse response, + final HttpCacheEntry entry) { + final Header entryDateHeader = entry.getFirstHeader(HTTP.DATE_HEADER); + final Header responseDateHeader = response.getFirstHeader(HTTP.DATE_HEADER); + if (entryDateHeader == null || responseDateHeader == null) { + /* be conservative; should probably flush */ + return false; + } + final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue()); + final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue()); + if (entryDate == null || responseDate == null) { + return false; + } + return responseDate.before(entryDate); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java new file mode 100644 index 000000000..b0628041f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java @@ -0,0 +1,178 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; + +/** + * @since 4.1 + */ +@Immutable +class CacheKeyGenerator { + + private static final URI BASE_URI = URI.create("http://example.com/"); + + /** + * For a given {@link HttpHost} and {@link HttpRequest} get a URI from the + * pair that I can use as an identifier KEY into my HttpCache + * + * @param host The host for this request + * @param req the {@link HttpRequest} + * @return String the extracted URI + */ + public String getURI(final HttpHost host, final HttpRequest req) { + if (isRelativeRequest(req)) { + return canonicalizeUri(String.format("%s%s", host.toString(), req.getRequestLine().getUri())); + } + return canonicalizeUri(req.getRequestLine().getUri()); + } + + public String canonicalizeUri(final String uri) { + try { + final URI normalized = URIUtils.resolve(BASE_URI, uri); + final URL u = new URL(normalized.toASCIIString()); + final String protocol = u.getProtocol(); + final String hostname = u.getHost(); + final int port = canonicalizePort(u.getPort(), protocol); + final String path = u.getPath(); + final String query = u.getQuery(); + final String file = (query != null) ? (path + "?" + query) : path; + final URL out = new URL(protocol, hostname, port, file); + return out.toString(); + } catch (final IllegalArgumentException e) { + return uri; + } catch (final MalformedURLException e) { + return uri; + } + } + + private int canonicalizePort(final int port, final String protocol) { + if (port == -1 && "http".equalsIgnoreCase(protocol)) { + return 80; + } else if (port == -1 && "https".equalsIgnoreCase(protocol)) { + return 443; + } + return port; + } + + private boolean isRelativeRequest(final HttpRequest req) { + final String requestUri = req.getRequestLine().getUri(); + return ("*".equals(requestUri) || requestUri.startsWith("/")); + } + + protected String getFullHeaderValue(final Header[] headers) { + if (headers == null) { + return ""; + } + + final StringBuilder buf = new StringBuilder(""); + boolean first = true; + for (final Header hdr : headers) { + if (!first) { + buf.append(", "); + } + buf.append(hdr.getValue().trim()); + first = false; + + } + return buf.toString(); + } + + /** + * For a given {@link HttpHost} and {@link HttpRequest} if the request has a + * VARY header - I need to get an additional URI from the pair of host and + * request so that I can also store the variant into my HttpCache. + * + * @param host The host for this request + * @param req the {@link HttpRequest} + * @param entry the parent entry used to track the variants + * @return String the extracted variant URI + */ + public String getVariantURI(final HttpHost host, final HttpRequest req, final HttpCacheEntry entry) { + if (!entry.hasVariants()) { + return getURI(host, req); + } + return getVariantKey(req, entry) + getURI(host, req); + } + + /** + * Compute a "variant key" from the headers of a given request that are + * covered by the Vary header of a given cache entry. Any request whose + * varying headers match those of this request should have the same + * variant key. + * @param req originating request + * @param entry cache entry in question that has variants + * @return a <code>String</code> variant key + */ + public String getVariantKey(final HttpRequest req, final HttpCacheEntry entry) { + final List<String> variantHeaderNames = new ArrayList<String>(); + for (final Header varyHdr : entry.getHeaders(HeaderConstants.VARY)) { + for (final HeaderElement elt : varyHdr.getElements()) { + variantHeaderNames.add(elt.getName()); + } + } + Collections.sort(variantHeaderNames); + + StringBuilder buf; + try { + buf = new StringBuilder("{"); + boolean first = true; + for (final String headerName : variantHeaderNames) { + if (!first) { + buf.append("&"); + } + buf.append(URLEncoder.encode(headerName, Consts.UTF_8.name())); + buf.append("="); + buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)), + Consts.UTF_8.name())); + first = false; + } + buf.append("}"); + } catch (final UnsupportedEncodingException uee) { + throw new RuntimeException("couldn't encode to UTF-8", uee); + } + return buf.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java new file mode 100644 index 000000000..0ed80920c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java @@ -0,0 +1,50 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.LinkedHashMap; +import java.util.Map; + +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; + +final class CacheMap extends LinkedHashMap<String, HttpCacheEntry> { + + private static final long serialVersionUID = -7750025207539768511L; + + private final int maxEntries; + + CacheMap(final int maxEntries) { + super(20, 0.75f, true); + this.maxEntries = maxEntries; + } + + @Override + protected boolean removeEldestEntry(final Map.Entry<String, HttpCacheEntry> eldest) { + return size() > this.maxEntries; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java new file mode 100644 index 000000000..333dfbbfb --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java @@ -0,0 +1,320 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * @since 4.1 + */ +@Immutable +class CacheValidityPolicy { + + public static final long MAX_AGE = 2147483648L; + + CacheValidityPolicy() { + super(); + } + + public long getCurrentAgeSecs(final HttpCacheEntry entry, final Date now) { + return getCorrectedInitialAgeSecs(entry) + getResidentTimeSecs(entry, now); + } + + public long getFreshnessLifetimeSecs(final HttpCacheEntry entry) { + final long maxage = getMaxAge(entry); + if (maxage > -1) { + return maxage; + } + + final Date dateValue = entry.getDate(); + if (dateValue == null) { + return 0L; + } + + final Date expiry = getExpirationDate(entry); + if (expiry == null) { + return 0; + } + final long diff = expiry.getTime() - dateValue.getTime(); + return (diff / 1000); + } + + public boolean isResponseFresh(final HttpCacheEntry entry, final Date now) { + return (getCurrentAgeSecs(entry, now) < getFreshnessLifetimeSecs(entry)); + } + + /** + * Decides if this response is fresh enough based Last-Modified and Date, if available. + * This entry is meant to be used when isResponseFresh returns false. The algorithm is as follows: + * + * if last-modified and date are defined, freshness lifetime is coefficient*(date-lastModified), + * else freshness lifetime is defaultLifetime + * + * @param entry the cache entry + * @param now what time is it currently (When is right NOW) + * @param coefficient Part of the heuristic for cache entry freshness + * @param defaultLifetime How long can I assume a cache entry is default TTL + * @return {@code true} if the response is fresh + */ + public boolean isResponseHeuristicallyFresh(final HttpCacheEntry entry, + final Date now, final float coefficient, final long defaultLifetime) { + return (getCurrentAgeSecs(entry, now) < getHeuristicFreshnessLifetimeSecs(entry, coefficient, defaultLifetime)); + } + + public long getHeuristicFreshnessLifetimeSecs(final HttpCacheEntry entry, + final float coefficient, final long defaultLifetime) { + final Date dateValue = entry.getDate(); + final Date lastModifiedValue = getLastModifiedValue(entry); + + if (dateValue != null && lastModifiedValue != null) { + final long diff = dateValue.getTime() - lastModifiedValue.getTime(); + if (diff < 0) { + return 0; + } + return (long)(coefficient * (diff / 1000)); + } + + return defaultLifetime; + } + + public boolean isRevalidatable(final HttpCacheEntry entry) { + return entry.getFirstHeader(HeaderConstants.ETAG) != null + || entry.getFirstHeader(HeaderConstants.LAST_MODIFIED) != null; + } + + public boolean mustRevalidate(final HttpCacheEntry entry) { + return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE); + } + + public boolean proxyRevalidate(final HttpCacheEntry entry) { + return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE); + } + + public boolean mayReturnStaleWhileRevalidating(final HttpCacheEntry entry, final Date now) { + for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.STALE_WHILE_REVALIDATE.equalsIgnoreCase(elt.getName())) { + try { + final int allowedStalenessLifetime = Integer.parseInt(elt.getValue()); + if (getStalenessSecs(entry, now) <= allowedStalenessLifetime) { + return true; + } + } catch (final NumberFormatException nfe) { + // skip malformed directive + } + } + } + } + + return false; + } + + public boolean mayReturnStaleIfError(final HttpRequest request, + final HttpCacheEntry entry, final Date now) { + final long stalenessSecs = getStalenessSecs(entry, now); + return mayReturnStaleIfError(request.getHeaders(HeaderConstants.CACHE_CONTROL), + stalenessSecs) + || mayReturnStaleIfError(entry.getHeaders(HeaderConstants.CACHE_CONTROL), + stalenessSecs); + } + + private boolean mayReturnStaleIfError(final Header[] headers, final long stalenessSecs) { + boolean result = false; + for(final Header h : headers) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.STALE_IF_ERROR.equals(elt.getName())) { + try { + final int staleIfErrorSecs = Integer.parseInt(elt.getValue()); + if (stalenessSecs <= staleIfErrorSecs) { + result = true; + break; + } + } catch (final NumberFormatException nfe) { + // skip malformed directive + } + } + } + } + return result; + } + + /** + * @deprecated (4.3) use {@link HttpCacheEntry#getDate()}. + * @param entry + * @return the Date of the entry + */ + @Deprecated + protected Date getDateValue(final HttpCacheEntry entry) { + return entry.getDate(); + } + + protected Date getLastModifiedValue(final HttpCacheEntry entry) { + final Header dateHdr = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED); + if (dateHdr == null) { + return null; + } + return DateUtils.parseDate(dateHdr.getValue()); + } + + protected long getContentLengthValue(final HttpCacheEntry entry) { + final Header cl = entry.getFirstHeader(HTTP.CONTENT_LEN); + if (cl == null) { + return -1; + } + + try { + return Long.parseLong(cl.getValue()); + } catch (final NumberFormatException ex) { + return -1; + } + } + + protected boolean hasContentLengthHeader(final HttpCacheEntry entry) { + return null != entry.getFirstHeader(HTTP.CONTENT_LEN); + } + + /** + * This matters for deciding whether the cache entry is valid to serve as a + * response. If these values do not match, we might have a partial response + * + * @param entry The cache entry we are currently working with + * @return boolean indicating whether actual length matches Content-Length + */ + protected boolean contentLengthHeaderMatchesActualLength(final HttpCacheEntry entry) { + return !hasContentLengthHeader(entry) || getContentLengthValue(entry) == entry.getResource().length(); + } + + protected long getApparentAgeSecs(final HttpCacheEntry entry) { + final Date dateValue = entry.getDate(); + if (dateValue == null) { + return MAX_AGE; + } + final long diff = entry.getResponseDate().getTime() - dateValue.getTime(); + if (diff < 0L) { + return 0; + } + return (diff / 1000); + } + + protected long getAgeValue(final HttpCacheEntry entry) { + long ageValue = 0; + for (final Header hdr : entry.getHeaders(HeaderConstants.AGE)) { + long hdrAge; + try { + hdrAge = Long.parseLong(hdr.getValue()); + if (hdrAge < 0) { + hdrAge = MAX_AGE; + } + } catch (final NumberFormatException nfe) { + hdrAge = MAX_AGE; + } + ageValue = (hdrAge > ageValue) ? hdrAge : ageValue; + } + return ageValue; + } + + protected long getCorrectedReceivedAgeSecs(final HttpCacheEntry entry) { + final long apparentAge = getApparentAgeSecs(entry); + final long ageValue = getAgeValue(entry); + return (apparentAge > ageValue) ? apparentAge : ageValue; + } + + protected long getResponseDelaySecs(final HttpCacheEntry entry) { + final long diff = entry.getResponseDate().getTime() - entry.getRequestDate().getTime(); + return (diff / 1000L); + } + + protected long getCorrectedInitialAgeSecs(final HttpCacheEntry entry) { + return getCorrectedReceivedAgeSecs(entry) + getResponseDelaySecs(entry); + } + + protected long getResidentTimeSecs(final HttpCacheEntry entry, final Date now) { + final long diff = now.getTime() - entry.getResponseDate().getTime(); + return (diff / 1000L); + } + + protected long getMaxAge(final HttpCacheEntry entry) { + long maxage = -1; + for (final Header hdr : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for (final HeaderElement elt : hdr.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName()) + || "s-maxage".equals(elt.getName())) { + try { + final long currMaxAge = Long.parseLong(elt.getValue()); + if (maxage == -1 || currMaxAge < maxage) { + maxage = currMaxAge; + } + } catch (final NumberFormatException nfe) { + // be conservative if can't parse + maxage = 0; + } + } + } + } + return maxage; + } + + protected Date getExpirationDate(final HttpCacheEntry entry) { + final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES); + if (expiresHeader == null) { + return null; + } + return DateUtils.parseDate(expiresHeader.getValue()); + } + + public boolean hasCacheControlDirective(final HttpCacheEntry entry, + final String directive) { + for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (directive.equalsIgnoreCase(elt.getName())) { + return true; + } + } + } + return false; + } + + public long getStalenessSecs(final HttpCacheEntry entry, final Date now) { + final long age = getCurrentAgeSecs(entry, now); + final long freshness = getFreshnessLifetimeSecs(entry); + if (age <= freshness) { + return 0L; + } + return (age - freshness); + } + + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java new file mode 100644 index 000000000..500da01dc --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java @@ -0,0 +1,96 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; + +/** + * Determines if an HttpRequest is allowed to be served from the cache. + * + * @since 4.1 + */ +@Immutable +class CacheableRequestPolicy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Determines if an HttpRequest can be served from the cache. + * + * @param request + * an HttpRequest + * @return boolean Is it possible to serve this request from cache + */ + public boolean isServableFromCache(final HttpRequest request) { + final String method = request.getRequestLine().getMethod(); + + final ProtocolVersion pv = request.getRequestLine().getProtocolVersion(); + if (HttpVersion.HTTP_1_1.compareToVersion(pv) != 0) { + log.trace("non-HTTP/1.1 request was not serveable from cache"); + return false; + } + + if (!method.equals(HeaderConstants.GET_METHOD)) { + log.trace("non-GET request was not serveable from cache"); + return false; + } + + if (request.getHeaders(HeaderConstants.PRAGMA).length > 0) { + log.trace("request with Pragma header was not serveable from cache"); + return false; + } + + final Header[] cacheControlHeaders = request.getHeaders(HeaderConstants.CACHE_CONTROL); + for (final Header cacheControl : cacheControlHeaders) { + for (final HeaderElement cacheControlElement : cacheControl.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_STORE.equalsIgnoreCase(cacheControlElement + .getName())) { + log.trace("Request with no-store was not serveable from cache"); + return false; + } + + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(cacheControlElement + .getName())) { + log.trace("Request with no-cache was not serveable from cache"); + return false; + } + } + } + + log.trace("Request was serveable from cache"); + return true; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java new file mode 100644 index 000000000..f42529e57 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java @@ -0,0 +1,166 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.message.BasicHeader; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * Rebuilds an {@link HttpResponse} from a {@link net.sf.ehcache.CacheEntry} + * + * @since 4.1 + */ +@Immutable +class CachedHttpResponseGenerator { + + private final CacheValidityPolicy validityStrategy; + + CachedHttpResponseGenerator(final CacheValidityPolicy validityStrategy) { + super(); + this.validityStrategy = validityStrategy; + } + + CachedHttpResponseGenerator() { + this(new CacheValidityPolicy()); + } + + /** + * If I was able to use a {@link CacheEntity} to response to the {@link ch.boye.httpclientandroidlib.HttpRequest} then + * generate an {@link HttpResponse} based on the cache entry. + * @param entry + * {@link CacheEntity} to transform into an {@link HttpResponse} + * @return {@link HttpResponse} that was constructed + */ + CloseableHttpResponse generateResponse(final HttpCacheEntry entry) { + + final Date now = new Date(); + final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, entry + .getStatusCode(), entry.getReasonPhrase()); + + response.setHeaders(entry.getAllHeaders()); + + if (entry.getResource() != null) { + final HttpEntity entity = new CacheEntity(entry); + addMissingContentLengthHeader(response, entity); + response.setEntity(entity); + } + + final long age = this.validityStrategy.getCurrentAgeSecs(entry, now); + if (age > 0) { + if (age >= Integer.MAX_VALUE) { + response.setHeader(HeaderConstants.AGE, "2147483648"); + } else { + response.setHeader(HeaderConstants.AGE, "" + ((int) age)); + } + } + + return Proxies.enhanceResponse(response); + } + + /** + * Generate a 304 - Not Modified response from a {@link CacheEntity}. This should be + * used to respond to conditional requests, when the entry exists or has been re-validated. + */ + CloseableHttpResponse generateNotModifiedResponse(final HttpCacheEntry entry) { + + final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, + HttpStatus.SC_NOT_MODIFIED, "Not Modified"); + + // The response MUST include the following headers + // (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) + + // - Date, unless its omission is required by section 14.8.1 + Header dateHeader = entry.getFirstHeader(HTTP.DATE_HEADER); + if (dateHeader == null) { + dateHeader = new BasicHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date())); + } + response.addHeader(dateHeader); + + // - ETag and/or Content-Location, if the header would have been sent + // in a 200 response to the same request + final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG); + if (etagHeader != null) { + response.addHeader(etagHeader); + } + + final Header contentLocationHeader = entry.getFirstHeader("Content-Location"); + if (contentLocationHeader != null) { + response.addHeader(contentLocationHeader); + } + + // - Expires, Cache-Control, and/or Vary, if the field-value might + // differ from that sent in any previous response for the same + // variant + final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES); + if (expiresHeader != null) { + response.addHeader(expiresHeader); + } + + final Header cacheControlHeader = entry.getFirstHeader(HeaderConstants.CACHE_CONTROL); + if (cacheControlHeader != null) { + response.addHeader(cacheControlHeader); + } + + final Header varyHeader = entry.getFirstHeader(HeaderConstants.VARY); + if (varyHeader != null) { + response.addHeader(varyHeader); + } + + return Proxies.enhanceResponse(response); + } + + private void addMissingContentLengthHeader(final HttpResponse response, final HttpEntity entity) { + if (transferEncodingIsPresent(response)) { + return; + } + + Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN); + if (contentLength == null) { + contentLength = new BasicHeader(HTTP.CONTENT_LEN, Long.toString(entity + .getContentLength())); + response.setHeader(contentLength); + } + } + + private boolean transferEncodingIsPresent(final HttpResponse response) { + final Header hdr = response.getFirstHeader(HTTP.TRANSFER_ENCODING); + return hdr != null; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java new file mode 100644 index 000000000..a289729d3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java @@ -0,0 +1,346 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; + +/** + * Determines whether a given {@link HttpCacheEntry} is suitable to be + * used as a response for a given {@link HttpRequest}. + * + * @since 4.1 + */ +@Immutable +class CachedResponseSuitabilityChecker { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final boolean sharedCache; + private final boolean useHeuristicCaching; + private final float heuristicCoefficient; + private final long heuristicDefaultLifetime; + private final CacheValidityPolicy validityStrategy; + + CachedResponseSuitabilityChecker(final CacheValidityPolicy validityStrategy, + final CacheConfig config) { + super(); + this.validityStrategy = validityStrategy; + this.sharedCache = config.isSharedCache(); + this.useHeuristicCaching = config.isHeuristicCachingEnabled(); + this.heuristicCoefficient = config.getHeuristicCoefficient(); + this.heuristicDefaultLifetime = config.getHeuristicDefaultLifetime(); + } + + CachedResponseSuitabilityChecker(final CacheConfig config) { + this(new CacheValidityPolicy(), config); + } + + private boolean isFreshEnough(final HttpCacheEntry entry, final HttpRequest request, final Date now) { + if (validityStrategy.isResponseFresh(entry, now)) { + return true; + } + if (useHeuristicCaching && + validityStrategy.isResponseHeuristicallyFresh(entry, now, heuristicCoefficient, heuristicDefaultLifetime)) { + return true; + } + if (originInsistsOnFreshness(entry)) { + return false; + } + final long maxstale = getMaxStale(request); + if (maxstale == -1) { + return false; + } + return (maxstale > validityStrategy.getStalenessSecs(entry, now)); + } + + private boolean originInsistsOnFreshness(final HttpCacheEntry entry) { + if (validityStrategy.mustRevalidate(entry)) { + return true; + } + if (!sharedCache) { + return false; + } + return validityStrategy.proxyRevalidate(entry) || + validityStrategy.hasCacheControlDirective(entry, "s-maxage"); + } + + private long getMaxStale(final HttpRequest request) { + long maxstale = -1; + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) { + if ((elt.getValue() == null || "".equals(elt.getValue().trim())) + && maxstale == -1) { + maxstale = Long.MAX_VALUE; + } else { + try { + long val = Long.parseLong(elt.getValue()); + if (val < 0) { + val = 0; + } + if (maxstale == -1 || val < maxstale) { + maxstale = val; + } + } catch (final NumberFormatException nfe) { + // err on the side of preserving semantic transparency + maxstale = 0; + } + } + } + } + } + return maxstale; + } + + /** + * Determine if I can utilize a {@link HttpCacheEntry} to respond to the given + * {@link HttpRequest} + * + * @param host + * {@link HttpHost} + * @param request + * {@link HttpRequest} + * @param entry + * {@link HttpCacheEntry} + * @param now + * Right now in time + * @return boolean yes/no answer + */ + public boolean canCachedResponseBeUsed(final HttpHost host, final HttpRequest request, final HttpCacheEntry entry, final Date now) { + + if (!isFreshEnough(entry, request, now)) { + log.trace("Cache entry was not fresh enough"); + return false; + } + + if (!validityStrategy.contentLengthHeaderMatchesActualLength(entry)) { + log.debug("Cache entry Content-Length and header information do not match"); + return false; + } + + if (hasUnsupportedConditionalHeaders(request)) { + log.debug("Request contained conditional headers we don't handle"); + return false; + } + + if (!isConditional(request) && entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { + return false; + } + + if (isConditional(request) && !allConditionalsMatch(request, entry, now)) { + return false; + } + + for (final Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for (final HeaderElement elt : ccHdr.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) { + log.trace("Response contained NO CACHE directive, cache was not suitable"); + return false; + } + + if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elt.getName())) { + log.trace("Response contained NO STORE directive, cache was not suitable"); + return false; + } + + if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) { + try { + final int maxage = Integer.parseInt(elt.getValue()); + if (validityStrategy.getCurrentAgeSecs(entry, now) > maxage) { + log.trace("Response from cache was NOT suitable due to max age"); + return false; + } + } catch (final NumberFormatException ex) { + // err conservatively + log.debug("Response from cache was malformed" + ex.getMessage()); + return false; + } + } + + if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) { + try { + final int maxstale = Integer.parseInt(elt.getValue()); + if (validityStrategy.getFreshnessLifetimeSecs(entry) > maxstale) { + log.trace("Response from cache was not suitable due to Max stale freshness"); + return false; + } + } catch (final NumberFormatException ex) { + // err conservatively + log.debug("Response from cache was malformed: " + ex.getMessage()); + return false; + } + } + + if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())) { + try { + final long minfresh = Long.parseLong(elt.getValue()); + if (minfresh < 0L) { + return false; + } + final long age = validityStrategy.getCurrentAgeSecs(entry, now); + final long freshness = validityStrategy.getFreshnessLifetimeSecs(entry); + if (freshness - age < minfresh) { + log.trace("Response from cache was not suitable due to min fresh " + + "freshness requirement"); + return false; + } + } catch (final NumberFormatException ex) { + // err conservatively + log.debug("Response from cache was malformed: " + ex.getMessage()); + return false; + } + } + } + } + + log.trace("Response from cache was suitable"); + return true; + } + + /** + * Is this request the type of conditional request we support? + * @param request The current httpRequest being made + * @return {@code true} if the request is supported + */ + public boolean isConditional(final HttpRequest request) { + return hasSupportedEtagValidator(request) || hasSupportedLastModifiedValidator(request); + } + + /** + * Check that conditionals that are part of this request match + * @param request The current httpRequest being made + * @param entry the cache entry + * @param now right NOW in time + * @return {@code true} if the request matches all conditionals + */ + public boolean allConditionalsMatch(final HttpRequest request, final HttpCacheEntry entry, final Date now) { + final boolean hasEtagValidator = hasSupportedEtagValidator(request); + final boolean hasLastModifiedValidator = hasSupportedLastModifiedValidator(request); + + final boolean etagValidatorMatches = (hasEtagValidator) && etagValidatorMatches(request, entry); + final boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) && lastModifiedValidatorMatches(request, entry, now); + + if ((hasEtagValidator && hasLastModifiedValidator) + && !(etagValidatorMatches && lastModifiedValidatorMatches)) { + return false; + } else if (hasEtagValidator && !etagValidatorMatches) { + return false; + } + + if (hasLastModifiedValidator && !lastModifiedValidatorMatches) { + return false; + } + return true; + } + + private boolean hasUnsupportedConditionalHeaders(final HttpRequest request) { + return (request.getFirstHeader(HeaderConstants.IF_RANGE) != null + || request.getFirstHeader(HeaderConstants.IF_MATCH) != null + || hasValidDateField(request, HeaderConstants.IF_UNMODIFIED_SINCE)); + } + + private boolean hasSupportedEtagValidator(final HttpRequest request) { + return request.containsHeader(HeaderConstants.IF_NONE_MATCH); + } + + private boolean hasSupportedLastModifiedValidator(final HttpRequest request) { + return hasValidDateField(request, HeaderConstants.IF_MODIFIED_SINCE); + } + + /** + * Check entry against If-None-Match + * @param request The current httpRequest being made + * @param entry the cache entry + * @return boolean does the etag validator match + */ + private boolean etagValidatorMatches(final HttpRequest request, final HttpCacheEntry entry) { + final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG); + final String etag = (etagHeader != null) ? etagHeader.getValue() : null; + final Header[] ifNoneMatch = request.getHeaders(HeaderConstants.IF_NONE_MATCH); + if (ifNoneMatch != null) { + for (final Header h : ifNoneMatch) { + for (final HeaderElement elt : h.getElements()) { + final String reqEtag = elt.toString(); + if (("*".equals(reqEtag) && etag != null) + || reqEtag.equals(etag)) { + return true; + } + } + } + } + return false; + } + + /** + * Check entry against If-Modified-Since, if If-Modified-Since is in the future it is invalid as per + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + * @param request The current httpRequest being made + * @param entry the cache entry + * @param now right NOW in time + * @return boolean Does the last modified header match + */ + private boolean lastModifiedValidatorMatches(final HttpRequest request, final HttpCacheEntry entry, final Date now) { + final Header lastModifiedHeader = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED); + Date lastModified = null; + if (lastModifiedHeader != null) { + lastModified = DateUtils.parseDate(lastModifiedHeader.getValue()); + } + if (lastModified == null) { + return false; + } + + for (final Header h : request.getHeaders(HeaderConstants.IF_MODIFIED_SINCE)) { + final Date ifModifiedSince = DateUtils.parseDate(h.getValue()); + if (ifModifiedSince != null) { + if (ifModifiedSince.after(now) || lastModified.after(ifModifiedSince)) { + return false; + } + } + } + return true; + } + + private boolean hasValidDateField(final HttpRequest request, final String headerName) { + for(final Header h : request.getHeaders(headerName)) { + final Date date = DateUtils.parseDate(h.getValue()); + return date != null; + } + return false; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java new file mode 100644 index 000000000..cf9b8154a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java @@ -0,0 +1,870 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.RequestLine; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.CacheResponseStatus; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheContext; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.VersionInfo; + +/** + * Request executor in the request execution chain that is responsible for + * transparent client-side caching. The current implementation is conditionally + * compliant with HTTP/1.1 (meaning all the MUST and MUST NOTs are obeyed), + * although quite a lot, though not all, of the SHOULDs and SHOULD NOTs + * are obeyed too. + * <p/> + * Folks that would like to experiment with alternative storage backends + * should look at the {@link HttpCacheStorage} interface and the related + * package documentation there. You may also be interested in the provided + * {@link ch.boye.httpclientandroidlib.impl.client.cache.ehcache.EhcacheHttpCacheStorage + * EhCache} and {@link + * ch.boye.httpclientandroidlib.impl.client.cache.memcached.MemcachedHttpCacheStorage + * memcached} storage backends. + * <p/> + * Further responsibilities such as communication with the opposite + * endpoint is delegated to the next executor in the request execution + * chain. + * + * @since 4.3 + */ +@ThreadSafe // So long as the responseCache implementation is threadsafe +public class CachingExec implements ClientExecChain { + + private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false; + + private final AtomicLong cacheHits = new AtomicLong(); + private final AtomicLong cacheMisses = new AtomicLong(); + private final AtomicLong cacheUpdates = new AtomicLong(); + + private final Map<ProtocolVersion, String> viaHeaders = new HashMap<ProtocolVersion, String>(4); + + private final CacheConfig cacheConfig; + private final ClientExecChain backend; + private final HttpCache responseCache; + private final CacheValidityPolicy validityPolicy; + private final CachedHttpResponseGenerator responseGenerator; + private final CacheableRequestPolicy cacheableRequestPolicy; + private final CachedResponseSuitabilityChecker suitabilityChecker; + private final ConditionalRequestBuilder conditionalRequestBuilder; + private final ResponseProtocolCompliance responseCompliance; + private final RequestProtocolCompliance requestCompliance; + private final ResponseCachingPolicy responseCachingPolicy; + + private final AsynchronousValidator asynchRevalidator; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + public CachingExec( + final ClientExecChain backend, + final HttpCache cache, + final CacheConfig config) { + this(backend, cache, config, null); + } + + public CachingExec( + final ClientExecChain backend, + final HttpCache cache, + final CacheConfig config, + final AsynchronousValidator asynchRevalidator) { + super(); + Args.notNull(backend, "HTTP backend"); + Args.notNull(cache, "HttpCache"); + this.cacheConfig = config != null ? config : CacheConfig.DEFAULT; + this.backend = backend; + this.responseCache = cache; + this.validityPolicy = new CacheValidityPolicy(); + this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy); + this.cacheableRequestPolicy = new CacheableRequestPolicy(); + this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, this.cacheConfig); + this.conditionalRequestBuilder = new ConditionalRequestBuilder(); + this.responseCompliance = new ResponseProtocolCompliance(); + this.requestCompliance = new RequestProtocolCompliance(this.cacheConfig.isWeakETagOnPutDeleteAllowed()); + this.responseCachingPolicy = new ResponseCachingPolicy( + this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(), + this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled()); + this.asynchRevalidator = asynchRevalidator; + } + + public CachingExec( + final ClientExecChain backend, + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config) { + this(backend, new BasicHttpCache(resourceFactory, storage, config), config); + } + + public CachingExec(final ClientExecChain backend) { + this(backend, new BasicHttpCache(), CacheConfig.DEFAULT); + } + + CachingExec( + final ClientExecChain backend, + final HttpCache responseCache, + final CacheValidityPolicy validityPolicy, + final ResponseCachingPolicy responseCachingPolicy, + final CachedHttpResponseGenerator responseGenerator, + final CacheableRequestPolicy cacheableRequestPolicy, + final CachedResponseSuitabilityChecker suitabilityChecker, + final ConditionalRequestBuilder conditionalRequestBuilder, + final ResponseProtocolCompliance responseCompliance, + final RequestProtocolCompliance requestCompliance, + final CacheConfig config, + final AsynchronousValidator asynchRevalidator) { + this.cacheConfig = config != null ? config : CacheConfig.DEFAULT; + this.backend = backend; + this.responseCache = responseCache; + this.validityPolicy = validityPolicy; + this.responseCachingPolicy = responseCachingPolicy; + this.responseGenerator = responseGenerator; + this.cacheableRequestPolicy = cacheableRequestPolicy; + this.suitabilityChecker = suitabilityChecker; + this.conditionalRequestBuilder = conditionalRequestBuilder; + this.responseCompliance = responseCompliance; + this.requestCompliance = requestCompliance; + this.asynchRevalidator = asynchRevalidator; + } + + /** + * Reports the number of times that the cache successfully responded + * to an {@link HttpRequest} without contacting the origin server. + * @return the number of cache hits + */ + public long getCacheHits() { + return cacheHits.get(); + } + + /** + * Reports the number of times that the cache contacted the origin + * server because it had no appropriate response cached. + * @return the number of cache misses + */ + public long getCacheMisses() { + return cacheMisses.get(); + } + + /** + * Reports the number of times that the cache was able to satisfy + * a response by revalidating an existing but stale cache entry. + * @return the number of cache revalidations + */ + public long getCacheUpdates() { + return cacheUpdates.get(); + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request) throws IOException, HttpException { + return execute(route, request, HttpClientContext.create(), null); + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context) throws IOException, HttpException { + return execute(route, request, context, null); + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + + final HttpHost target = context.getTargetHost(); + final String via = generateViaHeader(request.getOriginal()); + + // default response context + setResponseStatus(context, CacheResponseStatus.CACHE_MISS); + + if (clientRequestsOurOptions(request)) { + setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE); + return Proxies.enhanceResponse(new OptionsHttp11Response()); + } + + final HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context); + if (fatalErrorResponse != null) { + return Proxies.enhanceResponse(fatalErrorResponse); + } + + requestCompliance.makeRequestCompliant(request); + request.addHeader("Via",via); + + flushEntriesInvalidatedByRequest(context.getTargetHost(), request); + + if (!cacheableRequestPolicy.isServableFromCache(request)) { + log.debug("Request is not servable from cache"); + return callBackend(route, request, context, execAware); + } + + final HttpCacheEntry entry = satisfyFromCache(target, request); + if (entry == null) { + log.debug("Cache miss"); + return handleCacheMiss(route, request, context, execAware); + } else { + return handleCacheHit(route, request, context, execAware, entry); + } + } + + private CloseableHttpResponse handleCacheHit( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry entry) throws IOException, HttpException { + final HttpHost target = context.getTargetHost(); + recordCacheHit(target, request); + CloseableHttpResponse out = null; + final Date now = getCurrentDate(); + if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) { + log.debug("Cache hit"); + out = generateCachedResponse(request, context, entry, now); + } else if (!mayCallBackend(request)) { + log.debug("Cache entry not suitable but only-if-cached requested"); + out = generateGatewayTimeout(context); + } else if (!(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED + && !suitabilityChecker.isConditional(request))) { + log.debug("Revalidating cache entry"); + return revalidateCacheEntry(route, request, context, execAware, entry, now); + } else { + log.debug("Cache entry not usable; calling backend"); + return callBackend(route, request, context, execAware); + } + context.setAttribute(HttpClientContext.HTTP_ROUTE, route); + context.setAttribute(HttpClientContext.HTTP_TARGET_HOST, target); + context.setAttribute(HttpClientContext.HTTP_REQUEST, request); + context.setAttribute(HttpClientContext.HTTP_RESPONSE, out); + context.setAttribute(HttpClientContext.HTTP_REQ_SENT, Boolean.TRUE); + return out; + } + + private CloseableHttpResponse revalidateCacheEntry( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry entry, + final Date now) throws HttpException { + + try { + if (asynchRevalidator != null + && !staleResponseNotAllowed(request, entry, now) + && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) { + log.trace("Serving stale with asynchronous revalidation"); + final CloseableHttpResponse resp = generateCachedResponse(request, context, entry, now); + asynchRevalidator.revalidateCacheEntry(this, route, request, context, execAware, entry); + return resp; + } + return revalidateCacheEntry(route, request, context, execAware, entry); + } catch (final IOException ioex) { + return handleRevalidationFailure(request, context, entry, now); + } + } + + private CloseableHttpResponse handleCacheMiss( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + final HttpHost target = context.getTargetHost(); + recordCacheMiss(target, request); + + if (!mayCallBackend(request)) { + return Proxies.enhanceResponse( + new BasicHttpResponse( + HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout")); + } + + final Map<String, Variant> variants = getExistingCacheVariants(target, request); + if (variants != null && variants.size() > 0) { + return negotiateResponseFromVariants(route, request, context, + execAware, variants); + } + + return callBackend(route, request, context, execAware); + } + + private HttpCacheEntry satisfyFromCache( + final HttpHost target, final HttpRequestWrapper request) { + HttpCacheEntry entry = null; + try { + entry = responseCache.getCacheEntry(target, request); + } catch (final IOException ioe) { + log.warn("Unable to retrieve entries from cache", ioe); + } + return entry; + } + + private HttpResponse getFatallyNoncompliantResponse( + final HttpRequestWrapper request, + final HttpContext context) { + HttpResponse fatalErrorResponse = null; + final List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request); + + for (final RequestProtocolError error : fatalError) { + setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE); + fatalErrorResponse = requestCompliance.getErrorForRequest(error); + } + return fatalErrorResponse; + } + + private Map<String, Variant> getExistingCacheVariants( + final HttpHost target, + final HttpRequestWrapper request) { + Map<String,Variant> variants = null; + try { + variants = responseCache.getVariantCacheEntriesWithEtags(target, request); + } catch (final IOException ioe) { + log.warn("Unable to retrieve variant entries from cache", ioe); + } + return variants; + } + + private void recordCacheMiss(final HttpHost target, final HttpRequestWrapper request) { + cacheMisses.getAndIncrement(); + if (log.isTraceEnabled()) { + final RequestLine rl = request.getRequestLine(); + log.trace("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]"); + } + } + + private void recordCacheHit(final HttpHost target, final HttpRequestWrapper request) { + cacheHits.getAndIncrement(); + if (log.isTraceEnabled()) { + final RequestLine rl = request.getRequestLine(); + log.trace("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]"); + } + } + + private void recordCacheUpdate(final HttpContext context) { + cacheUpdates.getAndIncrement(); + setResponseStatus(context, CacheResponseStatus.VALIDATED); + } + + private void flushEntriesInvalidatedByRequest( + final HttpHost target, + final HttpRequestWrapper request) { + try { + responseCache.flushInvalidatedCacheEntriesFor(target, request); + } catch (final IOException ioe) { + log.warn("Unable to flush invalidated entries from cache", ioe); + } + } + + private CloseableHttpResponse generateCachedResponse(final HttpRequestWrapper request, + final HttpContext context, final HttpCacheEntry entry, final Date now) { + final CloseableHttpResponse cachedResponse; + if (request.containsHeader(HeaderConstants.IF_NONE_MATCH) + || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) { + cachedResponse = responseGenerator.generateNotModifiedResponse(entry); + } else { + cachedResponse = responseGenerator.generateResponse(entry); + } + setResponseStatus(context, CacheResponseStatus.CACHE_HIT); + if (validityPolicy.getStalenessSecs(entry, now) > 0L) { + cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\""); + } + return cachedResponse; + } + + private CloseableHttpResponse handleRevalidationFailure( + final HttpRequestWrapper request, + final HttpContext context, + final HttpCacheEntry entry, + final Date now) { + if (staleResponseNotAllowed(request, entry, now)) { + return generateGatewayTimeout(context); + } else { + return unvalidatedCacheHit(context, entry); + } + } + + private CloseableHttpResponse generateGatewayTimeout( + final HttpContext context) { + setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE); + return Proxies.enhanceResponse(new BasicHttpResponse( + HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, + "Gateway Timeout")); + } + + private CloseableHttpResponse unvalidatedCacheHit( + final HttpContext context, final HttpCacheEntry entry) { + final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(entry); + setResponseStatus(context, CacheResponseStatus.CACHE_HIT); + cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\""); + return cachedResponse; + } + + private boolean staleResponseNotAllowed( + final HttpRequestWrapper request, + final HttpCacheEntry entry, + final Date now) { + return validityPolicy.mustRevalidate(entry) + || (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry)) + || explicitFreshnessRequest(request, entry, now); + } + + private boolean mayCallBackend(final HttpRequestWrapper request) { + for (final Header h: request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for (final HeaderElement elt : h.getElements()) { + if ("only-if-cached".equals(elt.getName())) { + log.trace("Request marked only-if-cached"); + return false; + } + } + } + return true; + } + + private boolean explicitFreshnessRequest( + final HttpRequestWrapper request, + final HttpCacheEntry entry, + final Date now) { + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) { + try { + final int maxstale = Integer.parseInt(elt.getValue()); + final long age = validityPolicy.getCurrentAgeSecs(entry, now); + final long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry); + if (age - lifetime > maxstale) { + return true; + } + } catch (final NumberFormatException nfe) { + return true; + } + } else if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName()) + || HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) { + return true; + } + } + } + return false; + } + + private String generateViaHeader(final HttpMessage msg) { + + final ProtocolVersion pv = msg.getProtocolVersion(); + final String existingEntry = viaHeaders.get(pv); + if (existingEntry != null) { + return existingEntry; + } + + final VersionInfo vi = VersionInfo.loadVersionInfo("ch.boye.httpclientandroidlib.client", getClass().getClassLoader()); + final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; + + String value; + if ("http".equalsIgnoreCase(pv.getProtocol())) { + value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getMajor(), pv.getMinor(), + release); + } else { + value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), pv.getMajor(), + pv.getMinor(), release); + } + viaHeaders.put(pv, value); + + return value; + } + + private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) { + if (context != null) { + context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value); + } + } + + /** + * Reports whether this {@code CachingHttpClient} implementation + * supports byte-range requests as specified by the {@code Range} + * and {@code Content-Range} headers. + * @return {@code true} if byte-range requests are supported + */ + public boolean supportsRangeAndContentRangeHeaders() { + return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS; + } + + Date getCurrentDate() { + return new Date(); + } + + boolean clientRequestsOurOptions(final HttpRequest request) { + final RequestLine line = request.getRequestLine(); + + if (!HeaderConstants.OPTIONS_METHOD.equals(line.getMethod())) { + return false; + } + + if (!"*".equals(line.getUri())) { + return false; + } + + if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue())) { + return false; + } + + return true; + } + + CloseableHttpResponse callBackend( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + + final Date requestDate = getCurrentDate(); + + log.trace("Calling the backend"); + final CloseableHttpResponse backendResponse = backend.execute(route, request, context, execAware); + try { + backendResponse.addHeader("Via", generateViaHeader(backendResponse)); + return handleBackendResponse(route, request, context, execAware, + requestDate, getCurrentDate(), backendResponse); + } catch (final IOException ex) { + backendResponse.close(); + throw ex; + } catch (final RuntimeException ex) { + backendResponse.close(); + throw ex; + } + } + + private boolean revalidationResponseIsTooOld(final HttpResponse backendResponse, + final HttpCacheEntry cacheEntry) { + final Header entryDateHeader = cacheEntry.getFirstHeader(HTTP.DATE_HEADER); + final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER); + if (entryDateHeader != null && responseDateHeader != null) { + final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue()); + final Date respDate = DateUtils.parseDate(responseDateHeader.getValue()); + if (entryDate == null || respDate == null) { + // either backend response or cached entry did not have a valid + // Date header, so we can't tell if they are out of order + // according to the origin clock; thus we can skip the + // unconditional retry recommended in 13.2.6 of RFC 2616. + return false; + } + if (respDate.before(entryDate)) { + return true; + } + } + return false; + } + + CloseableHttpResponse negotiateResponseFromVariants( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final Map<String, Variant> variants) throws IOException, HttpException { + final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder + .buildConditionalRequestFromVariants(request, variants); + + final Date requestDate = getCurrentDate(); + final CloseableHttpResponse backendResponse = backend.execute( + route, conditionalRequest, context, execAware); + try { + final Date responseDate = getCurrentDate(); + + backendResponse.addHeader("Via", generateViaHeader(backendResponse)); + + if (backendResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_MODIFIED) { + return handleBackendResponse( + route, request, context, execAware, + requestDate, responseDate, backendResponse); + } + + final Header resultEtagHeader = backendResponse.getFirstHeader(HeaderConstants.ETAG); + if (resultEtagHeader == null) { + log.warn("304 response did not contain ETag"); + IOUtils.consume(backendResponse.getEntity()); + backendResponse.close(); + return callBackend(route, request, context, execAware); + } + + final String resultEtag = resultEtagHeader.getValue(); + final Variant matchingVariant = variants.get(resultEtag); + if (matchingVariant == null) { + log.debug("304 response did not contain ETag matching one sent in If-None-Match"); + IOUtils.consume(backendResponse.getEntity()); + backendResponse.close(); + return callBackend(route, request, context, execAware); + } + + final HttpCacheEntry matchedEntry = matchingVariant.getEntry(); + + if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) { + IOUtils.consume(backendResponse.getEntity()); + backendResponse.close(); + return retryRequestUnconditionally(route, request, context, execAware, matchedEntry); + } + + recordCacheUpdate(context); + + final HttpCacheEntry responseEntry = getUpdatedVariantEntry( + context.getTargetHost(), conditionalRequest, requestDate, responseDate, + backendResponse, matchingVariant, matchedEntry); + backendResponse.close(); + + final CloseableHttpResponse resp = responseGenerator.generateResponse(responseEntry); + tryToUpdateVariantMap(context.getTargetHost(), request, matchingVariant); + + if (shouldSendNotModifiedResponse(request, responseEntry)) { + return responseGenerator.generateNotModifiedResponse(responseEntry); + } + return resp; + } catch (final IOException ex) { + backendResponse.close(); + throw ex; + } catch (final RuntimeException ex) { + backendResponse.close(); + throw ex; + } + } + + private CloseableHttpResponse retryRequestUnconditionally( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry matchedEntry) throws IOException, HttpException { + final HttpRequestWrapper unconditional = conditionalRequestBuilder + .buildUnconditionalRequest(request, matchedEntry); + return callBackend(route, unconditional, context, execAware); + } + + private HttpCacheEntry getUpdatedVariantEntry( + final HttpHost target, + final HttpRequestWrapper conditionalRequest, + final Date requestDate, + final Date responseDate, + final CloseableHttpResponse backendResponse, + final Variant matchingVariant, + final HttpCacheEntry matchedEntry) throws IOException { + HttpCacheEntry responseEntry = matchedEntry; + try { + responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest, + matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey()); + } catch (final IOException ioe) { + log.warn("Could not update cache entry", ioe); + } finally { + backendResponse.close(); + } + return responseEntry; + } + + private void tryToUpdateVariantMap( + final HttpHost target, + final HttpRequestWrapper request, + final Variant matchingVariant) { + try { + responseCache.reuseVariantEntryFor(target, request, matchingVariant); + } catch (final IOException ioe) { + log.warn("Could not update cache entry to reuse variant", ioe); + } + } + + private boolean shouldSendNotModifiedResponse( + final HttpRequestWrapper request, + final HttpCacheEntry responseEntry) { + return (suitabilityChecker.isConditional(request) + && suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date())); + } + + CloseableHttpResponse revalidateCacheEntry( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry cacheEntry) throws IOException, HttpException { + + final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry); + + Date requestDate = getCurrentDate(); + CloseableHttpResponse backendResponse = backend.execute( + route, conditionalRequest, context, execAware); + Date responseDate = getCurrentDate(); + + if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) { + backendResponse.close(); + final HttpRequestWrapper unconditional = conditionalRequestBuilder + .buildUnconditionalRequest(request, cacheEntry); + requestDate = getCurrentDate(); + backendResponse = backend.execute(route, unconditional, context, execAware); + responseDate = getCurrentDate(); + } + + backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse)); + + final int statusCode = backendResponse.getStatusLine().getStatusCode(); + if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) { + recordCacheUpdate(context); + } + + if (statusCode == HttpStatus.SC_NOT_MODIFIED) { + final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry( + context.getTargetHost(), request, cacheEntry, + backendResponse, requestDate, responseDate); + if (suitabilityChecker.isConditional(request) + && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) { + return responseGenerator + .generateNotModifiedResponse(updatedEntry); + } + return responseGenerator.generateResponse(updatedEntry); + } + + if (staleIfErrorAppliesTo(statusCode) + && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate()) + && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) { + try { + final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry); + cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\""); + return cachedResponse; + } finally { + backendResponse.close(); + } + } + return handleBackendResponse( + route, conditionalRequest, context, execAware, + requestDate, responseDate, backendResponse); + } + + private boolean staleIfErrorAppliesTo(final int statusCode) { + return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR + || statusCode == HttpStatus.SC_BAD_GATEWAY + || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE + || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT; + } + + CloseableHttpResponse handleBackendResponse( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final Date requestDate, + final Date responseDate, + final CloseableHttpResponse backendResponse) throws IOException { + + log.trace("Handling Backend response"); + responseCompliance.ensureProtocolCompliance(request, backendResponse); + + final HttpHost target = context.getTargetHost(); + final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse); + responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse); + if (cacheable && !alreadyHaveNewerCacheEntry(target, request, backendResponse)) { + storeRequestIfModifiedSinceFor304Response(request, backendResponse); + return responseCache.cacheAndReturnResponse(target, request, + backendResponse, requestDate, responseDate); + } + if (!cacheable) { + try { + responseCache.flushCacheEntriesFor(target, request); + } catch (final IOException ioe) { + log.warn("Unable to flush invalid cache entries", ioe); + } + } + return backendResponse; + } + + /** + * For 304 Not modified responses, adds a "Last-Modified" header with the + * value of the "If-Modified-Since" header passed in the request. This + * header is required to be able to reuse match the cache entry for + * subsequent requests but as defined in http specifications it is not + * included in 304 responses by backend servers. This header will not be + * included in the resulting response. + */ + private void storeRequestIfModifiedSinceFor304Response( + final HttpRequest request, final HttpResponse backendResponse) { + if (backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { + final Header h = request.getFirstHeader("If-Modified-Since"); + if (h != null) { + backendResponse.addHeader("Last-Modified", h.getValue()); + } + } + } + + private boolean alreadyHaveNewerCacheEntry(final HttpHost target, final HttpRequestWrapper request, + final HttpResponse backendResponse) { + HttpCacheEntry existing = null; + try { + existing = responseCache.getCacheEntry(target, request); + } catch (final IOException ioe) { + // nop + } + if (existing == null) { + return false; + } + final Header entryDateHeader = existing.getFirstHeader(HTTP.DATE_HEADER); + if (entryDateHeader == null) { + return false; + } + final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER); + if (responseDateHeader == null) { + return false; + } + final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue()); + final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue()); + if (entryDate == null || responseDate == null) { + return false; + } + return responseDate.before(entryDate); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java new file mode 100644 index 000000000..385324f32 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java @@ -0,0 +1,149 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; + +import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; + +/** + * Builder for {@link ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient} + * instances capable of client-side caching. + * + * @since 4.3 + */ +public class CachingHttpClientBuilder extends HttpClientBuilder { + + private ResourceFactory resourceFactory; + private HttpCacheStorage storage; + private File cacheDir; + private CacheConfig cacheConfig; + private SchedulingStrategy schedulingStrategy; + private HttpCacheInvalidator httpCacheInvalidator; + + public static CachingHttpClientBuilder create() { + return new CachingHttpClientBuilder(); + } + + protected CachingHttpClientBuilder() { + super(); + } + + public final CachingHttpClientBuilder setResourceFactory( + final ResourceFactory resourceFactory) { + this.resourceFactory = resourceFactory; + return this; + } + + public final CachingHttpClientBuilder setHttpCacheStorage( + final HttpCacheStorage storage) { + this.storage = storage; + return this; + } + + public final CachingHttpClientBuilder setCacheDir( + final File cacheDir) { + this.cacheDir = cacheDir; + return this; + } + + public final CachingHttpClientBuilder setCacheConfig( + final CacheConfig cacheConfig) { + this.cacheConfig = cacheConfig; + return this; + } + + public final CachingHttpClientBuilder setSchedulingStrategy( + final SchedulingStrategy schedulingStrategy) { + this.schedulingStrategy = schedulingStrategy; + return this; + } + + public final CachingHttpClientBuilder setHttpCacheInvalidator( + final HttpCacheInvalidator cacheInvalidator) { + this.httpCacheInvalidator = cacheInvalidator; + return this; + } + + @Override + protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) { + final CacheConfig config = this.cacheConfig != null ? this.cacheConfig : CacheConfig.DEFAULT; + ResourceFactory resourceFactory = this.resourceFactory; + if (resourceFactory == null) { + if (this.cacheDir == null) { + resourceFactory = new HeapResourceFactory(); + } else { + resourceFactory = new FileResourceFactory(cacheDir); + } + } + HttpCacheStorage storage = this.storage; + if (storage == null) { + if (this.cacheDir == null) { + storage = new BasicHttpCacheStorage(config); + } else { + final ManagedHttpCacheStorage managedStorage = new ManagedHttpCacheStorage(config); + addCloseable(managedStorage); + storage = managedStorage; + } + } + final AsynchronousValidator revalidator = createAsynchronousRevalidator(config); + final CacheKeyGenerator uriExtractor = new CacheKeyGenerator(); + + HttpCacheInvalidator cacheInvalidator = this.httpCacheInvalidator; + if (cacheInvalidator == null) { + cacheInvalidator = new CacheInvalidator(uriExtractor, storage); + } + + return new CachingExec(mainExec, + new BasicHttpCache( + resourceFactory, + storage, config, + uriExtractor, + cacheInvalidator), config, revalidator); + } + + private AsynchronousValidator createAsynchronousRevalidator(final CacheConfig config) { + if (config.getAsynchronousWorkersMax() > 0) { + final SchedulingStrategy configuredSchedulingStrategy = createSchedulingStrategy(config); + final AsynchronousValidator revalidator = new AsynchronousValidator( + configuredSchedulingStrategy); + addCloseable(revalidator); + return revalidator; + } + return null; + } + + @SuppressWarnings("resource") + private SchedulingStrategy createSchedulingStrategy(final CacheConfig config) { + return schedulingStrategy != null ? schedulingStrategy : new ImmediateSchedulingStrategy(config); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java new file mode 100644 index 000000000..328c147f3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java @@ -0,0 +1,74 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; + +/** + * Factory methods for {@link CloseableHttpClient} instances + * capable of client-side caching. + * + * @since 4.3 + */ +@Immutable +public class CachingHttpClients { + + private CachingHttpClients() { + super(); + } + + /** + * Creates builder object for construction of custom + * {@link CloseableHttpClient} instances. + */ + public static CachingHttpClientBuilder custom() { + return CachingHttpClientBuilder.create(); + } + + /** + * Creates {@link CloseableHttpClient} instance that uses a memory bound + * response cache. + */ + public static CloseableHttpClient createMemoryBound() { + return CachingHttpClientBuilder.create().build(); + } + + /** + * Creates {@link CloseableHttpClient} instance that uses a file system + * bound response cache. + * + * @param cacheDir location of response cache. + */ + public static CloseableHttpClient createFileBound(final File cacheDir) { + return CachingHttpClientBuilder.create().setCacheDir(cacheDir).build(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java new file mode 100644 index 000000000..62bfaff3e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java @@ -0,0 +1,104 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity; +import ch.boye.httpclientandroidlib.util.Args; + +@NotThreadSafe +class CombinedEntity extends AbstractHttpEntity { + + private final Resource resource; + private final InputStream combinedStream; + + CombinedEntity(final Resource resource, final InputStream instream) throws IOException { + super(); + this.resource = resource; + this.combinedStream = new SequenceInputStream( + new ResourceStream(resource.getInputStream()), instream); + } + + public long getContentLength() { + return -1; + } + + public boolean isRepeatable() { + return false; + } + + public boolean isStreaming() { + return true; + } + + public InputStream getContent() throws IOException, IllegalStateException { + return this.combinedStream; + } + + public void writeTo(final OutputStream outstream) throws IOException { + Args.notNull(outstream, "Output stream"); + final InputStream instream = getContent(); + try { + int l; + final byte[] tmp = new byte[2048]; + while ((l = instream.read(tmp)) != -1) { + outstream.write(tmp, 0, l); + } + } finally { + instream.close(); + } + } + + private void dispose() { + this.resource.dispose(); + } + + class ResourceStream extends FilterInputStream { + + protected ResourceStream(final InputStream in) { + super(in); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + dispose(); + } + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java new file mode 100644 index 000000000..0edc4fb85 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java @@ -0,0 +1,140 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Map; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; + +/** + * @since 4.1 + */ +@Immutable +class ConditionalRequestBuilder { + + /** + * When a {@link HttpCacheEntry} is stale but 'might' be used as a response + * to an {@link ch.boye.httpclientandroidlib.HttpRequest} we will attempt to revalidate + * the entry with the origin. Build the origin {@link ch.boye.httpclientandroidlib.HttpRequest} + * here and return it. + * + * @param request the original request from the caller + * @param cacheEntry the entry that needs to be re-validated + * @return the wrapped request + * @throws ProtocolException when I am unable to build a new origin request. + */ + public HttpRequestWrapper buildConditionalRequest(final HttpRequestWrapper request, final HttpCacheEntry cacheEntry) + throws ProtocolException { + final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal()); + newRequest.setHeaders(request.getAllHeaders()); + final Header eTag = cacheEntry.getFirstHeader(HeaderConstants.ETAG); + if (eTag != null) { + newRequest.setHeader(HeaderConstants.IF_NONE_MATCH, eTag.getValue()); + } + final Header lastModified = cacheEntry.getFirstHeader(HeaderConstants.LAST_MODIFIED); + if (lastModified != null) { + newRequest.setHeader(HeaderConstants.IF_MODIFIED_SINCE, lastModified.getValue()); + } + boolean mustRevalidate = false; + for(final Header h : cacheEntry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE.equalsIgnoreCase(elt.getName()) + || HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE.equalsIgnoreCase(elt.getName())) { + mustRevalidate = true; + break; + } + } + } + if (mustRevalidate) { + newRequest.addHeader(HeaderConstants.CACHE_CONTROL, HeaderConstants.CACHE_CONTROL_MAX_AGE + "=0"); + } + return newRequest; + + } + + /** + * When a {@link HttpCacheEntry} does not exist for a specific + * {@link ch.boye.httpclientandroidlib.HttpRequest} we attempt to see if an existing + * {@link HttpCacheEntry} is appropriate by building a conditional + * {@link ch.boye.httpclientandroidlib.HttpRequest} using the variants' ETag values. + * If no such values exist, the request is unmodified + * + * @param request the original request from the caller + * @param variants + * @return the wrapped request + */ + public HttpRequestWrapper buildConditionalRequestFromVariants(final HttpRequestWrapper request, + final Map<String, Variant> variants) { + final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal()); + newRequest.setHeaders(request.getAllHeaders()); + + // we do not support partial content so all etags are used + final StringBuilder etags = new StringBuilder(); + boolean first = true; + for(final String etag : variants.keySet()) { + if (!first) { + etags.append(","); + } + first = false; + etags.append(etag); + } + + newRequest.setHeader(HeaderConstants.IF_NONE_MATCH, etags.toString()); + return newRequest; + } + + /** + * Returns a request to unconditionally validate a cache entry with + * the origin. In certain cases (due to multiple intervening caches) + * our cache may actually receive a response to a normal conditional + * validation where the Date header is actually older than that of + * our current cache entry. In this case, the protocol recommendation + * is to retry the validation and force syncup with the origin. + * @param request client request we are trying to satisfy + * @param entry existing cache entry we are trying to validate + * @return an unconditional validation request + */ + public HttpRequestWrapper buildUnconditionalRequest(final HttpRequestWrapper request, final HttpCacheEntry entry) { + final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal()); + newRequest.setHeaders(request.getAllHeaders()); + newRequest.addHeader(HeaderConstants.CACHE_CONTROL,HeaderConstants.CACHE_CONTROL_NO_CACHE); + newRequest.addHeader(HeaderConstants.PRAGMA,HeaderConstants.CACHE_CONTROL_NO_CACHE); + newRequest.removeHeaders(HeaderConstants.IF_RANGE); + newRequest.removeHeaders(HeaderConstants.IF_MATCH); + newRequest.removeHeaders(HeaderConstants.IF_NONE_MATCH); + newRequest.removeHeaders(HeaderConstants.IF_UNMODIFIED_SINCE); + newRequest.removeHeaders(HeaderConstants.IF_MODIFIED_SINCE); + return newRequest; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java new file mode 100644 index 000000000..3646c3c32 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java @@ -0,0 +1,143 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Implements a bounded failure cache. The oldest entries are discarded when + * the maximum size is exceeded. + * + * @since 4.3 + */ +@ThreadSafe +public class DefaultFailureCache implements FailureCache { + + static final int DEFAULT_MAX_SIZE = 1000; + static final int MAX_UPDATE_TRIES = 10; + + private final int maxSize; + private final ConcurrentMap<String, FailureCacheValue> storage; + + /** + * Create a new failure cache with the maximum size of + * {@link #DEFAULT_MAX_SIZE}. + */ + public DefaultFailureCache() { + this(DEFAULT_MAX_SIZE); + } + + /** + * Creates a new failure cache with the specified maximum size. + * @param maxSize the maximum number of entries the cache should store + */ + public DefaultFailureCache(final int maxSize) { + this.maxSize = maxSize; + this.storage = new ConcurrentHashMap<String, FailureCacheValue>(); + } + + public int getErrorCount(final String identifier) { + if (identifier == null) { + throw new IllegalArgumentException("identifier may not be null"); + } + final FailureCacheValue storedErrorCode = storage.get(identifier); + return storedErrorCode != null ? storedErrorCode.getErrorCount() : 0; + } + + public void resetErrorCount(final String identifier) { + if (identifier == null) { + throw new IllegalArgumentException("identifier may not be null"); + } + storage.remove(identifier); + } + + public void increaseErrorCount(final String identifier) { + if (identifier == null) { + throw new IllegalArgumentException("identifier may not be null"); + } + updateValue(identifier); + removeOldestEntryIfMapSizeExceeded(); + } + + private void updateValue(final String identifier) { + /** + * Due to concurrency it is possible that someone else is modifying an + * entry before we could write back our updated value. So we keep + * trying until it is our turn. + * + * In case there is a lot of contention on that identifier, a thread + * might starve. Thus it gives up after a certain number of failed + * update tries. + */ + for (int i = 0; i < MAX_UPDATE_TRIES; i++) { + final FailureCacheValue oldValue = storage.get(identifier); + if (oldValue == null) { + final FailureCacheValue newValue = new FailureCacheValue(identifier, 1); + if (storage.putIfAbsent(identifier, newValue) == null) { + return; + } + } + else { + final int errorCount = oldValue.getErrorCount(); + if (errorCount == Integer.MAX_VALUE) { + return; + } + final FailureCacheValue newValue = new FailureCacheValue(identifier, errorCount + 1); + if (storage.replace(identifier, oldValue, newValue)) { + return; + } + } + } + } + + private void removeOldestEntryIfMapSizeExceeded() { + if (storage.size() > maxSize) { + final FailureCacheValue valueWithOldestTimestamp = findValueWithOldestTimestamp(); + if (valueWithOldestTimestamp != null) { + storage.remove(valueWithOldestTimestamp.getKey(), valueWithOldestTimestamp); + } + } + } + + private FailureCacheValue findValueWithOldestTimestamp() { + long oldestTimestamp = Long.MAX_VALUE; + FailureCacheValue oldestValue = null; + for (final Map.Entry<String, FailureCacheValue> storageEntry : storage.entrySet()) { + final FailureCacheValue value = storageEntry.getValue(); + final long creationTimeInNanos = value.getCreationTimeInNanos(); + if (creationTimeInNanos < oldestTimestamp) { + oldestTimestamp = creationTimeInNanos; + oldestValue = storageEntry.getValue(); + } + } + return oldestValue; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java new file mode 100644 index 000000000..0d78a2eb1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntrySerializationException; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntrySerializer; + +/** + * {@link HttpCacheEntrySerializer} implementation that uses the default (native) + * serialization. + * + * @see java.io.Serializable + * + * @since 4.1 + */ +@Immutable +public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer { + + public void writeTo(final HttpCacheEntry cacheEntry, final OutputStream os) throws IOException { + final ObjectOutputStream oos = new ObjectOutputStream(os); + try { + oos.writeObject(cacheEntry); + } finally { + oos.close(); + } + } + + public HttpCacheEntry readFrom(final InputStream is) throws IOException { + final ObjectInputStream ois = new ObjectInputStream(is); + try { + return (HttpCacheEntry) ois.readObject(); + } catch (final ClassNotFoundException ex) { + throw new HttpCacheEntrySerializationException("Class not found: " + ex.getMessage(), ex); + } finally { + ois.close(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java new file mode 100644 index 000000000..286205583 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java @@ -0,0 +1,174 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * An implementation that backs off exponentially based on the number of + * consecutive failed attempts stored in the + * {@link AsynchronousValidationRequest}. It uses the following defaults: + * <pre> + * no delay in case it was never tried or didn't fail so far + * 6 secs delay for one failed attempt (= {@link #getInitialExpiryInMillis()}) + * 60 secs delay for two failed attempts + * 10 mins delay for three failed attempts + * 100 mins delay for four failed attempts + * ~16 hours delay for five failed attempts + * 24 hours delay for six or more failed attempts (= {@link #getMaxExpiryInMillis()}) + * </pre> + * + * The following equation is used to calculate the delay for a specific revalidation request: + * <pre> + * delay = {@link #getInitialExpiryInMillis()} * Math.pow({@link #getBackOffRate()}, {@link AsynchronousValidationRequest#getConsecutiveFailedAttempts()} - 1)) + * </pre> + * The resulting delay won't exceed {@link #getMaxExpiryInMillis()}. + * + * @since 4.3 + */ +@ThreadSafe +public class ExponentialBackOffSchedulingStrategy implements SchedulingStrategy { + + public static final long DEFAULT_BACK_OFF_RATE = 10; + public static final long DEFAULT_INITIAL_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(6); + public static final long DEFAULT_MAX_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(86400); + + private final long backOffRate; + private final long initialExpiryInMillis; + private final long maxExpiryInMillis; + + private final ScheduledExecutorService executor; + + /** + * Create a new scheduling strategy using a fixed pool of worker threads. + * @param cacheConfig the thread pool configuration to be used; not <code>null</code> + * @see ch.boye.httpclientandroidlib.impl.client.cache.CacheConfig#getAsynchronousWorkersMax() + * @see #DEFAULT_BACK_OFF_RATE + * @see #DEFAULT_INITIAL_EXPIRY_IN_MILLIS + * @see #DEFAULT_MAX_EXPIRY_IN_MILLIS + */ + public ExponentialBackOffSchedulingStrategy(final CacheConfig cacheConfig) { + this(cacheConfig, + DEFAULT_BACK_OFF_RATE, + DEFAULT_INITIAL_EXPIRY_IN_MILLIS, + DEFAULT_MAX_EXPIRY_IN_MILLIS); + } + + /** + * Create a new scheduling strategy by using a fixed pool of worker threads and the + * given parameters to calculated the delay. + * + * @param cacheConfig the thread pool configuration to be used; not <code>null</code> + * @param backOffRate the back off rate to be used; not negative + * @param initialExpiryInMillis the initial expiry in milli seconds; not negative + * @param maxExpiryInMillis the upper limit of the delay in milli seconds; not negative + * @see ch.boye.httpclientandroidlib.impl.client.cache.CacheConfig#getAsynchronousWorkersMax() + * @see ExponentialBackOffSchedulingStrategy + */ + public ExponentialBackOffSchedulingStrategy( + final CacheConfig cacheConfig, + final long backOffRate, + final long initialExpiryInMillis, + final long maxExpiryInMillis) { + this(createThreadPoolFromCacheConfig(cacheConfig), + backOffRate, + initialExpiryInMillis, + maxExpiryInMillis); + } + + private static ScheduledThreadPoolExecutor createThreadPoolFromCacheConfig( + final CacheConfig cacheConfig) { + final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor( + cacheConfig.getAsynchronousWorkersMax()); + scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + return scheduledThreadPoolExecutor; + } + + ExponentialBackOffSchedulingStrategy( + final ScheduledExecutorService executor, + final long backOffRate, + final long initialExpiryInMillis, + final long maxExpiryInMillis) { + this.executor = checkNotNull("executor", executor); + this.backOffRate = checkNotNegative("backOffRate", backOffRate); + this.initialExpiryInMillis = checkNotNegative("initialExpiryInMillis", initialExpiryInMillis); + this.maxExpiryInMillis = checkNotNegative("maxExpiryInMillis", maxExpiryInMillis); + } + + public void schedule( + final AsynchronousValidationRequest revalidationRequest) { + checkNotNull("revalidationRequest", revalidationRequest); + final int consecutiveFailedAttempts = revalidationRequest.getConsecutiveFailedAttempts(); + final long delayInMillis = calculateDelayInMillis(consecutiveFailedAttempts); + executor.schedule(revalidationRequest, delayInMillis, TimeUnit.MILLISECONDS); + } + + public void close() { + executor.shutdown(); + } + + public long getBackOffRate() { + return backOffRate; + } + + public long getInitialExpiryInMillis() { + return initialExpiryInMillis; + } + + public long getMaxExpiryInMillis() { + return maxExpiryInMillis; + } + + protected long calculateDelayInMillis(final int consecutiveFailedAttempts) { + if (consecutiveFailedAttempts > 0) { + final long delayInSeconds = (long) (initialExpiryInMillis * + Math.pow(backOffRate, consecutiveFailedAttempts - 1)); + return Math.min(delayInSeconds, maxExpiryInMillis); + } + else { + return 0; + } + } + + protected static <T> T checkNotNull(final String parameterName, final T value) { + if (value == null) { + throw new IllegalArgumentException(parameterName + " may not be null"); + } + return value; + } + + protected static long checkNotNegative(final String parameterName, final long value) { + if (value < 0) { + throw new IllegalArgumentException(parameterName + " may not be negative"); + } + return value; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java new file mode 100644 index 000000000..a30fc54d9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +/** + * Increase and reset the number of errors associated with a specific + * identifier. + * + * @since 4.3 + */ +public interface FailureCache { + + /** + * Get the current error count. + * @param identifier the identifier for which the error count is requested + * @return the currently known error count or zero if there is no record + */ + int getErrorCount(String identifier); + + /** + * Reset the error count back to zero. + * @param identifier the identifier for which the error count should be + * reset + */ + void resetErrorCount(String identifier); + + /** + * Increases the error count by one. + * @param identifier the identifier for which the error count should be + * increased + */ + void increaseErrorCount(String identifier); +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java new file mode 100644 index 000000000..48bbf55fc --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * The error count with a creation timestamp and its associated key. + * + * @since 4.3 + */ +@Immutable +public class FailureCacheValue { + + private final long creationTimeInNanos; + private final String key; + private final int errorCount; + + public FailureCacheValue(final String key, final int errorCount) { + this.creationTimeInNanos = System.nanoTime(); + this.key = key; + this.errorCount = errorCount; + } + + public long getCreationTimeInNanos() { + return creationTimeInNanos; + } + + public String getKey() + { + return key; + } + + public int getErrorCount() { + return errorCount; + } + + @Override + public String toString() { + return "[entry creationTimeInNanos=" + creationTimeInNanos + "; " + + "key=" + key + "; errorCount=" + errorCount + ']'; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java new file mode 100644 index 000000000..934ecd13d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java @@ -0,0 +1,77 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.Resource; + +/** + * Cache resource backed by a file. + * + * @since 4.1 + */ +@ThreadSafe +public class FileResource implements Resource { + + private static final long serialVersionUID = 4132244415919043397L; + + private final File file; + + private volatile boolean disposed; + + public FileResource(final File file) { + super(); + this.file = file; + this.disposed = false; + } + + synchronized File getFile() { + return this.file; + } + + public synchronized InputStream getInputStream() throws IOException { + return new FileInputStream(this.file); + } + + public synchronized long length() { + return this.file.length(); + } + + public synchronized void dispose() { + if (this.disposed) { + return; + } + this.disposed = true; + this.file.delete(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java new file mode 100644 index 000000000..8522c54b5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java @@ -0,0 +1,111 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.InputLimit; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; + +/** + * Generates {@link Resource} instances whose body is stored in a temporary file. + * + * @since 4.1 + */ +@Immutable +public class FileResourceFactory implements ResourceFactory { + + private final File cacheDir; + private final BasicIdGenerator idgen; + + public FileResourceFactory(final File cacheDir) { + super(); + this.cacheDir = cacheDir; + this.idgen = new BasicIdGenerator(); + } + + private File generateUniqueCacheFile(final String requestId) { + final StringBuilder buffer = new StringBuilder(); + this.idgen.generate(buffer); + buffer.append('.'); + final int len = Math.min(requestId.length(), 100); + for (int i = 0; i < len; i++) { + final char ch = requestId.charAt(i); + if (Character.isLetterOrDigit(ch) || ch == '.') { + buffer.append(ch); + } else { + buffer.append('-'); + } + } + return new File(this.cacheDir, buffer.toString()); + } + + public Resource generate( + final String requestId, + final InputStream instream, + final InputLimit limit) throws IOException { + final File file = generateUniqueCacheFile(requestId); + final FileOutputStream outstream = new FileOutputStream(file); + try { + final byte[] buf = new byte[2048]; + long total = 0; + int l; + while ((l = instream.read(buf)) != -1) { + outstream.write(buf, 0, l); + total += l; + if (limit != null && total > limit.getValue()) { + limit.reached(); + break; + } + } + } finally { + outstream.close(); + } + return new FileResource(file); + } + + public Resource copy( + final String requestId, + final Resource resource) throws IOException { + final File file = generateUniqueCacheFile(requestId); + + if (resource instanceof FileResource) { + final File src = ((FileResource) resource).getFile(); + IOUtils.copyFile(src, file); + } else { + final FileOutputStream out = new FileOutputStream(file); + IOUtils.copyAndClose(resource.getInputStream(), out); + } + return new FileResource(file); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java new file mode 100644 index 000000000..e4b353a3c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.Resource; + +/** + * Cache resource backed by a byte array on the heap. + * + * @since 4.1 + */ +@Immutable +public class HeapResource implements Resource { + + private static final long serialVersionUID = -2078599905620463394L; + + private final byte[] b; + + public HeapResource(final byte[] b) { + super(); + this.b = b; + } + + byte[] getByteArray() { + return this.b; + } + + public InputStream getInputStream() { + return new ByteArrayInputStream(this.b); + } + + public long length() { + return this.b.length; + } + + public void dispose() { + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java new file mode 100644 index 000000000..f45cc0965 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java @@ -0,0 +1,83 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.InputLimit; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; + +/** + * Generates {@link Resource} instances stored entirely in heap. + * + * @since 4.1 + */ +@Immutable +public class HeapResourceFactory implements ResourceFactory { + + public Resource generate( + final String requestId, + final InputStream instream, + final InputLimit limit) throws IOException { + final ByteArrayOutputStream outstream = new ByteArrayOutputStream(); + final byte[] buf = new byte[2048]; + long total = 0; + int l; + while ((l = instream.read(buf)) != -1) { + outstream.write(buf, 0, l); + total += l; + if (limit != null && total > limit.getValue()) { + limit.reached(); + break; + } + } + return createResource(outstream.toByteArray()); + } + + public Resource copy( + final String requestId, + final Resource resource) throws IOException { + byte[] body; + if (resource instanceof HeapResource) { + body = ((HeapResource) resource).getByteArray(); + } else { + final ByteArrayOutputStream outstream = new ByteArrayOutputStream(); + IOUtils.copyAndClose(resource.getInputStream(), outstream); + body = outstream.toByteArray(); + } + return createResource(body); + } + + Resource createResource(final byte[] buf) { + return new HeapResource(buf); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java new file mode 100644 index 000000000..48037b5f5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java @@ -0,0 +1,166 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; + +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; + +/** + * @since 4.1 + */ +interface HttpCache { + + /** + * Clear all matching {@link HttpCacheEntry}s. + * @param host + * @param request + * @throws IOException + */ + void flushCacheEntriesFor(HttpHost host, HttpRequest request) + throws IOException; + + /** + * Clear invalidated matching {@link HttpCacheEntry}s + * @param host + * @param request + * @throws IOException + */ + void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request) + throws IOException; + + /** Clear any entries that may be invalidated by the given response to + * a particular request. + * @param host + * @param request + * @param response + */ + void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request, + HttpResponse response); + + /** + * Retrieve matching {@link HttpCacheEntry} from the cache if it exists + * @param host + * @param request + * @return the matching {@link HttpCacheEntry} or {@code null} + * @throws IOException + */ + HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request) + throws IOException; + + /** + * Retrieve all variants from the cache, if there are no variants then an empty + * {@link Map} is returned + * @param host + * @param request + * @return a <code>Map</code> mapping Etags to variant cache entries + * @throws IOException + */ + Map<String,Variant> getVariantCacheEntriesWithEtags(HttpHost host, HttpRequest request) + throws IOException; + + /** + * Store a {@link HttpResponse} in the cache if possible, and return + * @param host + * @param request + * @param originResponse + * @param requestSent + * @param responseReceived + * @return the {@link HttpResponse} + * @throws IOException + */ + HttpResponse cacheAndReturnResponse( + HttpHost host, HttpRequest request, HttpResponse originResponse, + Date requestSent, Date responseReceived) + throws IOException; + + /** + * Store a {@link HttpResponse} in the cache if possible, and return + * @param host + * @param request + * @param originResponse + * @param requestSent + * @param responseReceived + * @return the {@link HttpResponse} + * @throws IOException + */ + CloseableHttpResponse cacheAndReturnResponse(HttpHost host, + HttpRequest request, CloseableHttpResponse originResponse, + Date requestSent, Date responseReceived) + throws IOException; + + /** + * Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}. + * @param target + * @param request + * @param stale + * @param originResponse + * @param requestSent + * @param responseReceived + * @return the updated {@link HttpCacheEntry} + * @throws IOException + */ + HttpCacheEntry updateCacheEntry( + HttpHost target, HttpRequest request, HttpCacheEntry stale, HttpResponse originResponse, + Date requestSent, Date responseReceived) + throws IOException; + + /** + * Update a specific {@link HttpCacheEntry} representing a cached variant + * using a 304 {@link HttpResponse}. + * @param target host for client request + * @param request actual request from upstream client + * @param stale current variant cache entry + * @param originResponse 304 response received from origin + * @param requestSent when the validating request was sent + * @param responseReceived when the validating response was received + * @param cacheKey where in the cache this entry is currently stored + * @return the updated {@link HttpCacheEntry} + * @throws IOException + */ + HttpCacheEntry updateVariantCacheEntry(HttpHost target, HttpRequest request, + HttpCacheEntry stale, HttpResponse originResponse, Date requestSent, + Date responseReceived, String cacheKey) + throws IOException; + + /** + * Specifies cache should reuse the given cached variant to satisfy + * requests whose varying headers match those of the given client request. + * @param target host of the upstream client request + * @param req request sent by upstream client + * @param variant variant cache entry to reuse + * @throws IOException may be thrown during cache update + */ + void reuseVariantEntryFor(HttpHost target, final HttpRequest req, + final Variant variant) throws IOException; +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java new file mode 100644 index 000000000..aaf41b2c9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java @@ -0,0 +1,109 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.annotation.Immutable; + +@Immutable +class IOUtils { + + static void consume(final HttpEntity entity) throws IOException { + if (entity == null) { + return; + } + if (entity.isStreaming()) { + final InputStream instream = entity.getContent(); + if (instream != null) { + instream.close(); + } + } + } + + static void copy(final InputStream in, final OutputStream out) throws IOException { + final byte[] buf = new byte[2048]; + int len; + while ((len = in.read(buf)) != -1) { + out.write(buf, 0, len); + } + } + + static void closeSilently(final Closeable closable) { + try { + closable.close(); + } catch (final IOException ignore) { + } + } + + static void copyAndClose(final InputStream in, final OutputStream out) throws IOException { + try { + copy(in, out); + in.close(); + out.close(); + } catch (final IOException ex) { + closeSilently(in); + closeSilently(out); + // Propagate the original exception + throw ex; + } + } + + static void copyFile(final File in, final File out) throws IOException { + final RandomAccessFile f1 = new RandomAccessFile(in, "r"); + final RandomAccessFile f2 = new RandomAccessFile(out, "rw"); + try { + final FileChannel c1 = f1.getChannel(); + final FileChannel c2 = f2.getChannel(); + try { + c1.transferTo(0, f1.length(), c2); + c1.close(); + c2.close(); + } catch (final IOException ex) { + closeSilently(c1); + closeSilently(c2); + // Propagate the original exception + throw ex; + } + f1.close(); + f2.close(); + } catch (final IOException ex) { + closeSilently(f1); + closeSilently(f2); + // Propagate the original exception + throw ex; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java new file mode 100644 index 000000000..71d633825 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Immediately schedules any incoming validation request. Relies on + * {@link CacheConfig} to configure the used {@link java.util.concurrent.ThreadPoolExecutor}. + * + * @since 4.3 + */ +@ThreadSafe +public class ImmediateSchedulingStrategy implements SchedulingStrategy { + + private final ExecutorService executor; + + /** + * Uses a {@link java.util.concurrent.ThreadPoolExecutor} which is configured according to the + * given {@link CacheConfig}. + * @param cacheConfig specifies thread pool settings. See + * {@link CacheConfig#getAsynchronousWorkersMax()}, + * {@link CacheConfig#getAsynchronousWorkersCore()}, + * {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()}, + * and {@link CacheConfig#getRevalidationQueueSize()}. + */ + public ImmediateSchedulingStrategy(final CacheConfig cacheConfig) { + this(new ThreadPoolExecutor( + cacheConfig.getAsynchronousWorkersCore(), + cacheConfig.getAsynchronousWorkersMax(), + cacheConfig.getAsynchronousWorkerIdleLifetimeSecs(), + TimeUnit.SECONDS, + new ArrayBlockingQueue<Runnable>(cacheConfig.getRevalidationQueueSize())) + ); + } + + ImmediateSchedulingStrategy(final ExecutorService executor) { + this.executor = executor; + } + + public void schedule(final AsynchronousValidationRequest revalidationRequest) { + if (revalidationRequest == null) { + throw new IllegalArgumentException("AsynchronousValidationRequest may not be null"); + } + + executor.execute(revalidationRequest); + } + + public void close() { + executor.shutdown(); + } + + /** + * Visible for testing. + */ + void awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { + executor.awaitTermination(timeout, unit); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java new file mode 100644 index 000000000..3a2589cc3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java @@ -0,0 +1,163 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * {@link HttpCacheStorage} implementation capable of deallocating resources associated with + * the cache entries. This cache keeps track of cache entries using + * {@link java.lang.ref.PhantomReference} and maintains a collection of all resources that + * are no longer in use. The cache, however, does not automatically deallocates associated + * resources by invoking {@link Resource#dispose()} method. The consumer MUST periodically + * call {@link #cleanResources()} method to trigger resource deallocation. The cache can be + * permanently shut down using {@link #shutdown()} method. All resources associated with + * the entries used by the cache will be deallocated. + * + * This {@link HttpCacheStorage} implementation is intended for use with {@link FileResource} + * and similar. + * + * @since 4.1 + */ +@ThreadSafe +public class ManagedHttpCacheStorage implements HttpCacheStorage, Closeable { + + private final CacheMap entries; + private final ReferenceQueue<HttpCacheEntry> morque; + private final Set<ResourceReference> resources; + private final AtomicBoolean active; + + public ManagedHttpCacheStorage(final CacheConfig config) { + super(); + this.entries = new CacheMap(config.getMaxCacheEntries()); + this.morque = new ReferenceQueue<HttpCacheEntry>(); + this.resources = new HashSet<ResourceReference>(); + this.active = new AtomicBoolean(true); + } + + private void ensureValidState() throws IllegalStateException { + if (!this.active.get()) { + throw new IllegalStateException("Cache has been shut down"); + } + } + + private void keepResourceReference(final HttpCacheEntry entry) { + final Resource resource = entry.getResource(); + if (resource != null) { + // Must deallocate the resource when the entry is no longer in used + final ResourceReference ref = new ResourceReference(entry, this.morque); + this.resources.add(ref); + } + } + + public void putEntry(final String url, final HttpCacheEntry entry) throws IOException { + Args.notNull(url, "URL"); + Args.notNull(entry, "Cache entry"); + ensureValidState(); + synchronized (this) { + this.entries.put(url, entry); + keepResourceReference(entry); + } + } + + public HttpCacheEntry getEntry(final String url) throws IOException { + Args.notNull(url, "URL"); + ensureValidState(); + synchronized (this) { + return this.entries.get(url); + } + } + + public void removeEntry(final String url) throws IOException { + Args.notNull(url, "URL"); + ensureValidState(); + synchronized (this) { + // Cannot deallocate the associated resources immediately as the + // cache entry may still be in use + this.entries.remove(url); + } + } + + public void updateEntry( + final String url, + final HttpCacheUpdateCallback callback) throws IOException { + Args.notNull(url, "URL"); + Args.notNull(callback, "Callback"); + ensureValidState(); + synchronized (this) { + final HttpCacheEntry existing = this.entries.get(url); + final HttpCacheEntry updated = callback.update(existing); + this.entries.put(url, updated); + if (existing != updated) { + keepResourceReference(updated); + } + } + } + + public void cleanResources() { + if (this.active.get()) { + ResourceReference ref; + while ((ref = (ResourceReference) this.morque.poll()) != null) { + synchronized (this) { + this.resources.remove(ref); + } + ref.getResource().dispose(); + } + } + } + + public void shutdown() { + if (this.active.compareAndSet(true, false)) { + synchronized (this) { + this.entries.clear(); + for (final ResourceReference ref: this.resources) { + ref.getResource().dispose(); + } + this.resources.clear(); + while (this.morque.poll() != null) { + } + } + } + } + + public void close() { + shutdown(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java new file mode 100644 index 000000000..c2e07b52d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java @@ -0,0 +1,182 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Locale; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderIterator; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.message.AbstractHttpMessage; +import ch.boye.httpclientandroidlib.message.BasicStatusLine; +import ch.boye.httpclientandroidlib.params.BasicHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParams; + +/** + * @since 4.1 + */ +@SuppressWarnings("deprecation") +@Immutable +final class OptionsHttp11Response extends AbstractHttpMessage implements HttpResponse { + + private final StatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_NOT_IMPLEMENTED, ""); + private final ProtocolVersion version = HttpVersion.HTTP_1_1; + + public StatusLine getStatusLine() { + return statusLine; + } + + public void setStatusLine(final StatusLine statusline) { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setStatusLine(final ProtocolVersion ver, final int code) { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setStatusLine(final ProtocolVersion ver, final int code, final String reason) { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setStatusCode(final int code) throws IllegalStateException { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setReasonPhrase(final String reason) throws IllegalStateException { + // No-op on purpose, this class is not going to be doing any work. + } + + public HttpEntity getEntity() { + return null; + } + + public void setEntity(final HttpEntity entity) { + // No-op on purpose, this class is not going to be doing any work. + } + + public Locale getLocale() { + return null; + } + + public void setLocale(final Locale loc) { + // No-op on purpose, this class is not going to be doing any work. + } + + public ProtocolVersion getProtocolVersion() { + return version; + } + + @Override + public boolean containsHeader(final String name) { + return this.headergroup.containsHeader(name); + } + + @Override + public Header[] getHeaders(final String name) { + return this.headergroup.getHeaders(name); + } + + @Override + public Header getFirstHeader(final String name) { + return this.headergroup.getFirstHeader(name); + } + + @Override + public Header getLastHeader(final String name) { + return this.headergroup.getLastHeader(name); + } + + @Override + public Header[] getAllHeaders() { + return this.headergroup.getAllHeaders(); + } + + @Override + public void addHeader(final Header header) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void addHeader(final String name, final String value) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void setHeader(final Header header) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void setHeader(final String name, final String value) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void setHeaders(final Header[] headers) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void removeHeader(final Header header) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void removeHeaders(final String name) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public HeaderIterator headerIterator() { + return this.headergroup.iterator(); + } + + @Override + public HeaderIterator headerIterator(final String name) { + return this.headergroup.iterator(name); + } + + @Override + public HttpParams getParams() { + if (this.params == null) { + this.params = new BasicHttpParams(); + } + return this.params; + } + + @Override + public void setParams(final HttpParams params) { + // No-op on purpose, this class is not going to be doing any work. + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java new file mode 100644 index 000000000..4499d8288 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java @@ -0,0 +1,56 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.lang.reflect.Proxy; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Proxies for HTTP message objects. + * + * @since 4.3 + */ +@NotThreadSafe +class Proxies { + + public static CloseableHttpResponse enhanceResponse(final HttpResponse original) { + Args.notNull(original, "HTTP response"); + if (original instanceof CloseableHttpResponse) { + return (CloseableHttpResponse) original; + } else { + return (CloseableHttpResponse) Proxy.newProxyInstance( + ResponseProxyHandler.class.getClassLoader(), + new Class<?>[] { CloseableHttpResponse.class }, + new ResponseProxyHandler(original)); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java new file mode 100644 index 000000000..ac7ab439c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java @@ -0,0 +1,376 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity; +import ch.boye.httpclientandroidlib.entity.ContentType; +import ch.boye.httpclientandroidlib.message.BasicHeader; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.message.BasicStatusLine; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * @since 4.1 + */ +@Immutable +class RequestProtocolCompliance { + private final boolean weakETagOnPutDeleteAllowed; + + public RequestProtocolCompliance() { + super(); + this.weakETagOnPutDeleteAllowed = false; + } + + public RequestProtocolCompliance(final boolean weakETagOnPutDeleteAllowed) { + super(); + this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed; + } + + private static final List<String> disallowedWithNoCache = + Arrays.asList(HeaderConstants.CACHE_CONTROL_MIN_FRESH, HeaderConstants.CACHE_CONTROL_MAX_STALE, HeaderConstants.CACHE_CONTROL_MAX_AGE); + + /** + * Test to see if the {@link HttpRequest} is HTTP1.1 compliant or not + * and if not, we can not continue. + * + * @param request the HttpRequest Object + * @return list of {@link RequestProtocolError} + */ + public List<RequestProtocolError> requestIsFatallyNonCompliant(final HttpRequest request) { + final List<RequestProtocolError> theErrors = new ArrayList<RequestProtocolError>(); + + RequestProtocolError anError = requestHasWeakETagAndRange(request); + if (anError != null) { + theErrors.add(anError); + } + + if (!weakETagOnPutDeleteAllowed) { + anError = requestHasWeekETagForPUTOrDELETEIfMatch(request); + if (anError != null) { + theErrors.add(anError); + } + } + + anError = requestContainsNoCacheDirectiveWithFieldName(request); + if (anError != null) { + theErrors.add(anError); + } + + return theErrors; + } + + /** + * If the {@link HttpRequest} is non-compliant but 'fixable' we go ahead and + * fix the request here. + * + * @param request the request to check for compliance + * @throws ClientProtocolException when we have trouble making the request compliant + */ + public void makeRequestCompliant(final HttpRequestWrapper request) + throws ClientProtocolException { + + if (requestMustNotHaveEntity(request)) { + ((HttpEntityEnclosingRequest) request).setEntity(null); + } + + verifyRequestWithExpectContinueFlagHas100continueHeader(request); + verifyOPTIONSRequestWithBodyHasContentType(request); + decrementOPTIONSMaxForwardsIfGreaterThen0(request); + stripOtherFreshnessDirectivesWithNoCache(request); + + if (requestVersionIsTooLow(request) + || requestMinorVersionIsTooHighMajorVersionsMatch(request)) { + request.setProtocolVersion(HttpVersion.HTTP_1_1); + } + } + + private void stripOtherFreshnessDirectivesWithNoCache(final HttpRequest request) { + final List<HeaderElement> outElts = new ArrayList<HeaderElement>(); + boolean shouldStrip = false; + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (!disallowedWithNoCache.contains(elt.getName())) { + outElts.add(elt); + } + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) { + shouldStrip = true; + } + } + } + if (!shouldStrip) { + return; + } + request.removeHeaders(HeaderConstants.CACHE_CONTROL); + request.setHeader(HeaderConstants.CACHE_CONTROL, buildHeaderFromElements(outElts)); + } + + private String buildHeaderFromElements(final List<HeaderElement> outElts) { + final StringBuilder newHdr = new StringBuilder(""); + boolean first = true; + for(final HeaderElement elt : outElts) { + if (!first) { + newHdr.append(","); + } else { + first = false; + } + newHdr.append(elt.toString()); + } + return newHdr.toString(); + } + + private boolean requestMustNotHaveEntity(final HttpRequest request) { + return HeaderConstants.TRACE_METHOD.equals(request.getRequestLine().getMethod()) + && request instanceof HttpEntityEnclosingRequest; + } + + private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) { + if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) { + return; + } + + final Header maxForwards = request.getFirstHeader(HeaderConstants.MAX_FORWARDS); + if (maxForwards == null) { + return; + } + + request.removeHeaders(HeaderConstants.MAX_FORWARDS); + final int currentMaxForwards = Integer.parseInt(maxForwards.getValue()); + + request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1)); + } + + private void verifyOPTIONSRequestWithBodyHasContentType(final HttpRequest request) { + if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) { + return; + } + + if (!(request instanceof HttpEntityEnclosingRequest)) { + return; + } + + addContentTypeHeaderIfMissing((HttpEntityEnclosingRequest) request); + } + + private void addContentTypeHeaderIfMissing(final HttpEntityEnclosingRequest request) { + if (request.getEntity().getContentType() == null) { + ((AbstractHttpEntity) request.getEntity()).setContentType( + ContentType.APPLICATION_OCTET_STREAM.getMimeType()); + } + } + + private void verifyRequestWithExpectContinueFlagHas100continueHeader(final HttpRequest request) { + if (request instanceof HttpEntityEnclosingRequest) { + + if (((HttpEntityEnclosingRequest) request).expectContinue() + && ((HttpEntityEnclosingRequest) request).getEntity() != null) { + add100ContinueHeaderIfMissing(request); + } else { + remove100ContinueHeaderIfExists(request); + } + } else { + remove100ContinueHeaderIfExists(request); + } + } + + private void remove100ContinueHeaderIfExists(final HttpRequest request) { + boolean hasHeader = false; + + final Header[] expectHeaders = request.getHeaders(HTTP.EXPECT_DIRECTIVE); + List<HeaderElement> expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>(); + + for (final Header h : expectHeaders) { + for (final HeaderElement elt : h.getElements()) { + if (!(HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName()))) { + expectElementsThatAreNot100Continue.add(elt); + } else { + hasHeader = true; + } + } + + if (hasHeader) { + request.removeHeader(h); + for (final HeaderElement elt : expectElementsThatAreNot100Continue) { + final BasicHeader newHeader = new BasicHeader(HTTP.EXPECT_DIRECTIVE, elt.getName()); + request.addHeader(newHeader); + } + return; + } else { + expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>(); + } + } + } + + private void add100ContinueHeaderIfMissing(final HttpRequest request) { + boolean hasHeader = false; + + for (final Header h : request.getHeaders(HTTP.EXPECT_DIRECTIVE)) { + for (final HeaderElement elt : h.getElements()) { + if (HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName())) { + hasHeader = true; + } + } + } + + if (!hasHeader) { + request.addHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE); + } + } + + protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(final HttpRequest request) { + final ProtocolVersion requestProtocol = request.getProtocolVersion(); + if (requestProtocol.getMajor() != HttpVersion.HTTP_1_1.getMajor()) { + return false; + } + + if (requestProtocol.getMinor() > HttpVersion.HTTP_1_1.getMinor()) { + return true; + } + + return false; + } + + protected boolean requestVersionIsTooLow(final HttpRequest request) { + return request.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) < 0; + } + + /** + * Extract error information about the {@link HttpRequest} telling the 'caller' + * that a problem occured. + * + * @param errorCheck What type of error should I get + * @return The {@link HttpResponse} that is the error generated + */ + public HttpResponse getErrorForRequest(final RequestProtocolError errorCheck) { + switch (errorCheck) { + case BODY_BUT_NO_LENGTH_ERROR: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_LENGTH_REQUIRED, "")); + + case WEAK_ETAG_AND_RANGE_ERROR: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_BAD_REQUEST, "Weak eTag not compatible with byte range")); + + case WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_BAD_REQUEST, + "Weak eTag not compatible with PUT or DELETE requests")); + + case NO_CACHE_DIRECTIVE_WITH_FIELD_NAME: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_BAD_REQUEST, + "No-Cache directive MUST NOT include a field name")); + + default: + throw new IllegalStateException( + "The request was compliant, therefore no error can be generated for it."); + + } + } + + private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) { + // TODO: Should these be looking at all the headers marked as Range? + final String method = request.getRequestLine().getMethod(); + if (!(HeaderConstants.GET_METHOD.equals(method))) { + return null; + } + + final Header range = request.getFirstHeader(HeaderConstants.RANGE); + if (range == null) { + return null; + } + + final Header ifRange = request.getFirstHeader(HeaderConstants.IF_RANGE); + if (ifRange == null) { + return null; + } + + final String val = ifRange.getValue(); + if (val.startsWith("W/")) { + return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR; + } + + return null; + } + + private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(final HttpRequest request) { + // TODO: Should these be looking at all the headers marked as If-Match/If-None-Match? + + final String method = request.getRequestLine().getMethod(); + if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD + .equals(method))) { + return null; + } + + final Header ifMatch = request.getFirstHeader(HeaderConstants.IF_MATCH); + if (ifMatch != null) { + final String val = ifMatch.getValue(); + if (val.startsWith("W/")) { + return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR; + } + } else { + final Header ifNoneMatch = request.getFirstHeader(HeaderConstants.IF_NONE_MATCH); + if (ifNoneMatch == null) { + return null; + } + + final String val2 = ifNoneMatch.getValue(); + if (val2.startsWith("W/")) { + return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR; + } + } + + return null; + } + + private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(final HttpRequest request) { + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(elt.getName()) + && elt.getValue() != null) { + return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME; + } + } + } + return null; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java new file mode 100644 index 000000000..1cc5668c7 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java @@ -0,0 +1,40 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +/** + * @since 4.1 + */ +enum RequestProtocolError { + + UNKNOWN, + BODY_BUT_NO_LENGTH_ERROR, + WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR, + WEAK_ETAG_AND_RANGE_ERROR, + NO_CACHE_DIRECTIVE_WITH_FIELD_NAME + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java new file mode 100644 index 000000000..5e3f368f1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.util.Args; + +@Immutable +class ResourceReference extends PhantomReference<HttpCacheEntry> { + + private final Resource resource; + + public ResourceReference(final HttpCacheEntry entry, final ReferenceQueue<HttpCacheEntry> q) { + super(entry, q); + Args.notNull(entry.getResource(), "Resource"); + this.resource = entry.getResource(); + } + + public Resource getResource() { + return this.resource; + } + + @Override + public int hashCode() { + return this.resource.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return this.resource.equals(obj); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java new file mode 100644 index 000000000..abadb88d4 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java @@ -0,0 +1,311 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * Determines if an HttpResponse can be cached. + * + * @since 4.1 + */ +@Immutable +class ResponseCachingPolicy { + + private static final String[] AUTH_CACHEABLE_PARAMS = { + "s-maxage", HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE, HeaderConstants.PUBLIC + }; + private final long maxObjectSizeBytes; + private final boolean sharedCache; + private final boolean neverCache1_0ResponsesWithQueryString; + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + private static final Set<Integer> cacheableStatuses = + new HashSet<Integer>(Arrays.asList(HttpStatus.SC_OK, + HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, + HttpStatus.SC_MULTIPLE_CHOICES, + HttpStatus.SC_MOVED_PERMANENTLY, + HttpStatus.SC_GONE)); + private final Set<Integer> uncacheableStatuses; + /** + * Define a cache policy that limits the size of things that should be stored + * in the cache to a maximum of {@link HttpResponse} bytes in size. + * + * @param maxObjectSizeBytes the size to limit items into the cache + * @param sharedCache whether to behave as a shared cache (true) or a + * non-shared/private cache (false) + * @param neverCache1_0ResponsesWithQueryString true to never cache HTTP 1.0 responses with a query string, false + * to cache if explicit cache headers are found. + * @param allow303Caching if this policy is permitted to cache 303 response + */ + public ResponseCachingPolicy(final long maxObjectSizeBytes, + final boolean sharedCache, + final boolean neverCache1_0ResponsesWithQueryString, + final boolean allow303Caching) { + this.maxObjectSizeBytes = maxObjectSizeBytes; + this.sharedCache = sharedCache; + this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString; + if (allow303Caching) { + uncacheableStatuses = new HashSet<Integer>( + Arrays.asList(HttpStatus.SC_PARTIAL_CONTENT)); + } else { + uncacheableStatuses = new HashSet<Integer>(Arrays.asList( + HttpStatus.SC_PARTIAL_CONTENT, HttpStatus.SC_SEE_OTHER)); + } + } + + /** + * Determines if an HttpResponse can be cached. + * + * @param httpMethod What type of request was this, a GET, PUT, other? + * @param response The origin response + * @return <code>true</code> if response is cacheable + */ + public boolean isResponseCacheable(final String httpMethod, final HttpResponse response) { + boolean cacheable = false; + + if (!HeaderConstants.GET_METHOD.equals(httpMethod)) { + log.debug("Response was not cacheable."); + return false; + } + + final int status = response.getStatusLine().getStatusCode(); + if (cacheableStatuses.contains(status)) { + // these response codes MAY be cached + cacheable = true; + } else if (uncacheableStatuses.contains(status)) { + return false; + } else if (unknownStatusCode(status)) { + // a response with an unknown status code MUST NOT be + // cached + return false; + } + + final Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN); + if (contentLength != null) { + final int contentLengthValue = Integer.parseInt(contentLength.getValue()); + if (contentLengthValue > this.maxObjectSizeBytes) { + return false; + } + } + + final Header[] ageHeaders = response.getHeaders(HeaderConstants.AGE); + + if (ageHeaders.length > 1) { + return false; + } + + final Header[] expiresHeaders = response.getHeaders(HeaderConstants.EXPIRES); + + if (expiresHeaders.length > 1) { + return false; + } + + final Header[] dateHeaders = response.getHeaders(HTTP.DATE_HEADER); + + if (dateHeaders.length != 1) { + return false; + } + + final Date date = DateUtils.parseDate(dateHeaders[0].getValue()); + if (date == null) { + return false; + } + + for (final Header varyHdr : response.getHeaders(HeaderConstants.VARY)) { + for (final HeaderElement elem : varyHdr.getElements()) { + if ("*".equals(elem.getName())) { + return false; + } + } + } + + if (isExplicitlyNonCacheable(response)) { + return false; + } + + return (cacheable || isExplicitlyCacheable(response)); + } + + private boolean unknownStatusCode(final int status) { + if (status >= 100 && status <= 101) { + return false; + } + if (status >= 200 && status <= 206) { + return false; + } + if (status >= 300 && status <= 307) { + return false; + } + if (status >= 400 && status <= 417) { + return false; + } + if (status >= 500 && status <= 505) { + return false; + } + return true; + } + + protected boolean isExplicitlyNonCacheable(final HttpResponse response) { + final Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL); + for (final Header header : cacheControlHeaders) { + for (final HeaderElement elem : header.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elem.getName()) + || HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elem.getName()) + || (sharedCache && HeaderConstants.PRIVATE.equals(elem.getName()))) { + return true; + } + } + } + return false; + } + + protected boolean hasCacheControlParameterFrom(final HttpMessage msg, final String[] params) { + final Header[] cacheControlHeaders = msg.getHeaders(HeaderConstants.CACHE_CONTROL); + for (final Header header : cacheControlHeaders) { + for (final HeaderElement elem : header.getElements()) { + for (final String param : params) { + if (param.equalsIgnoreCase(elem.getName())) { + return true; + } + } + } + } + return false; + } + + protected boolean isExplicitlyCacheable(final HttpResponse response) { + if (response.getFirstHeader(HeaderConstants.EXPIRES) != null) { + return true; + } + final String[] cacheableParams = { HeaderConstants.CACHE_CONTROL_MAX_AGE, "s-maxage", + HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE, + HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE, + HeaderConstants.PUBLIC + }; + return hasCacheControlParameterFrom(response, cacheableParams); + } + + /** + * Determine if the {@link HttpResponse} gotten from the origin is a + * cacheable response. + * + * @param request the {@link HttpRequest} that generated an origin hit + * @param response the {@link HttpResponse} from the origin + * @return <code>true</code> if response is cacheable + */ + public boolean isResponseCacheable(final HttpRequest request, final HttpResponse response) { + if (requestProtocolGreaterThanAccepted(request)) { + log.debug("Response was not cacheable."); + return false; + } + + final String[] uncacheableRequestDirectives = { HeaderConstants.CACHE_CONTROL_NO_STORE }; + if (hasCacheControlParameterFrom(request,uncacheableRequestDirectives)) { + return false; + } + + if (request.getRequestLine().getUri().contains("?")) { + if (neverCache1_0ResponsesWithQueryString && from1_0Origin(response)) { + log.debug("Response was not cacheable as it had a query string."); + return false; + } else if (!isExplicitlyCacheable(response)) { + log.debug("Response was not cacheable as it is missing explicit caching headers."); + return false; + } + } + + if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(response)) { + return false; + } + + if (sharedCache) { + final Header[] authNHeaders = request.getHeaders(HeaderConstants.AUTHORIZATION); + if (authNHeaders != null && authNHeaders.length > 0 + && !hasCacheControlParameterFrom(response, AUTH_CACHEABLE_PARAMS)) { + return false; + } + } + + final String method = request.getRequestLine().getMethod(); + return isResponseCacheable(method, response); + } + + private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl( + final HttpResponse response) { + if (response.getFirstHeader(HeaderConstants.CACHE_CONTROL) != null) { + return false; + } + final Header expiresHdr = response.getFirstHeader(HeaderConstants.EXPIRES); + final Header dateHdr = response.getFirstHeader(HTTP.DATE_HEADER); + if (expiresHdr == null || dateHdr == null) { + return false; + } + final Date expires = DateUtils.parseDate(expiresHdr.getValue()); + final Date date = DateUtils.parseDate(dateHdr.getValue()); + if (expires == null || date == null) { + return false; + } + return expires.equals(date) || expires.before(date); + } + + private boolean from1_0Origin(final HttpResponse response) { + final Header via = response.getFirstHeader(HeaderConstants.VIA); + if (via != null) { + for(final HeaderElement elt : via.getElements()) { + final String proto = elt.toString().split("\\s")[0]; + if (proto.contains("/")) { + return proto.equals("HTTP/1.0"); + } else { + return proto.equals("1.0"); + } + } + } + return HttpVersion.HTTP_1_0.equals(response.getProtocolVersion()); + } + + private boolean requestProtocolGreaterThanAccepted(final HttpRequest req) { + return req.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) > 0; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java new file mode 100644 index 000000000..7f5ebb483 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java @@ -0,0 +1,251 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.message.BasicHeader; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * @since 4.1 + */ +@Immutable +class ResponseProtocolCompliance { + + private static final String UNEXPECTED_100_CONTINUE = "The incoming request did not contain a " + + "100-continue header, but the response was a Status 100, continue."; + private static final String UNEXPECTED_PARTIAL_CONTENT = "partial content was returned for a request that did not ask for it"; + + /** + * When we get a response from a down stream server (Origin Server) + * we attempt to see if it is HTTP 1.1 Compliant and if not, attempt to + * make it so. + * + * @param request The {@link HttpRequest} that generated an origin hit and response + * @param response The {@link HttpResponse} from the origin server + * @throws IOException Bad things happened + */ + public void ensureProtocolCompliance(final HttpRequestWrapper request, final HttpResponse response) + throws IOException { + if (backendResponseMustNotHaveBody(request, response)) { + consumeBody(response); + response.setEntity(null); + } + + requestDidNotExpect100ContinueButResponseIsOne(request, response); + + transferEncodingIsNotReturnedTo1_0Client(request, response); + + ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(request, response); + + ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response); + + ensure206ContainsDateHeader(response); + + ensure304DoesNotContainExtraEntityHeaders(response); + + identityIsNotUsedInContentEncoding(response); + + warningsWithNonMatchingWarnDatesAreRemoved(response); + } + + private void consumeBody(final HttpResponse response) throws IOException { + final HttpEntity body = response.getEntity(); + if (body != null) { + IOUtils.consume(body); + } + } + + private void warningsWithNonMatchingWarnDatesAreRemoved( + final HttpResponse response) { + final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER).getValue()); + if (responseDate == null) { + return; + } + + final Header[] warningHeaders = response.getHeaders(HeaderConstants.WARNING); + + if (warningHeaders == null || warningHeaders.length == 0) { + return; + } + + final List<Header> newWarningHeaders = new ArrayList<Header>(); + boolean modified = false; + for(final Header h : warningHeaders) { + for(final WarningValue wv : WarningValue.getWarningValues(h)) { + final Date warnDate = wv.getWarnDate(); + if (warnDate == null || warnDate.equals(responseDate)) { + newWarningHeaders.add(new BasicHeader(HeaderConstants.WARNING,wv.toString())); + } else { + modified = true; + } + } + } + if (modified) { + response.removeHeaders(HeaderConstants.WARNING); + for(final Header h : newWarningHeaders) { + response.addHeader(h); + } + } + } + + private void identityIsNotUsedInContentEncoding(final HttpResponse response) { + final Header[] hdrs = response.getHeaders(HTTP.CONTENT_ENCODING); + if (hdrs == null || hdrs.length == 0) { + return; + } + final List<Header> newHeaders = new ArrayList<Header>(); + boolean modified = false; + for (final Header h : hdrs) { + final StringBuilder buf = new StringBuilder(); + boolean first = true; + for (final HeaderElement elt : h.getElements()) { + if ("identity".equalsIgnoreCase(elt.getName())) { + modified = true; + } else { + if (!first) { + buf.append(","); + } + buf.append(elt.toString()); + first = false; + } + } + final String newHeaderValue = buf.toString(); + if (!"".equals(newHeaderValue)) { + newHeaders.add(new BasicHeader(HTTP.CONTENT_ENCODING, newHeaderValue)); + } + } + if (!modified) { + return; + } + response.removeHeaders(HTTP.CONTENT_ENCODING); + for (final Header h : newHeaders) { + response.addHeader(h); + } + } + + private void ensure206ContainsDateHeader(final HttpResponse response) { + if (response.getFirstHeader(HTTP.DATE_HEADER) == null) { + response.addHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date())); + } + + } + + private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(final HttpRequest request, + final HttpResponse response) throws IOException { + if (request.getFirstHeader(HeaderConstants.RANGE) != null + || response.getStatusLine().getStatusCode() != HttpStatus.SC_PARTIAL_CONTENT) { + return; + } + + consumeBody(response); + throw new ClientProtocolException(UNEXPECTED_PARTIAL_CONTENT); + } + + private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(final HttpRequest request, + final HttpResponse response) { + if (!request.getRequestLine().getMethod().equalsIgnoreCase(HeaderConstants.OPTIONS_METHOD)) { + return; + } + + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + return; + } + + if (response.getFirstHeader(HTTP.CONTENT_LEN) == null) { + response.addHeader(HTTP.CONTENT_LEN, "0"); + } + } + + private void ensure304DoesNotContainExtraEntityHeaders(final HttpResponse response) { + final String[] disallowedEntityHeaders = { HeaderConstants.ALLOW, HTTP.CONTENT_ENCODING, + "Content-Language", HTTP.CONTENT_LEN, "Content-MD5", + "Content-Range", HTTP.CONTENT_TYPE, HeaderConstants.LAST_MODIFIED + }; + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { + for(final String hdr : disallowedEntityHeaders) { + response.removeHeaders(hdr); + } + } + } + + private boolean backendResponseMustNotHaveBody(final HttpRequest request, final HttpResponse backendResponse) { + return HeaderConstants.HEAD_METHOD.equals(request.getRequestLine().getMethod()) + || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT + || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_RESET_CONTENT + || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED; + } + + private void requestDidNotExpect100ContinueButResponseIsOne(final HttpRequestWrapper request, + final HttpResponse response) throws IOException { + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) { + return; + } + + final HttpRequest originalRequest = request.getOriginal(); + if (originalRequest instanceof HttpEntityEnclosingRequest) { + if (((HttpEntityEnclosingRequest)originalRequest).expectContinue()) { + return; + } + } + consumeBody(response); + throw new ClientProtocolException(UNEXPECTED_100_CONTINUE); + } + + private void transferEncodingIsNotReturnedTo1_0Client(final HttpRequestWrapper request, + final HttpResponse response) { + final HttpRequest originalRequest = request.getOriginal(); + if (originalRequest.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) >= 0) { + return; + } + + removeResponseTransferEncoding(response); + } + + private void removeResponseTransferEncoding(final HttpResponse response) { + response.removeHeaders("TE"); + response.removeHeaders(HTTP.TRANSFER_ENCODING); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java new file mode 100644 index 000000000..a541b2778 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; + +/** + * A proxy class that can enhance an arbitrary {@link HttpResponse} with + * {@link Closeable#close()} method. + * + * @since 4.3 + */ +@NotThreadSafe +class ResponseProxyHandler implements InvocationHandler { + + private static final Method CLOSE_METHOD; + + static { + try { + CLOSE_METHOD = Closeable.class.getMethod("close"); + } catch (final NoSuchMethodException ex) { + throw new Error(ex); + } + } + + private final HttpResponse original; + + ResponseProxyHandler(final HttpResponse original) { + super(); + this.original = original; + } + + public void close() throws IOException { + IOUtils.consume(original.getEntity()); + } + + public Object invoke( + final Object proxy, final Method method, final Object[] args) throws Throwable { + if (method.equals(CLOSE_METHOD)) { + close(); + return null; + } else { + try { + return method.invoke(this.original, args); + } catch (final InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause != null) { + throw cause; + } else { + throw ex; + } + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java new file mode 100644 index 000000000..0940fbc83 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java @@ -0,0 +1,45 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; + +/** + * Specifies when revalidation requests are scheduled. + * + * @since 4.3 + */ +public interface SchedulingStrategy extends Closeable +{ + /** + * Schedule an {@link AsynchronousValidationRequest} to be executed. + * + * @param revalidationRequest the request to be executed; not <code>null</code> + * @throws java.util.concurrent.RejectedExecutionException if the request could not be scheduled for execution + */ + void schedule(AsynchronousValidationRequest revalidationRequest); +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java new file mode 100644 index 000000000..4c36ebb48 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java @@ -0,0 +1,150 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Proxy; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.InputLimit; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; + +/** + * @since 4.1 + */ +@NotThreadSafe +class SizeLimitedResponseReader { + + private final ResourceFactory resourceFactory; + private final long maxResponseSizeBytes; + private final HttpRequest request; + private final CloseableHttpResponse response; + + private InputStream instream; + private InputLimit limit; + private Resource resource; + private boolean consumed; + + /** + * Create an {@link HttpResponse} that is limited in size, this allows for checking + * the size of objects that will be stored in the cache. + */ + public SizeLimitedResponseReader( + final ResourceFactory resourceFactory, + final long maxResponseSizeBytes, + final HttpRequest request, + final CloseableHttpResponse response) { + super(); + this.resourceFactory = resourceFactory; + this.maxResponseSizeBytes = maxResponseSizeBytes; + this.request = request; + this.response = response; + } + + protected void readResponse() throws IOException { + if (!consumed) { + doConsume(); + } + } + + private void ensureNotConsumed() { + if (consumed) { + throw new IllegalStateException("Response has already been consumed"); + } + } + + private void ensureConsumed() { + if (!consumed) { + throw new IllegalStateException("Response has not been consumed"); + } + } + + private void doConsume() throws IOException { + ensureNotConsumed(); + consumed = true; + + limit = new InputLimit(maxResponseSizeBytes); + + final HttpEntity entity = response.getEntity(); + if (entity == null) { + return; + } + final String uri = request.getRequestLine().getUri(); + instream = entity.getContent(); + try { + resource = resourceFactory.generate(uri, instream, limit); + } finally { + if (!limit.isReached()) { + instream.close(); + } + } + } + + boolean isLimitReached() { + ensureConsumed(); + return limit.isReached(); + } + + Resource getResource() { + ensureConsumed(); + return resource; + } + + CloseableHttpResponse getReconstructedResponse() throws IOException { + ensureConsumed(); + final HttpResponse reconstructed = new BasicHttpResponse(response.getStatusLine()); + reconstructed.setHeaders(response.getAllHeaders()); + + final CombinedEntity combinedEntity = new CombinedEntity(resource, instream); + final HttpEntity entity = response.getEntity(); + if (entity != null) { + combinedEntity.setContentType(entity.getContentType()); + combinedEntity.setContentEncoding(entity.getContentEncoding()); + combinedEntity.setChunked(entity.isChunked()); + } + reconstructed.setEntity(combinedEntity); + return (CloseableHttpResponse) Proxy.newProxyInstance( + ResponseProxyHandler.class.getClassLoader(), + new Class<?>[] { CloseableHttpResponse.class }, + new ResponseProxyHandler(reconstructed) { + + @Override + public void close() throws IOException { + response.close(); + } + + }); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Variant.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Variant.java new file mode 100644 index 000000000..f631b0211 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Variant.java @@ -0,0 +1,55 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; + +/** Records a set of information describing a cached variant. */ +class Variant { + + private final String variantKey; + private final String cacheKey; + private final HttpCacheEntry entry; + + public Variant(final String variantKey, final String cacheKey, final HttpCacheEntry entry) { + this.variantKey = variantKey; + this.cacheKey = cacheKey; + this.entry = entry; + } + + public String getVariantKey() { + return variantKey; + } + + public String getCacheKey() { + return cacheKey; + } + + public HttpCacheEntry getEntry() { + return entry; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java new file mode 100644 index 000000000..a3c2ce67d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java @@ -0,0 +1,370 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; + +/** This class provides for parsing and understanding Warning headers. As + * the Warning header can be multi-valued, but the values can contain + * separators like commas inside quoted strings, we cannot use the regular + * {@link Header#getElements()} call to access the values. + */ +class WarningValue { + + private int offs; + private int init_offs; + private final String src; + private int warnCode; + private String warnAgent; + private String warnText; + private Date warnDate; + + WarningValue(final String s) { + this(s, 0); + } + + WarningValue(final String s, final int offs) { + this.offs = this.init_offs = offs; + this.src = s; + consumeWarnValue(); + } + + /** Returns an array of the parseable warning values contained + * in the given header value, which is assumed to be a + * Warning header. Improperly formatted warning values will be + * skipped, in keeping with the philosophy of "ignore what you + * cannot understand." + * @param h Warning {@link Header} to parse + * @return array of <code>WarnValue</code> objects + */ + public static WarningValue[] getWarningValues(final Header h) { + final List<WarningValue> out = new ArrayList<WarningValue>(); + final String src = h.getValue(); + int offs = 0; + while(offs < src.length()) { + try { + final WarningValue wv = new WarningValue(src, offs); + out.add(wv); + offs = wv.offs; + } catch (final IllegalArgumentException e) { + final int nextComma = src.indexOf(',', offs); + if (nextComma == -1) { + break; + } + offs = nextComma + 1; + } + } + final WarningValue[] wvs = {}; + return out.toArray(wvs); + } + + /* + * LWS = [CRLF] 1*( SP | HT ) + * CRLF = CR LF + */ + protected void consumeLinearWhitespace() { + while(offs < src.length()) { + switch(src.charAt(offs)) { + case '\r': + if (offs+2 >= src.length() + || src.charAt(offs+1) != '\n' + || (src.charAt(offs+2) != ' ' + && src.charAt(offs+2) != '\t')) { + return; + } + offs += 2; + break; + case ' ': + case '\t': + break; + default: + return; + } + offs++; + } + } + + /* + * CHAR = <any US-ASCII character (octets 0 - 127)> + */ + private boolean isChar(final char c) { + final int i = c; + return (i >= 0 && i <= 127); + } + + /* + * CTL = <any US-ASCII control character + (octets 0 - 31) and DEL (127)> + */ + private boolean isControl(final char c) { + final int i = c; + return (i == 127 || (i >=0 && i <= 31)); + } + + /* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ + private boolean isSeparator(final char c) { + return (c == '(' || c == ')' || c == '<' || c == '>' + || c == '@' || c == ',' || c == ';' || c == ':' + || c == '\\' || c == '\"' || c == '/' + || c == '[' || c == ']' || c == '?' || c == '=' + || c == '{' || c == '}' || c == ' ' || c == '\t'); + } + + /* + * token = 1*<any CHAR except CTLs or separators> + */ + protected void consumeToken() { + if (!isTokenChar(src.charAt(offs))) { + parseError(); + } + while(offs < src.length()) { + if (!isTokenChar(src.charAt(offs))) { + break; + } + offs++; + } + } + + private boolean isTokenChar(final char c) { + return (isChar(c) && !isControl(c) && !isSeparator(c)); + } + + private static final String TOPLABEL = "\\p{Alpha}([\\p{Alnum}-]*\\p{Alnum})?"; + private static final String DOMAINLABEL = "\\p{Alnum}([\\p{Alnum}-]*\\p{Alnum})?"; + private static final String HOSTNAME = "(" + DOMAINLABEL + "\\.)*" + TOPLABEL + "\\.?"; + private static final String IPV4ADDRESS = "\\d+\\.\\d+\\.\\d+\\.\\d+"; + private static final String HOST = "(" + HOSTNAME + ")|(" + IPV4ADDRESS + ")"; + private static final String PORT = "\\d*"; + private static final String HOSTPORT = "(" + HOST + ")(\\:" + PORT + ")?"; + private static final Pattern HOSTPORT_PATTERN = Pattern.compile(HOSTPORT); + + protected void consumeHostPort() { + final Matcher m = HOSTPORT_PATTERN.matcher(src.substring(offs)); + if (!m.find()) { + parseError(); + } + if (m.start() != 0) { + parseError(); + } + offs += m.end(); + } + + + /* + * warn-agent = ( host [ ":" port ] ) | pseudonym + * pseudonym = token + */ + protected void consumeWarnAgent() { + final int curr_offs = offs; + try { + consumeHostPort(); + warnAgent = src.substring(curr_offs, offs); + consumeCharacter(' '); + return; + } catch (final IllegalArgumentException e) { + offs = curr_offs; + } + consumeToken(); + warnAgent = src.substring(curr_offs, offs); + consumeCharacter(' '); + } + + /* + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = <any TEXT except <">> + */ + protected void consumeQuotedString() { + if (src.charAt(offs) != '\"') { + parseError(); + } + offs++; + boolean foundEnd = false; + while(offs < src.length() && !foundEnd) { + final char c = src.charAt(offs); + if (offs + 1 < src.length() && c == '\\' + && isChar(src.charAt(offs+1))) { + offs += 2; // consume quoted-pair + } else if (c == '\"') { + foundEnd = true; + offs++; + } else if (c != '\"' && !isControl(c)) { + offs++; + } else { + parseError(); + } + } + if (!foundEnd) { + parseError(); + } + } + + /* + * warn-text = quoted-string + */ + protected void consumeWarnText() { + final int curr = offs; + consumeQuotedString(); + warnText = src.substring(curr, offs); + } + + private static final String MONTH = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; + private static final String WEEKDAY = "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday"; + private static final String WKDAY = "Mon|Tue|Wed|Thu|Fri|Sat|Sun"; + private static final String TIME = "\\d{2}:\\d{2}:\\d{2}"; + private static final String DATE3 = "(" + MONTH + ") ( |\\d)\\d"; + private static final String DATE2 = "\\d{2}-(" + MONTH + ")-\\d{2}"; + private static final String DATE1 = "\\d{2} (" + MONTH + ") \\d{4}"; + private static final String ASCTIME_DATE = "(" + WKDAY + ") (" + DATE3 + ") (" + TIME + ") \\d{4}"; + private static final String RFC850_DATE = "(" + WEEKDAY + "), (" + DATE2 + ") (" + TIME + ") GMT"; + private static final String RFC1123_DATE = "(" + WKDAY + "), (" + DATE1 + ") (" + TIME + ") GMT"; + private static final String HTTP_DATE = "(" + RFC1123_DATE + ")|(" + RFC850_DATE + ")|(" + ASCTIME_DATE + ")"; + private static final String WARN_DATE = "\"(" + HTTP_DATE + ")\""; + private static final Pattern WARN_DATE_PATTERN = Pattern.compile(WARN_DATE); + + /* + * warn-date = <"> HTTP-date <"> + */ + protected void consumeWarnDate() { + final int curr = offs; + final Matcher m = WARN_DATE_PATTERN.matcher(src.substring(offs)); + if (!m.lookingAt()) { + parseError(); + } + offs += m.end(); + warnDate = DateUtils.parseDate(src.substring(curr+1,offs-1)); + } + + /* + * warning-value = warn-code SP warn-agent SP warn-text [SP warn-date] + */ + protected void consumeWarnValue() { + consumeLinearWhitespace(); + consumeWarnCode(); + consumeWarnAgent(); + consumeWarnText(); + if (offs + 1 < src.length() && src.charAt(offs) == ' ' && src.charAt(offs+1) == '\"') { + consumeCharacter(' '); + consumeWarnDate(); + } + consumeLinearWhitespace(); + if (offs != src.length()) { + consumeCharacter(','); + } + } + + protected void consumeCharacter(final char c) { + if (offs + 1 > src.length() + || c != src.charAt(offs)) { + parseError(); + } + offs++; + } + + /* + * warn-code = 3DIGIT + */ + protected void consumeWarnCode() { + if (offs + 4 > src.length() + || !Character.isDigit(src.charAt(offs)) + || !Character.isDigit(src.charAt(offs + 1)) + || !Character.isDigit(src.charAt(offs + 2)) + || src.charAt(offs + 3) != ' ') { + parseError(); + } + warnCode = Integer.parseInt(src.substring(offs,offs+3)); + offs += 4; + } + + private void parseError() { + final String s = src.substring(init_offs); + throw new IllegalArgumentException("Bad warn code \"" + s + "\""); + } + + /** Returns the 3-digit code associated with this warning. + * @return <code>int</code> + */ + public int getWarnCode() { return warnCode; } + + /** Returns the "warn-agent" string associated with this warning, + * which is either the name or pseudonym of the server that added + * this particular Warning header. + * @return {@link String} + */ + public String getWarnAgent() { return warnAgent; } + + /** Returns the human-readable warning text for this warning. Note + * that the original quoted-string is returned here, including + * escaping for any contained characters. In other words, if the + * header was: + * <pre> + * Warning: 110 fred "Response is stale" + * </pre> + * then this method will return <code>"\"Response is stale\""</code> + * (surrounding quotes included). + * @return {@link String} + */ + public String getWarnText() { return warnText; } + + /** Returns the date and time when this warning was added, or + * <code>null</code> if a warning date was not supplied in the + * header. + * @return {@link Date} + */ + public Date getWarnDate() { return warnDate; } + + /** Formats a <code>WarningValue</code> as a {@link String} + * suitable for including in a header. For example, you can: + * <pre> + * WarningValue wv = ...; + * HttpResponse resp = ...; + * resp.addHeader("Warning", wv.toString()); + * </pre> + * @return {@link String} + */ + @Override + public String toString() { + if (warnDate != null) { + return String.format("%d %s %s \"%s\"", warnCode, + warnAgent, warnText, DateUtils.formatDate(warnDate)); + } else { + return String.format("%d %s %s", warnCode, warnAgent, warnText); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/package.html b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/package.html new file mode 100644 index 000000000..4c208e7a9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/package.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<html> +<head> +<!-- +==================================================================== +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +==================================================================== + +This software consists of voluntary contributions made by many +individuals on behalf of the Apache Software Foundation. For more +information on the Apache Software Foundation, please see +<http://www.apache.org/>. +--> +</head> +<body bgcolor="white"> + +<p> +This package contains a cache module that can be used for HTTP/1.1 +client-side caching. The primary classes in this package are the +{@link org.apache.http.impl.client.cache.CachingHttpClient}, +which is a drop-in replacement for +a {@link org.apache.http.impl.client.DefaultHttpClient} that adds +caching, and the {@link org.apache.http.impl.client.cache.CacheConfig} +class that can be used for configuring it. +</p> +</body> +</html> diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/package-info.java new file mode 100644 index 000000000..1efbf4cec --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/package-info.java @@ -0,0 +1,51 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +/** + * Default HTTP client implementation. + * <p/> + * The usual execution flow can be demonstrated by the code snippet below: + * <pre> + * CloseableHttpClient httpclient = HttpClients.createDefault(); + * try { + * HttpGet httpGet = new HttpGet("http://targethost/homepage"); + * CloseableHttpResponse response = httpclient.execute(httpGet); + * try { + * System.out.println(response.getStatusLine()); + * HttpEntity entity = response.getEntity(); + * // do something useful with the response body + * // and ensure it is fully consumed + * EntityUtils.consume(entity); + * } finally { + * response.close(); + * } + * } finally { + * httpclient.close(); + * } + * </pre> + */ +package ch.boye.httpclientandroidlib.impl.client; |