gecko/mobile/android/base/sync/net/BaseResource.java
2012-03-09 13:30:57 -08:00

229 lines
8.4 KiB
Java

/* 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.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.HttpVersion;
import ch.boye.httpclientandroidlib.auth.Credentials;
import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials;
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
import ch.boye.httpclientandroidlib.client.methods.HttpDelete;
import ch.boye.httpclientandroidlib.client.methods.HttpGet;
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.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.impl.auth.BasicScheme;
import ch.boye.httpclientandroidlib.impl.client.AbstractHttpClient;
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 org.mozilla.gecko.sync.Logger;
/**
* 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.
*/
public class BaseResource implements Resource {
private static final String ANDROID_LOOPBACK_IP = "10.0.2.2";
public static boolean rewriteLocalhost = true;
private static final String LOG_TAG = "BaseResource";
protected URI uri;
protected BasicHttpContext context;
protected DefaultHttpClient client;
public ResourceDelegate delegate;
protected HttpRequestBase request;
public String charset = "utf-8";
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 (rewrite && uri.getHost().equals("localhost")) {
// 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);
}
} else {
this.uri = uri;
}
}
public URI getURI() {
return this.uri;
}
/**
* Apply the provided credentials string to the provided request.
* @param credentials
* A string, "user:pass".
* @param client
* @param request
* @param context
*/
private static void applyCredentials(String credentials, AbstractHttpClient client, HttpUriRequest request, HttpContext context) {
Credentials creds = new UsernamePasswordCredentials(credentials);
Header header = BasicScheme.authenticate(creds, "US-ASCII", false);
request.addHeader(header);
Logger.trace(LOG_TAG, "Adding Basic Auth header.");
}
/**
* Invoke this after delegate and request have been set.
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
private void prepareClient() throws KeyManagementException, NoSuchAlgorithmException {
ClientConnectionManager connectionManager = getConnectionManager();
context = new BasicHttpContext();
client = new DefaultHttpClient(connectionManager);
// 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.
String credentials = delegate.getCredentials();
if (credentials != null) {
BaseResource.applyCredentials(credentials, client, request, context);
}
HttpParams params = client.getParams();
HttpConnectionParams.setConnectionTimeout(params, delegate.connectionTimeout());
HttpConnectionParams.setSoTimeout(params, delegate.socketTimeout());
HttpProtocolParams.setContentCharset(params, charset);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
delegate.addHeaders(request, client);
}
private static Object connManagerMonitor = new Object();
private static ClientConnectionManager connManager;
/**
* This method exists for test code.
*/
public static ClientConnectionManager enablePlainHTTPConnectionManager() {
synchronized (connManagerMonitor) {
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager();
connManager = cm;
return cm;
}
}
private static ClientConnectionManager enableTLSConnectionManager() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, new SecureRandom());
SSLSocketFactory sf = new TLSSocketFactory(sslContext);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("https", 443, sf));
schemeRegistry.register(new Scheme("http", 80, new PlainSocketFactory()));
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
connManager = cm;
return cm;
}
public static ClientConnectionManager getConnectionManager() throws KeyManagementException, NoSuchAlgorithmException
{
// TODO: shutdown.
synchronized (connManagerMonitor) {
if (connManager != null) {
return connManager;
}
return enableTLSConnectionManager();
}
}
private void execute() {
try {
HttpResponse response = client.execute(request, context);
Logger.debug(LOG_TAG, "Response: " + response.getStatusLine().toString());
delegate.handleHttpResponse(response);
} catch (ClientProtocolException e) {
delegate.handleHttpProtocolException(e);
} catch (IOException e) {
delegate.handleHttpIOException(e);
}
}
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);
} catch (NoSuchAlgorithmException e) {
Logger.error(LOG_TAG, "Couldn't prepare client.", e);
delegate.handleTransportException(e);
}
this.execute();
}
@Override
public void get() {
Logger.debug(LOG_TAG, "HTTP GET " + this.uri.toASCIIString());
this.go(new HttpGet(this.uri));
}
@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());
HttpPost request = new HttpPost(this.uri);
request.setEntity(body);
this.go(request);
}
@Override
public void put(HttpEntity body) {
Logger.debug(LOG_TAG, "HTTP PUT " + this.uri.toASCIIString());
HttpPut request = new HttpPut(this.uri);
request.setEntity(body);
this.go(request);
}
}