package com.litesuits.http.impl.apache;
import android.os.Build;
import com.litesuits.http.HttpClient;
import com.litesuits.http.HttpConfig;
import com.litesuits.http.data.*;
import com.litesuits.http.data.HttpStatus;
import com.litesuits.http.exception.*;
import com.litesuits.http.listener.StatisticsListener;
import com.litesuits.http.log.HttpLog;
import com.litesuits.http.parser.DataParser;
import com.litesuits.http.request.AbstractRequest;
import com.litesuits.http.response.InternalResponse;
import org.apache.http.*;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.methods.*;
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.HttpEntityWrapper;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.Set;
/**
* 使用Apache HttpClient来实现
* implemented by Apache HttpClient
*
* @author MaTianyu
* 2014-1-1下午9:38:21
*/
public class ApacheClient implements HttpClient {
private static String TAG = ApacheClient.class.getSimpleName();
private DefaultHttpClient mHttpClient;
public static final int DEFAULT_KEEP_LIVE = 30000;
public static final int DEFAULT_MAX_CONN_PER_ROUT = 128;
public static final int DEFAULT_MAX_CONN_TOTAL = 256;
public static final int DEFAULT_TIMEOUT = 20 * 1000;
public static final int DEFAULT_BUFFER_SIZE = 4096;
public static final boolean TCP_NO_DELAY = true;
/**
* User-Agent
*/
protected String userAgent = String.format("litehttp-%s (android-%s; api-%s; %s; %s)", "v3"
, Build.VERSION.RELEASE, Build.VERSION.SDK_INT, Build.BRAND, Build.MODEL);
public ApacheClient() {
mHttpClient = createApacheHttpClient(createHttpParams());
}
/*__________________________ initialization_methods __________________________*/
/**
* initialize HttpParams , initialize settings such as total connextions,timeout ...
*/
private BasicHttpParams createHttpParams() {
BasicHttpParams params = new BasicHttpParams();
ConnManagerParams.setTimeout(params, DEFAULT_TIMEOUT);
ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(DEFAULT_MAX_CONN_PER_ROUT));
ConnManagerParams.setMaxTotalConnections(params, DEFAULT_MAX_CONN_TOTAL);
HttpConnectionParams.setTcpNoDelay(params, TCP_NO_DELAY);
HttpConnectionParams.setConnectionTimeout(params, DEFAULT_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, DEFAULT_TIMEOUT);
HttpConnectionParams.setSocketBufferSize(params, DEFAULT_BUFFER_SIZE);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setUserAgent(params, userAgent);
// settingOthers(params);
return params;
}
private void settingOthers(BasicHttpParams params) {
HttpConnectionParams.setLinger(params, DEFAULT_KEEP_LIVE);
HttpConnectionParams.setStaleCheckingEnabled(params, false);
HttpProtocolParams.setUseExpectContinue(params, false);
}
/**
* new {@link DefaultHttpClient}, and set strategy.
*
* @return DefaultHttpClient
*/
private DefaultHttpClient createApacheHttpClient(BasicHttpParams httpParams) {
DefaultHttpClient httpClient = new DefaultHttpClient(createClientConnManager(httpParams), httpParams);
// disable apache default redirect handler
httpClient.setRedirectHandler(new RedirectHandler() {
@Override
public boolean isRedirectRequested(HttpResponse response, HttpContext context) {
return false;
}
@Override
public URI getLocationURI(HttpResponse response, HttpContext context) {
return null;
}
});
// disable apache default retry handler
httpClient.setHttpRequestRetryHandler(new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
return false;
}
});
// enable gzip supporting in request
httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) {
if (!request.containsHeader(Consts.HEADER_ACCEPT_ENCODING)) {
request.addHeader(Consts.HEADER_ACCEPT_ENCODING, Consts.ENCODING_GZIP);
}
}
});
// enable gzip supporting in response
httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
@Override
public void process(HttpResponse response, HttpContext context) {
final HttpEntity entity = response.getEntity();
if (entity == null) {
return;
}
final Header encoding = entity.getContentEncoding();
if (encoding != null) {
for (HeaderElement element : encoding.getElements()) {
if (element.getName().equalsIgnoreCase(Consts.ENCODING_GZIP)) {
response.setEntity(new GZIPEntityWrapper(entity));
break;
}
}
}
}
});
// setKeepAlive(httpClient);
return httpClient;
}
private void setKeepAlive(DefaultHttpClient httpClient) {
// 10 seconds keepalive
httpClient.setReuseStrategy(new ConnectionReuseStrategy() {
@Override
public boolean keepAlive(HttpResponse arg0, HttpContext arg1) {
return true;
}
});
httpClient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse arg0, HttpContext arg1) {
return DEFAULT_KEEP_LIVE;
}
});
}
/**
* register http and https scheme, and got ThreadSafeClientConnManager
*
* @return ThreadSafeClientConnManager
*/
private ThreadSafeClientConnManager createClientConnManager(BasicHttpParams httpParams) {
SchemeRegistry schemeRegistry = new SchemeRegistry();
SSLSocketFactory socketFactory = MySSLSocketFactory.getFixedSocketFactory();
schemeRegistry.register(new Scheme(Consts.SCHEME_HTTP,
PlainSocketFactory.getSocketFactory(),
HttpConfig.DEFAULT_HTTP_PORT));
schemeRegistry.register(new Scheme(Consts.SCHEME_HTTPS, socketFactory, HttpConfig.DEFAULT_HTTPS_PORT));
return new ThreadSafeClientConnManager(httpParams, schemeRegistry);
}
/*__________________________ implemention_methods __________________________*/
@Override
public void config(HttpConfig config) {
if (mHttpClient != null) {
HttpParams params = mHttpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, config.getConnectTimeout());
HttpConnectionParams.setSoTimeout(params, config.getSocketTimeout());
HttpProtocolParams.setUserAgent(params, config.getUserAgent());
mHttpClient.setParams(params);
}
}
@Override
public <T> void connect(AbstractRequest<T> request, InternalResponse response) throws Exception {
//try to connect
HttpEntity entity = null;
int maxRedirectTimes = request.getMaxRedirectTimes();
StatisticsListener statistic = response.getStatistics();
try {
// 1. create apache request
final HttpUriRequest apacheRequest = createApacheRequest(request);
// 2. update http header
if (request.getHeaders() != null) {
Set<Entry<String, String>> set = request.getHeaders().entrySet();
for (Entry<String, String> en : set) {
apacheRequest.setHeader(new BasicHeader(en.getKey(), en.getValue()));
}
}
if (statistic != null) {
statistic.onPreConnect(request);
}
HttpResponse ares = mHttpClient.execute(apacheRequest);
if (statistic != null) {
statistic.onAfterConnect(request);
}
// status
StatusLine status = ares.getStatusLine();
HttpStatus httpStatus = new HttpStatus(status.getStatusCode(), status.getReasonPhrase());
response.setHttpStatus(httpStatus);
// header
Header[] headers = ares.getAllHeaders();
if (headers != null) {
ArrayList<com.litesuits.http.data.NameValuePair> hs = new ArrayList<com.litesuits.http.data.NameValuePair>();
for (Header header : headers) {
String name = header.getName();
String value = header.getValue();
if ("Content-Length".equalsIgnoreCase(name)) {
response.setContentLength(Long.parseLong(value));
}
hs.add(new com.litesuits.http.data.NameValuePair(name, value));
}
response.setHeaders(hs);
}
// is cancelled ?
if (request.isCancelledOrInterrupted()) {
return;
}
// 成功
entity = ares.getEntity();
// data body
if (status.getStatusCode() <= 299 || status.getStatusCode() == 600) {
if (entity != null) {
// charset
String charSet = getCharsetFromEntity(entity, request.getCharSet());
response.setCharSet(charSet);
if (!request.isCancelledOrInterrupted()) {
// length
long len = response.getContentLength();
DataParser<T> parser = request.getDataParser();
if (statistic != null) {
statistic.onPreRead(request);
}
parser.readFromNetStream(entity.getContent(), len, charSet);
if (statistic != null) {
statistic.onAfterRead(request);
}
response.setReadedLength(parser.getReadedLength());
}
}else{
throw new HttpNetException(NetException.NetworkUnreachable);
}
} else if (status.getStatusCode() <= 399) {
// redirect
if (response.getRedirectTimes() < maxRedirectTimes) {
// get the location header to find out where to redirect to
Header locationHeader = ares.getFirstHeader(Consts.REDIRECT_LOCATION);
if (locationHeader != null) {
String location = locationHeader.getValue();
if (location != null && location.length() > 0) {
if (!location.toLowerCase().startsWith("http")) {
URI uri = new URI(request.createFullUri());
URI redirect = new URI(uri.getScheme(), uri.getHost(), location, null);
location = redirect.toString();
}
response.setRedirectTimes(response.getRedirectTimes() + 1);
request.setUri(location);
if (HttpLog.isPrint) {
HttpLog.i(TAG, "Redirect to : " + location);
}
if (request.getHttpListener() != null) {
request.getHttpListener()
.notifyCallRedirect(request, maxRedirectTimes, response.getRedirectTimes());
}
connect(request, response);
return;
}
}
throw new HttpServerException(httpStatus);
} else {
throw new HttpServerException(ServerException.RedirectTooMuch);
}
} else if (status.getStatusCode() <= 499) {
// 客户端被拒
throw new HttpServerException(httpStatus);
} else if (status.getStatusCode() < 599) {
// 服务器有误
throw new HttpServerException(httpStatus);
}
} finally {
if (entity != null) {
consumeEntityContent(entity);
}
}
}
/**
* create an apache request
*/
private HttpUriRequest createApacheRequest(AbstractRequest request) throws HttpClientException {
HttpEntityEnclosingRequestBase entityRequset = null;
switch (request.getMethod()) {
case Get:
return new HttpGet(request.createFullUri());
case Head:
return new HttpHead(request.createFullUri());
case Delete:
return new HttpDelete(request.createFullUri());
case Trace:
return new HttpTrace(request.createFullUri());
case Options:
return new HttpOptions(request.createFullUri());
case Post:
entityRequset = new HttpPost(request.createFullUri());
break;
case Put:
entityRequset = new HttpPut(request.createFullUri());
break;
case Patch:
entityRequset = new HttpPatch(request.createFullUri());
break;
default:
return new HttpGet(request.createFullUri());
}
entityRequset.setEntity(EntityBuilder.build(request));
return entityRequset;
}
/**
* get Charset String From HTTP Response
*/
private String getCharsetFromEntity(HttpEntity entity, String defCharset) {
final Header header = entity.getContentType();
if (header != null) {
final HeaderElement[] elements = header.getElements();
if (elements.length > 0) {
HeaderElement helem = elements[0];
// final String mimeType = helem.getName();
final NameValuePair[] params = helem.getParameters();
if (params != null) {
for (final NameValuePair param : params) {
if (param.getName().equalsIgnoreCase("charset")) {
String s = param.getValue();
if (s != null && s.length() > 0) {
return s;
}
}
}
}
}
}
return defCharset == null ? Charsets.UTF_8 : defCharset;
}
/**
* This horrible hack is required on Android, due to implementation of BasicManagedEntity, which
* doesn't chain call consumeContent on underlying wrapped HttpEntity
*
* @param entity HttpEntity, may be null
*/
public void consumeEntityContent(HttpEntity entity) {
try {
// Close the InputStream and release the resources by "consuming the content".
if (entity instanceof HttpEntityWrapper) {
Field f = null;
Field[] fields = HttpEntityWrapper.class.getDeclaredFields();
for (Field ff : fields) {
if (ff.getName().equals("wrappedEntity")) {
f = ff;
break;
}
}
if (f != null) {
f.setAccessible(true);
HttpEntity wrapped = (HttpEntity) f.get(entity);
if (wrapped != null) {
wrapped.consumeContent();
if (HttpLog.isPrint) {
HttpLog.d(TAG, "HttpEntity wrappedEntity reflection consumeContent");
}
}
}
} else {
entity.consumeContent();
}
} catch (Throwable t) {
HttpLog.e(TAG, "wrappedEntity consume error. ", t);
}
}
}