com.couchbase.lite.support.RemoteRequest Maven / Gradle / Ivy
package com.couchbase.lite.support;
import com.couchbase.lite.Database;
import com.couchbase.lite.Manager;
import com.couchbase.lite.auth.Authenticator;
import com.couchbase.lite.auth.AuthenticatorImpl;
import com.couchbase.lite.util.Log;
import com.couchbase.lite.util.URIUtils;
import com.couchbase.lite.util.Utils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HttpContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.zip.GZIPInputStream;
/**
* @exclude
*/
public class RemoteRequest implements Runnable {
// Don't compress data shorter than this (not worth the CPU time, plus it might not shrink)
public static final int MIN_JSON_LENGTH_TO_COMPRESS = 100;
protected ScheduledExecutorService workExecutor;
protected final HttpClientFactory clientFactory;
protected String method;
protected URL url;
protected Object body;
protected Authenticator authenticator;
protected RemoteRequestCompletionBlock onPreCompletion;
protected RemoteRequestCompletionBlock onCompletion;
protected RemoteRequestCompletionBlock onPostCompletion;
protected HttpUriRequest request;
protected Map requestHeaders;
// if true, we wont log any 404 errors (useful when getting remote checkpoint doc)
private boolean dontLog404;
// if true, send compressed (gzip) request
private boolean compressedRequest = false;
private String str = null;
public RemoteRequest(ScheduledExecutorService workExecutor,
HttpClientFactory clientFactory, String method, URL url,
Object body, Database db, Map requestHeaders, RemoteRequestCompletionBlock onCompletion) {
this.clientFactory = clientFactory;
this.method = method;
this.url = url;
this.body = body;
this.onCompletion = onCompletion;
this.workExecutor = workExecutor;
this.requestHeaders = requestHeaders;
this.request = createConcreteRequest();
Log.v(Log.TAG_SYNC, "%s: RemoteRequest created, url: %s", this, url);
}
@Override
public void run() {
Log.v(Log.TAG_SYNC, "%s: RemoteRequest run() called, url: %s", this, url);
HttpClient httpClient = clientFactory.getHttpClient();
try {
preemptivelySetAuthCredentials(httpClient);
request.addHeader("Accept", "multipart/related, application/json");
request.addHeader("User-Agent", Manager.getUserAgent());
request.addHeader("Accept-Encoding", "gzip, deflate");
addRequestHeaders(request);
setBody(request);
executeRequest(httpClient, request);
Log.v(Log.TAG_SYNC, "%s: RemoteRequest run() finished, url: %s", this, url);
} catch (Throwable e) {
Log.e(Log.TAG_SYNC, "RemoteRequest.run() exception: %s", e);
} finally {
// shutdown connection manager (close all connections)
if (httpClient != null && httpClient.getConnectionManager() != null)
httpClient.getConnectionManager().shutdown();
}
}
public void abort() {
Log.w(Log.TAG_REMOTE_REQUEST, "%s: aborting request: %s", this, request);
if (request != null) {
request.abort();
} else {
Log.w(Log.TAG_REMOTE_REQUEST, "%s: Unable to abort request since underlying request is null", this);
}
}
public HttpUriRequest getRequest() {
return request;
}
protected void addRequestHeaders(HttpUriRequest request) {
if (requestHeaders != null) {
for (String requestHeaderKey : requestHeaders.keySet()) {
request.addHeader(requestHeaderKey, requestHeaders.get(requestHeaderKey).toString());
}
}
}
public void setOnPostCompletion(RemoteRequestCompletionBlock onPostCompletion) {
this.onPostCompletion = onPostCompletion;
}
public void setOnPreCompletion(RemoteRequestCompletionBlock onPreCompletion) {
this.onPreCompletion = onPreCompletion;
}
protected HttpUriRequest createConcreteRequest() {
HttpUriRequest request = null;
if ("GET".equalsIgnoreCase(method)) {
request = new HttpGet(url.toExternalForm());
} else if ("PUT".equalsIgnoreCase(method)) {
request = new HttpPut(url.toExternalForm());
} else if ("POST".equalsIgnoreCase(method)) {
request = new HttpPost(url.toExternalForm());
}
return request;
}
/**
* Set Authenticator for BASIC Authentication
*/
public void setAuthenticator(Authenticator authenticator) {
this.authenticator = authenticator;
}
protected void executeRequest(HttpClient httpClient, HttpUriRequest requestParam) {
Object fullBody = null;
Throwable error = null;
HttpResponse response = null;
try {
Log.v(Log.TAG_SYNC, "%s: RemoteRequest calling httpClient.execute, url: %s", this, url);
if (requestParam.isAborted()) {
Log.v(Log.TAG_SYNC, "%s: RemoteRequest has already been aborted", this);
respondWithResult(fullBody, new Exception(String.format("%s: Request %s has been aborted", this, requestParam)), response);
return;
}
Log.v(Log.TAG_SYNC, "%s: RemoteRequest calling httpClient.execute, client: %s url: %s", this, httpClient, url);
response = httpClient.execute(requestParam);
Log.v(Log.TAG_SYNC, "%s: RemoteRequest called httpClient.execute, url: %s", this, url);
// add in cookies to global store
try {
if (httpClient instanceof DefaultHttpClient) {
DefaultHttpClient defaultHttpClient = (DefaultHttpClient)httpClient;
this.clientFactory.addCookies(defaultHttpClient.getCookieStore().getCookies());
}
} catch (Exception e) {
Log.e(Log.TAG_REMOTE_REQUEST, "Unable to add in cookies to global store", e);
}
StatusLine status = response.getStatusLine();
if (status.getStatusCode() >= 300) {
try {
if (!dontLog404) {
Log.e(Log.TAG_REMOTE_REQUEST, "Got error status: %d for %s. Reason: %s", status.getStatusCode(), url, status.getReasonPhrase());
}
error = new HttpResponseException(status.getStatusCode(), status.getReasonPhrase());
respondWithResult(fullBody, error, response);
return;
} finally {
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
entity.consumeContent();
} catch (IOException e) {
}
}
}
} else {
HttpEntity entity = response.getEntity();
try {
if (entity != null) {
InputStream inputStream = entity.getContent();
try {
// decompress if contentEncoding is gzip
if (Utils.isGzip(entity))
inputStream = new GZIPInputStream(inputStream);
fullBody = Manager.getObjectMapper().readValue(inputStream, Object.class);
respondWithResult(fullBody, error, response);
return;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
}
}
}
} finally {
if (entity != null) {
try {
entity.consumeContent();
} catch (IOException e) {
}
}
}
}
} catch (Exception e) {
Log.e(Log.TAG_REMOTE_REQUEST, "%s: executeRequest() Exception: %s. url: %s", this, e, url);
error = e;
}
finally {
Log.v(Log.TAG_SYNC, "%s: RemoteRequest finally block. url: %s", this, url);
}
// error scenario
respondWithResult(fullBody, error, response);
}
protected void preemptivelySetAuthCredentials(HttpClient httpClient) {
boolean isUrlBasedUserInfo = false;
String userInfo = url.getUserInfo();
if (userInfo != null) {
isUrlBasedUserInfo = true;
} else {
if (authenticator != null) {
AuthenticatorImpl auth = (AuthenticatorImpl) authenticator;
userInfo = auth.authUserInfo();
}
}
if (userInfo != null) {
if (userInfo.contains(":") && !":".equals(userInfo.trim())) {
String[] userInfoElements = userInfo.split(":");
String username = isUrlBasedUserInfo ? URIUtils.decode(userInfoElements[0]) : userInfoElements[0];
String password = "";
if(userInfoElements.length >= 2)
password = isUrlBasedUserInfo ? URIUtils.decode(userInfoElements[1]) : userInfoElements[1];
final Credentials credentials = new UsernamePasswordCredentials(username, password);
if (httpClient instanceof DefaultHttpClient) {
DefaultHttpClient dhc = (DefaultHttpClient) httpClient;
HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
if (authState.getAuthScheme() == null) {
authState.setAuthScheme(new BasicScheme());
authState.setCredentials(credentials);
}
}
};
dhc.addRequestInterceptor(preemptiveAuth, 0);
}
} else {
Log.w(Log.TAG_REMOTE_REQUEST, "RemoteRequest Unable to parse user info, not setting credentials");
}
}
}
public void respondWithResult(final Object result, final Throwable error, final HttpResponse response) {
try {
if (onPreCompletion != null) {
onPreCompletion.onCompletion(response, null, error);
}
onCompletion.onCompletion(response, result, error);
if (onPostCompletion != null) {
onPostCompletion.onCompletion(response, null, error);
}
} catch (Exception e) {
// don't let this crash the thread
Log.e(Log.TAG_REMOTE_REQUEST,
"RemoteRequestCompletionBlock throw Exception",
e);
}
}
public void setDontLog404(boolean dontLog404) {
this.dontLog404 = dontLog404;
}
public boolean isCompressedRequest() {
return compressedRequest;
}
public void setCompressedRequest(boolean compressedRequest) {
this.compressedRequest = compressedRequest;
}
protected void setBody(HttpUriRequest request) {
// set body if appropriate
if (body != null && request instanceof HttpEntityEnclosingRequestBase) {
byte[] bodyBytes = null;
try {
bodyBytes = Manager.getObjectMapper().writeValueAsBytes(body);
} catch (Exception e) {
Log.e(Log.TAG_REMOTE_REQUEST, "Error serializing body of request", e);
}
ByteArrayEntity entity = null;
if(isCompressedRequest() && bodyBytes.length > MIN_JSON_LENGTH_TO_COMPRESS){
entity = setCompressedBody(bodyBytes);
}
if(entity == null){
entity = setUncompressedBody(bodyBytes);
}
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
}
/**
* gzip
*
* in CBLRemoteRequest.m
* - (BOOL) compressBody
*/
protected static ByteArrayEntity setCompressedBody(byte[] bodyBytes){
if(bodyBytes.length < MIN_JSON_LENGTH_TO_COMPRESS){
return null;
}
// Gzipping
byte[] encodedBytes = Utils.compressByGzip(bodyBytes);
if(encodedBytes == null || encodedBytes.length >= bodyBytes.length) {
return null;
}
ByteArrayEntity entity = new ByteArrayEntity(encodedBytes);
entity.setContentType("application/json");
entity.setContentEncoding("gzip");
encodedBytes = null;
bodyBytes = null;
return entity;
}
protected static ByteArrayEntity setUncompressedBody(byte[] bodyBytes){
ByteArrayEntity entity = new ByteArrayEntity(bodyBytes);
entity.setContentType("application/json");
return entity;
}
@Override
public String toString() {
if (str == null) {
String remoteURL = url.toExternalForm().replaceAll("://.*:.*@", "://---:---@");
str = String.format("RemoteRequest{%s, %s}", method, remoteURL);
}
return str;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy