/* Copyright 2012-2013, Polyvi Inc. (http://polyvi.github.io/openxface) This program is distributed under the terms of the GNU General Public License. This file is part of xFace. xFace is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. xFace is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with xFace. If not, see <http://www.gnu.org/licenses/>. */ package com.polyvi.xface.http; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpEntity; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseInterceptor; import org.apache.http.HttpVersion; 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.HttpUriRequest; 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.entity.HttpEntityWrapper; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.AbstractHttpClient; 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.HttpConnectionParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.SyncBasicHttpContext; import com.polyvi.xface.ssl.XSSLSocketFactory; import com.polyvi.xface.util.XStringUtils; /** * 实现异步Http请求的封装 * */ public class XHttpWorker { /** 检查网络连接的间隔时间 */ private static final int DEFAULT_MAX_CONNECTIONS = 10; private static final int DEFAULT_SOCKET_TIMEOUT = 60 * 1000; private static int maxConnections = DEFAULT_MAX_CONNECTIONS; private static int socketTimeout = DEFAULT_SOCKET_TIMEOUT; private static final int DEFAULT_SOCKET_BUFFER_SIZE = 8192; private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; private static final String ENCODING_GZIP = "gzip"; /**url检测的正则表达式及错误信息*/ private static final String TAG_URL_REGEX = "https?://([\\w]+\\.)+[\\w-]+(:\\d*)?(/[\\w-./?%&=]*)?$"; private static final String TAG_URL_INVALID_MSG = "url is invalid"; private AbstractHttpClient mHttpClient; private HttpContext mHttpContext; private Map<String, String> mClientHeaderMap; private ThreadPoolExecutor mThreadPool; /**防止异步请求次数太多 导致内存紧张 通过弱引用存储任务对象*/ private List<WeakReference<Future<?>>> mRequestList = new LinkedList<WeakReference<Future<?>>>(); public XHttpWorker() { mThreadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); mClientHeaderMap = new HashMap<String, String>(); initHttpClient(); } /** * 初始化httpclient */ private void initHttpClient() { BasicHttpParams httpParams = new BasicHttpParams(); ConnManagerParams.setTimeout(httpParams, socketTimeout); ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(maxConnections)); ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS); HttpConnectionParams.setSoTimeout(httpParams, socketTimeout); HttpConnectionParams.setConnectionTimeout(httpParams, socketTimeout); HttpConnectionParams.setTcpNoDelay(httpParams, true); HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE); HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1); HttpProtocolParams.setUserAgent(httpParams, "xface"); SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", PlainSocketFactory .getSocketFactory(), 80)); schemeRegistry.register(new Scheme("https", XSSLSocketFactory .getSocketFactory(), 443)); schemeRegistry.register(new Scheme("https", XSSLSocketFactory .getSocketFactory(), 8443)); ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager( httpParams, schemeRegistry); mHttpClient = new DefaultHttpClient(cm, httpParams); mHttpClient.addRequestInterceptor(new HttpRequestInterceptor() { @Override public void process(HttpRequest request, HttpContext context) { if (!request.containsHeader(HEADER_ACCEPT_ENCODING)) { request.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP); } for (String header : mClientHeaderMap.keySet()) { request.addHeader(header, mClientHeaderMap.get(header)); } } }); mHttpClient.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(ENCODING_GZIP)) { response.setEntity(new InflatingEntity(response .getEntity())); break; } } } } }); mHttpContext = new SyncBasicHttpContext(new BasicHttpContext()); } /** * http post请求(异步) * * @param url * 请求的url地址 * @param headers * 请求的头部 * @param postData * post给服务器的数据 * @param contentType * 数据类型 * @param handler * http响应的处理器 */ public void post(String url, Header[] headers, String postData, String contentType, XAsyncHttpResponseHandler handler) throws IllegalArgumentException, IOException { checkUrlValid(url); HttpEntity entity = null; if (postData != null) { try { entity = new StringEntity(postData, "UTF-8"); } catch (UnsupportedEncodingException e) { throw e; } } this.post(url, headers, entity, contentType, handler); } /** * http post请求(异步) * * @param url * 请求的url地址 * @param headers * 请求的头部 * @param postData * post给服务器的数据 * @param contentType * 数据类型 * @param handler * http响应的处理器 */ public void post(String url, Header[] headers, HttpEntity postData, String contentType, XAsyncHttpResponseHandler handler) throws IllegalArgumentException, IOException { checkUrlValid(url); HttpPost post = new HttpPost(url); this.setHeader(post, headers); doHttpRequest(addEntityToRequestBase(post, postData), contentType, handler); } /** * http post请求(同步) * * @param url * 请求的url地址 * @param headers * 请求的头部 * @param postData * post给服务器的数据 * @param contentType * 数据类型 * @param handler * http响应的处理器 */ public XSynHttpResponse post(String url, Header[] headers, String postData, String contentType) throws IllegalArgumentException, IOException { checkUrlValid(url); XSyncHttpResponseHandler handler = new XSyncHttpResponseHandler(); this.post(url, headers, postData, contentType, handler); return handler.getHttpResponse(); } /** * http post请求(同步) * * @param url * 请求的url地址 * @param headers * 请求的头部 * @param postData * post给服务器的数据 * @param contentType * 数据类型 * @param handler * http响应的处理器 */ public XSynHttpResponse post(String url, Header[] headers, HttpEntity postData, String contentType) throws IllegalArgumentException, IOException { checkUrlValid(url); XSyncHttpResponseHandler handler = new XSyncHttpResponseHandler(); this.post(url, headers, postData, contentType, handler); return handler.getHttpResponse(); } /** * http get请求(同步) * * @param url * 请求的url地址 * @param headers * 请求的头部 * @param handler * http响应的处理器 */ public XSynHttpResponse get(String url, Header[] headers) throws IllegalArgumentException { checkUrlValid(url); XSyncHttpResponseHandler handler = new XSyncHttpResponseHandler(); this.get(url, headers, handler); return handler.getHttpResponse(); } /** * http get请求(异步) * * @param url * 请求的url地址 * @param headers * 请求的头部 * @param handler * http响应的处理器 */ public void get(String url, Header[] headers, XAsyncHttpResponseHandler handler) throws IllegalArgumentException{ checkUrlValid(url); HttpGet get = new HttpGet(url); this.setHeader(get, headers); doHttpRequest(get, null, handler); } /** * 取消请求 * * @param mayInterruptIfRunning */ public void cancelRequest(boolean mayInterruptIfRunning) { for (WeakReference<Future<?>> requestRef : mRequestList) { Future<?> request = requestRef.get(); if (request != null) { request.cancel(mayInterruptIfRunning); } } } /** * 工具方法 设置头部 * * @param request * @param headers */ private void setHeader(HttpUriRequest request, Header[] headers) { if (headers != null) request.setHeaders(headers); } /** * 提交一个异步http请求 * * @param request * @param contentType * @param handler */ private void doHttpRequest(HttpUriRequest request, String contentType, XAsyncHttpResponseHandler handler) { if (contentType != null) { request.addHeader("Content-Type", contentType); } if (handler.isAsync()) { Future<?> task = mThreadPool.submit(new XAsyncHttpRequest( mHttpClient, mHttpContext, request, handler)); mRequestList.add(new WeakReference<Future<?>>(task)); } else { new XAsyncHttpRequest(mHttpClient, mHttpContext, request, handler) .run(); } } /** * 增加http实体到post请求中 * * @param requestBase * @param entity * @return */ private HttpEntityEnclosingRequestBase addEntityToRequestBase( HttpEntityEnclosingRequestBase requestBase, HttpEntity entity) { if (entity != null) { requestBase.setEntity(entity); } return requestBase; } /** * 设置头部 这些头部会被增加所有的http请求中 * * @param header * @param value */ public void addHeader(String header, String value) { mClientHeaderMap.put(header, value); } /** * 对于gzip数据客户端需要解压 */ private static class InflatingEntity extends HttpEntityWrapper { public InflatingEntity(HttpEntity wrapped) { super(wrapped); } @Override public InputStream getContent() throws IOException { return new GZIPInputStream(wrappedEntity.getContent()); } @Override public long getContentLength() { return -1; } } /** * 检查url的合法性 * @param url:要检测的url * @throws IllegalArgumentException */ private void checkUrlValid(String url) throws IllegalArgumentException { if(XStringUtils.isEmptyString(url) || !Pattern.matches(TAG_URL_REGEX, url)){ throw new IllegalArgumentException(TAG_URL_INVALID_MSG); } } }