/*
* 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.
*/
package com.evernote.client.conn.mobile;
import com.evernote.thrift.transport.TTransport;
import com.evernote.thrift.transport.TTransportException;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* HTTP implementation of the TTransport interface modified by Evernote to work
* on Android. Instead of caching in memory, large Thrift messages are cached on
* disk before being sent to the Thrift server.
*/
public class TEvernoteHttpClient extends TTransport {
/**
* Keep requests in RAM if they are less than 512kb.
*/
private static final int MEMORY_BUFFER_SIZE = 512 * 1024;
private URL url_ = null;
private String userAgent = null;
private final DiskBackedByteStore requestBuffer_;
private InputStream inputStream_ = null;
private Map<String, String> customHeaders_ = null;
private HttpRequestBase request = null;
private HttpParams httpParameters = new BasicHttpParams();
/**
* Create a new TAndroidHttpClient.
*
* @param url The Thrift server URL, for example, https://www.evernote.com/edam/user.
* @param userAgent The User-Agent string to send, which should identify the
* client application.
* @param tempPath A temp directory where Thrift messages should be cached
* before they're sent.
* @throws TTransportException If an error occurs creating the temporary
* file that will be used to cache Thrift messages to disk before sending.
*/
public TEvernoteHttpClient(String url, String userAgent, File tempDir)
throws TTransportException {
getHTTPClient();
this.userAgent = userAgent;
try {
url_ = new URL(url);
requestBuffer_ =
new DiskBackedByteStore(tempDir, "http", MEMORY_BUFFER_SIZE);
} catch (IOException iox) {
throw new TTransportException(iox);
}
}
public void setConnectTimeout(int timeout) {
HttpConnectionParams.setConnectionTimeout(httpParameters, timeout);
}
public void setReadTimeout(int timeout) {
HttpConnectionParams.setSoTimeout(httpParameters, timeout);
}
public void setCustomHeaders(Map<String, String> headers) {
customHeaders_ = headers;
}
public void setCustomHeader(String key, String value) {
if (customHeaders_ == null) {
customHeaders_ = new HashMap<String, String>();
}
customHeaders_.put(key, value);
}
public void open() {
}
public void close() {
if (null != inputStream_) {
try {
inputStream_.close();
} catch (IOException ioe) {
;
}
inputStream_ = null;
}
if (mConnectionManager != null) {
mConnectionManager.shutdown();
mConnectionManager = null;
}
}
public boolean isOpen() {
return true;
}
public int read(byte[] buf, int off, int len) throws TTransportException {
if (inputStream_ == null) {
throw new TTransportException("Response buffer is empty, no request.");
}
try {
int ret = inputStream_.read(buf, off, len);
if (ret == -1) {
throw new TTransportException("No more data available.");
}
return ret;
} catch (IOException iox) {
throw new TTransportException(iox);
}
}
private ClientConnectionManager mConnectionManager;
private DefaultHttpClient mHttpClient;
private DefaultHttpClient getHTTPClient() {
try {
if (mConnectionManager != null) {
mConnectionManager.closeExpiredConnections();
mConnectionManager.closeIdleConnections(1, TimeUnit.SECONDS);
} else {
BasicHttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 10000);
HttpConnectionParams.setSoTimeout(params, 20000);
ConnManagerParams.setMaxTotalConnections(params, ConnManagerParams.DEFAULT_MAX_TOTAL_CONNECTIONS);
ConnManagerParams.setTimeout(params, 10000);
ConnPerRouteBean connPerRoute = new ConnPerRouteBean(18); // Giving 18 connections to Evernote
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(
new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
mConnectionManager = new ThreadSafeClientConnManager(params, schemeRegistry);
DefaultHttpClient httpClient = new DefaultHttpClient(mConnectionManager, params);
httpClient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response,
HttpContext context) {
return 2 * 60 * 1000; // 2 minutes in millis
}
});
httpClient.setReuseStrategy(new ConnectionReuseStrategy() {
@Override
public boolean keepAlive(HttpResponse response, HttpContext context) {
return true;
}
});
mHttpClient = httpClient;
}
} catch (Exception ex) {
return null;
}
return mHttpClient;
}
public void write(byte[] buf, int off, int len) {
requestBuffer_.write(buf, off, len);
}
public void flush() throws TTransportException {
long timer = System.currentTimeMillis();
HttpEntity httpEntity = null;
// Extract request and reset buffer
try {
// Prepare http post request
HttpPost request = new HttpPost(url_.toExternalForm());
this.request = request;
request.addHeader("Content-Type", "application/x-thrift");
request.addHeader("Cache-Control", "no-transform");
if (customHeaders_ != null) {
for (Map.Entry<String, String> header : customHeaders_.entrySet()) {
request.addHeader(header.getKey(), header.getValue());
}
}
InputStreamEntity entity =
new InputStreamEntity(requestBuffer_.getInputStream(), requestBuffer_
.getSize());
request.setEntity(entity);
request.addHeader("Accept", "application/x-thrift");
request.addHeader("User-Agent", userAgent == null ? "Java/THttpClient"
: userAgent);
request.getParams().setBooleanParameter(
CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
DefaultHttpClient dHTTP = getHTTPClient();
HttpResponse response = dHTTP.execute(request);
httpEntity = response.getEntity();
int responseCode = response.getStatusLine().getStatusCode();
if (responseCode != 200) {
if (httpEntity != null) {
httpEntity.consumeContent();
}
throw new TTransportException("HTTP Response code: " + responseCode);
}
// Read the responses
requestBuffer_.reset();
inputStream_ = response.getEntity().getContent();
} catch (IOException iox) {
throw new TTransportException(iox);
} catch (Exception ex) {
throw new TTransportException(ex);
} finally {
try {
requestBuffer_.reset();
} catch (IOException e) {
}
this.request = null;
}
}
public void cancel() {
try {
if (this.request != null) {
this.request.abort();
}
} catch (Exception e) {
}
close();
}
public void reset() {
}
}