summaryrefslogtreecommitdiffstats
path: root/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java')
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java565
1 files changed, 0 insertions, 565 deletions
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java
deleted file mode 100644
index 60bbc86bb..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java
+++ /dev/null
@@ -1,565 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import javax.net.ssl.SSLContext;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpVersion;
-import ch.boye.httpclientandroidlib.client.AuthCache;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.entity.GzipCompressingEntity;
-import ch.boye.httpclientandroidlib.client.methods.HttpDelete;
-import ch.boye.httpclientandroidlib.client.methods.HttpGet;
-import ch.boye.httpclientandroidlib.client.methods.HttpPatch;
-import ch.boye.httpclientandroidlib.client.methods.HttpPost;
-import ch.boye.httpclientandroidlib.client.methods.HttpPut;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import ch.boye.httpclientandroidlib.client.protocol.ClientContext;
-import ch.boye.httpclientandroidlib.conn.ClientConnectionManager;
-import ch.boye.httpclientandroidlib.conn.scheme.PlainSocketFactory;
-import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
-import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
-import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
-import ch.boye.httpclientandroidlib.entity.StringEntity;
-import ch.boye.httpclientandroidlib.impl.client.BasicAuthCache;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.impl.conn.tsccm.ThreadSafeClientConnManager;
-import ch.boye.httpclientandroidlib.params.HttpConnectionParams;
-import ch.boye.httpclientandroidlib.params.HttpParams;
-import ch.boye.httpclientandroidlib.params.HttpProtocolParams;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-import ch.boye.httpclientandroidlib.protocol.HttpContext;
-import ch.boye.httpclientandroidlib.util.EntityUtils;
-
-/**
- * Provide simple HTTP access to a Sync server or similar.
- * Implements Basic Auth by asking its delegate for credentials.
- * Communicates with a ResourceDelegate to asynchronously return responses and errors.
- * Exposes simple get/post/put/delete methods.
- */
-@SuppressWarnings("deprecation")
-public class BaseResource implements Resource {
- private static final String ANDROID_LOOPBACK_IP = "10.0.2.2";
-
- private static final int MAX_TOTAL_CONNECTIONS = 20;
- private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
-
- private boolean retryOnFailedRequest = true;
-
- public static boolean rewriteLocalhost = true;
-
- private static final String LOG_TAG = "BaseResource";
-
- protected final URI uri;
- protected BasicHttpContext context;
- protected DefaultHttpClient client;
- public ResourceDelegate delegate;
- protected HttpRequestBase request;
- public final String charset = "utf-8";
-
- private boolean shouldGzipCompress = false;
- // A hint whether uploaded payloads are chunked. Default true to use GzipCompressingEntity, which is built-in functionality.
- private boolean shouldChunkUploadsHint = true;
-
- /**
- * We have very few writes (observers tend to be installed around sync
- * sessions) and many iterations (every HTTP request iterates observers), so
- * CopyOnWriteArrayList is a reasonable choice.
- */
- protected static final CopyOnWriteArrayList<WeakReference<HttpResponseObserver>>
- httpResponseObservers = new CopyOnWriteArrayList<>();
-
- public BaseResource(String uri) throws URISyntaxException {
- this(uri, rewriteLocalhost);
- }
-
- public BaseResource(URI uri) {
- this(uri, rewriteLocalhost);
- }
-
- public BaseResource(String uri, boolean rewrite) throws URISyntaxException {
- this(new URI(uri), rewrite);
- }
-
- public BaseResource(URI uri, boolean rewrite) {
- if (uri == null) {
- throw new IllegalArgumentException("uri must not be null");
- }
- if (rewrite && "localhost".equals(uri.getHost())) {
- // Rewrite localhost URIs to refer to the special Android emulator loopback passthrough interface.
- Logger.debug(LOG_TAG, "Rewriting " + uri + " to point to " + ANDROID_LOOPBACK_IP + ".");
- try {
- this.uri = new URI(uri.getScheme(), uri.getUserInfo(), ANDROID_LOOPBACK_IP, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
- } catch (URISyntaxException e) {
- Logger.error(LOG_TAG, "Got error rewriting URI for Android emulator.", e);
- throw new IllegalArgumentException("Invalid URI", e);
- }
- } else {
- this.uri = uri;
- }
- }
-
- public static void addHttpResponseObserver(HttpResponseObserver newHttpResponseObserver) {
- if (newHttpResponseObserver == null) {
- return;
- }
- httpResponseObservers.add(new WeakReference<HttpResponseObserver>(newHttpResponseObserver));
- }
-
- public static boolean isHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
- for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
- HttpResponseObserver innerHttpResponseObserver = weakReference.get();
- if (innerHttpResponseObserver == httpResponseObserver) {
- return true;
- }
- }
- return false;
- }
-
- public static boolean removeHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
- for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
- HttpResponseObserver innerHttpResponseObserver = weakReference.get();
- if (innerHttpResponseObserver == httpResponseObserver) {
- // It's safe to mutate the observers while iterating.
- httpResponseObservers.remove(weakReference);
- return true;
- }
- }
- return false;
- }
-
- @Override
- public URI getURI() {
- return this.uri;
- }
-
- @Override
- public String getURIString() {
- return this.uri.toString();
- }
-
- @Override
- public String getHostname() {
- return this.getURI().getHost();
- }
-
- /**
- * Causes the Resource to compress the uploaded entity payload in requests with payloads (e.g. post, put)
- * @param shouldCompress true if the entity should be compressed, false otherwise
- */
- public void setShouldCompressUploadedEntity(final boolean shouldCompress) {
- shouldGzipCompress = shouldCompress;
- }
-
- /**
- * Causes the Resource to chunk the uploaded entity payload in requests with payloads (e.g. post, put).
- * Note: this flag is only a hint - chunking is not guaranteed.
- *
- * Chunking is currently supported with gzip compression.
- *
- * @param shouldChunk true if the transfer should be chunked, false otherwise
- */
- public void setShouldChunkUploadsHint(final boolean shouldChunk) {
- shouldChunkUploadsHint = shouldChunk;
- }
-
- private HttpEntity getMaybeCompressedEntity(final HttpEntity entity) {
- if (!shouldGzipCompress) {
- return entity;
- }
-
- return shouldChunkUploadsHint ? new GzipCompressingEntity(entity) : new GzipNonChunkedCompressingEntity(entity);
- }
-
- /**
- * This shuts up HttpClient, which will otherwise debug log about there
- * being no auth cache in the context.
- */
- private static void addAuthCacheToContext(HttpUriRequest request, HttpContext context) {
- AuthCache authCache = new BasicAuthCache(); // Not thread safe.
- context.setAttribute(ClientContext.AUTH_CACHE, authCache);
- }
-
- /**
- * Invoke this after delegate and request have been set.
- * @throws NoSuchAlgorithmException
- * @throws KeyManagementException
- */
- protected void prepareClient() throws KeyManagementException, NoSuchAlgorithmException, GeneralSecurityException {
- context = new BasicHttpContext();
-
- // We could reuse these client instances, except that we mess around
- // with their parameters… so we'd need a pool of some kind.
- client = new DefaultHttpClient(getConnectionManager());
-
- // TODO: Eventually we should use Apache HttpAsyncClient. It's not out of alpha yet.
- // Until then, we synchronously make the request, then invoke our delegate's callback.
- AuthHeaderProvider authHeaderProvider = delegate.getAuthHeaderProvider();
- if (authHeaderProvider != null) {
- Header authHeader = authHeaderProvider.getAuthHeader(request, context, client);
- if (authHeader != null) {
- request.addHeader(authHeader);
- Logger.debug(LOG_TAG, "Added auth header.");
- }
- }
-
- addAuthCacheToContext(request, context);
-
- HttpParams params = client.getParams();
- HttpConnectionParams.setConnectionTimeout(params, delegate.connectionTimeout());
- HttpConnectionParams.setSoTimeout(params, delegate.socketTimeout());
- HttpConnectionParams.setStaleCheckingEnabled(params, false);
- HttpProtocolParams.setContentCharset(params, charset);
- HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- final String userAgent = delegate.getUserAgent();
- if (userAgent != null) {
- HttpProtocolParams.setUserAgent(params, userAgent);
- }
- delegate.addHeaders(request, client);
- }
-
- private static final Object connManagerMonitor = new Object();
- private static ClientConnectionManager connManager;
-
- // Call within a synchronized block on connManagerMonitor.
- private static ClientConnectionManager enableTLSConnectionManager() throws KeyManagementException, NoSuchAlgorithmException {
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, null, new SecureRandom());
-
- Logger.debug(LOG_TAG, "Using protocols and cipher suites for Android API " + android.os.Build.VERSION.SDK_INT);
- SSLSocketFactory sf = new SSLSocketFactory(sslContext, GlobalConstants.DEFAULT_PROTOCOLS, GlobalConstants.DEFAULT_CIPHER_SUITES, null);
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("https", 443, sf));
- schemeRegistry.register(new Scheme("http", 80, new PlainSocketFactory()));
- ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
-
- cm.setMaxTotal(MAX_TOTAL_CONNECTIONS);
- cm.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
- connManager = cm;
- return cm;
- }
-
- public static ClientConnectionManager getConnectionManager() throws KeyManagementException, NoSuchAlgorithmException
- {
- // TODO: shutdown.
- synchronized (connManagerMonitor) {
- if (connManager != null) {
- return connManager;
- }
- return enableTLSConnectionManager();
- }
- }
-
- /**
- * Do some cleanup, so we don't need the stale connection check.
- */
- public static void closeExpiredConnections() {
- ClientConnectionManager connectionManager;
- synchronized (connManagerMonitor) {
- connectionManager = connManager;
- }
- if (connectionManager == null) {
- return;
- }
- Logger.trace(LOG_TAG, "Closing expired connections.");
- connectionManager.closeExpiredConnections();
- }
-
- public static void shutdownConnectionManager() {
- ClientConnectionManager connectionManager;
- synchronized (connManagerMonitor) {
- connectionManager = connManager;
- connManager = null;
- }
- if (connectionManager == null) {
- return;
- }
- Logger.debug(LOG_TAG, "Shutting down connection manager.");
- connectionManager.shutdown();
- }
-
- private void execute() {
- HttpResponse response;
- try {
- response = client.execute(request, context);
- Logger.debug(LOG_TAG, "Response: " + response.getStatusLine().toString());
- } catch (ClientProtocolException e) {
- delegate.handleHttpProtocolException(e);
- return;
- } catch (IOException e) {
- Logger.debug(LOG_TAG, "I/O exception returned from execute.");
- if (!retryOnFailedRequest) {
- delegate.handleHttpIOException(e);
- } else {
- retryRequest();
- }
- return;
- } catch (Exception e) {
- // Bug 740731: Don't let an exception fall through. Wrapping isn't
- // optimal, but often the exception is treated as an Exception anyway.
- if (!retryOnFailedRequest) {
- // Bug 769671: IOException(Throwable cause) was added only in API level 9.
- final IOException ex = new IOException();
- ex.initCause(e);
- delegate.handleHttpIOException(ex);
- } else {
- retryRequest();
- }
- return;
- }
-
- // Don't retry if the observer or delegate throws!
- for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
- HttpResponseObserver observer = weakReference.get();
- if (observer != null) {
- observer.observeHttpResponse(request, response);
- }
- }
- delegate.handleHttpResponse(response);
- }
-
- private void retryRequest() {
- // Only retry once.
- retryOnFailedRequest = false;
- Logger.debug(LOG_TAG, "Retrying request...");
- this.execute();
- }
-
- private void go(HttpRequestBase request) {
- if (delegate == null) {
- throw new IllegalArgumentException("No delegate provided.");
- }
- this.request = request;
- try {
- this.prepareClient();
- } catch (KeyManagementException e) {
- Logger.error(LOG_TAG, "Couldn't prepare client.", e);
- delegate.handleTransportException(e);
- return;
- } catch (GeneralSecurityException e) {
- Logger.error(LOG_TAG, "Couldn't prepare client.", e);
- delegate.handleTransportException(e);
- return;
- } catch (Exception e) {
- // Bug 740731: Don't let an exception fall through. Wrapping isn't
- // optimal, but often the exception is treated as an Exception anyway.
- delegate.handleTransportException(new GeneralSecurityException(e));
- return;
- }
- this.execute();
- }
-
- @Override
- public void get() {
- Logger.debug(LOG_TAG, "HTTP GET " + this.uri.toASCIIString());
- this.go(new HttpGet(this.uri));
- }
-
- /**
- * Perform an HTTP GET as with {@link BaseResource#get()}, returning only
- * after callbacks have been invoked.
- */
- public void getBlocking() {
- // Until we use the asynchronous Apache HttpClient, we can simply call
- // through.
- this.get();
- }
-
- @Override
- public void delete() {
- Logger.debug(LOG_TAG, "HTTP DELETE " + this.uri.toASCIIString());
- this.go(new HttpDelete(this.uri));
- }
-
- @Override
- public void post(HttpEntity body) {
- Logger.debug(LOG_TAG, "HTTP POST " + this.uri.toASCIIString());
- body = getMaybeCompressedEntity(body);
- HttpPost request = new HttpPost(this.uri);
- request.setEntity(body);
- this.go(request);
- }
-
- @Override
- public void patch(HttpEntity body) {
- Logger.debug(LOG_TAG, "HTTP PATCH " + this.uri.toASCIIString());
- body = getMaybeCompressedEntity(body);
- HttpPatch request = new HttpPatch(this.uri);
- request.setEntity(body);
- this.go(request);
- }
-
- @Override
- public void put(HttpEntity body) {
- Logger.debug(LOG_TAG, "HTTP PUT " + this.uri.toASCIIString());
- body = getMaybeCompressedEntity(body);
- HttpPut request = new HttpPut(this.uri);
- request.setEntity(body);
- this.go(request);
- }
-
- protected static StringEntity stringEntityWithContentTypeApplicationJSON(String s) {
- StringEntity e = new StringEntity(s, "UTF-8");
- e.setContentType("application/json");
- return e;
- }
-
- /**
- * Helper for turning a JSON object into a payload.
- * @throws UnsupportedEncodingException
- */
- protected static StringEntity jsonEntity(JSONObject body) {
- return stringEntityWithContentTypeApplicationJSON(body.toJSONString());
- }
-
- /**
- * Helper for turning an extended JSON object into a payload.
- * @throws UnsupportedEncodingException
- */
- protected static StringEntity jsonEntity(ExtendedJSONObject body) {
- return stringEntityWithContentTypeApplicationJSON(body.toJSONString());
- }
-
- /**
- * Helper for turning a JSON array into a payload.
- * @throws UnsupportedEncodingException
- */
- protected static HttpEntity jsonEntity(JSONArray toPOST) throws UnsupportedEncodingException {
- return stringEntityWithContentTypeApplicationJSON(toPOST.toJSONString());
- }
-
- /**
- * Best-effort attempt to ensure that the entity has been fully consumed and
- * that the underlying stream has been closed.
- *
- * This releases the connection back to the connection pool.
- *
- * @param entity The HttpEntity to be consumed.
- */
- public static void consumeEntity(HttpEntity entity) {
- try {
- EntityUtils.consume(entity);
- } catch (IOException e) {
- // Doesn't matter.
- }
- }
-
- /**
- * Best-effort attempt to ensure that the entity corresponding to the given
- * HTTP response has been fully consumed and that the underlying stream has
- * been closed.
- *
- * This releases the connection back to the connection pool.
- *
- * @param response
- * The HttpResponse to be consumed.
- */
- public static void consumeEntity(HttpResponse response) {
- if (response == null) {
- return;
- }
- try {
- EntityUtils.consume(response.getEntity());
- } catch (IOException e) {
- }
- }
-
- /**
- * Best-effort attempt to ensure that the entity corresponding to the given
- * Sync storage response has been fully consumed and that the underlying
- * stream has been closed.
- *
- * This releases the connection back to the connection pool.
- *
- * @param response
- * The SyncStorageResponse to be consumed.
- */
- public static void consumeEntity(SyncStorageResponse response) {
- if (response.httpResponse() == null) {
- return;
- }
- consumeEntity(response.httpResponse());
- }
-
- /**
- * Best-effort attempt to ensure that the reader has been fully consumed, so
- * that the underlying stream will be closed.
- *
- * This should allow the connection to be released back to the connection pool.
- *
- * @param reader The BufferedReader to be consumed.
- */
- public static void consumeReader(BufferedReader reader) {
- try {
- reader.close();
- } catch (IOException e) {
- // Do nothing.
- }
- }
-
- public void post(JSONArray jsonArray) throws UnsupportedEncodingException {
- post(jsonEntity(jsonArray));
- }
-
- public void put(JSONObject jsonObject) throws UnsupportedEncodingException {
- put(jsonEntity(jsonObject));
- }
-
- public void put(ExtendedJSONObject o) {
- put(jsonEntity(o));
- }
-
- public void post(ExtendedJSONObject o) {
- post(jsonEntity(o));
- }
-
- /**
- * Perform an HTTP POST as with {@link BaseResource#post(ExtendedJSONObject)}, returning only
- * after callbacks have been invoked.
- */
- public void postBlocking(final ExtendedJSONObject o) {
- // Until we use the asynchronous Apache HttpClient, we can simply call
- // through.
- post(jsonEntity(o));
- }
-
- public void post(JSONObject jsonObject) throws UnsupportedEncodingException {
- post(jsonEntity(jsonObject));
- }
-
- public void patch(JSONArray jsonArray) throws UnsupportedEncodingException {
- patch(jsonEntity(jsonArray));
- }
-
- public void patch(ExtendedJSONObject o) {
- patch(jsonEntity(o));
- }
-
- public void patch(JSONObject jsonObject) throws UnsupportedEncodingException {
- patch(jsonEntity(jsonObject));
- }
-}