http://blog.csdn.net/angjunqiang/article/details/55259170
背景
前面的一篇文章【同步的HttpClient使用詳解】中,提到了服務端通進行網路請求的方式。也講述了在併發量大的情況下使用HttpClient的連線池來提高效能。此方法雖然很有效果,但是當訪問量極大或網路不好的情況下也會出現某些網路請求慢導致其它請求阻塞的情況,為此本文引入了非同步的HttpClient包,將網路請求變成一個非同步的請求,不影響其它的請求。
非同步httpClient需要的jar包
- <span style="font-size:14px;"> <!-- httpclient -->
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.5.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpcore</artifactId>
- <version>4.4.6</version>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpmime</artifactId>
- <version>4.3.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpasyncclient</artifactId>
- <version>4.1.3</version>
- </dependency></span>
注意:由於非同步的HttpClient比較新,所以儘量使用該文中的版本jar包,或以上版本
使用方法
為了更好的使用,在這裡簡單的使用了工廠模式。將同步的httpclient與非同步的httpclient通過工廠進行例項化
1、定義非同步的httpclient
- <span style="font-size:14px;">package com.studyproject.httpclient;
- import java.nio.charset.CodingErrorAction;
- import java.security.KeyManagementException;
- import java.security.KeyStoreException;
- import java.security.NoSuchAlgorithmException;
- import java.security.UnrecoverableKeyException;
- import javax.net.ssl.SSLContext;
- import org.apache.http.Consts;
- import org.apache.http.HttpHost;
- import org.apache.http.auth.AuthSchemeProvider;
- import org.apache.http.auth.AuthScope;
- import org.apache.http.auth.MalformedChallengeException;
- import org.apache.http.auth.UsernamePasswordCredentials;
- import org.apache.http.client.CredentialsProvider;
- import org.apache.http.client.config.AuthSchemes;
- import org.apache.http.client.config.RequestConfig;
- import org.apache.http.config.ConnectionConfig;
- import org.apache.http.config.Lookup;
- import org.apache.http.config.Registry;
- import org.apache.http.config.RegistryBuilder;
- import org.apache.http.conn.ssl.SSLContexts;
- import org.apache.http.impl.auth.BasicSchemeFactory;
- import org.apache.http.impl.auth.DigestSchemeFactory;
- import org.apache.http.impl.auth.KerberosSchemeFactory;
- import org.apache.http.impl.auth.NTLMSchemeFactory;
- import org.apache.http.impl.auth.SPNegoSchemeFactory;
- import org.apache.http.impl.client.BasicCookieStore;
- import org.apache.http.impl.client.BasicCredentialsProvider;
- import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
- import org.apache.http.impl.nio.client.HttpAsyncClients;
- import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
- import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
- import org.apache.http.impl.nio.reactor.IOReactorConfig;
- import org.apache.http.nio.conn.NoopIOSessionStrategy;
- import org.apache.http.nio.conn.SchemeIOSessionStrategy;
- import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
- import org.apache.http.nio.reactor.ConnectingIOReactor;
- import org.apache.http.nio.reactor.IOReactorException;
- /**
- * 非同步的HTTP請求物件,可設定代理
- */
- public class HttpAsyncClient {
- private static int socketTimeout = 1000;// 設定等待資料超時時間5秒鐘 根據業務調整
- private static int connectTimeout = 2000;// 連線超時
- private static int poolSize = 3000;// 連線池最大連線數
- private static int maxPerRoute = 1500;// 每個主機的併發最多隻有1500
- // http代理相關引數
- private String host = "";
- private int port = 0;
- private String username = "";
- private String password = "";
- // 非同步httpclient
- private CloseableHttpAsyncClient asyncHttpClient;
- // 非同步加代理的httpclient
- private CloseableHttpAsyncClient proxyAsyncHttpClient;
- public HttpAsyncClient() {
- try {
- this.asyncHttpClient = createAsyncClient(false);
- this.proxyAsyncHttpClient = createAsyncClient(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public CloseableHttpAsyncClient createAsyncClient(boolean proxy)
- throws KeyManagementException, UnrecoverableKeyException,
- NoSuchAlgorithmException, KeyStoreException,
- MalformedChallengeException, IOReactorException {
- RequestConfig requestConfig = RequestConfig.custom()
- .setConnectTimeout(connectTimeout)
- .setSocketTimeout(socketTimeout).build();
- SSLContext sslcontext = SSLContexts.createDefault();
- UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
- username, password);
- CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
- credentialsProvider.setCredentials(AuthScope.ANY, credentials);
- // 設定協議http和https對應的處理socket連結工廠的物件
- Registry<SchemeIOSessionStrategy> sessionStrategyRegistry = RegistryBuilder
- .<SchemeIOSessionStrategy> create()
- .register("http", NoopIOSessionStrategy.INSTANCE)
- .register("https", new SSLIOSessionStrategy(sslcontext))
- .build();
- // 配置io執行緒
- IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
- .setIoThreadCount(Runtime.getRuntime().availableProcessors())
- .build();
- // 設定連線池大小
- ConnectingIOReactor ioReactor;
- ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
- PoolingNHttpClientConnectionManager conMgr = new PoolingNHttpClientConnectionManager(
- ioReactor, null, sessionStrategyRegistry, null);
- if (poolSize > 0) {
- conMgr.setMaxTotal(poolSize);
- }
- if (maxPerRoute > 0) {
- conMgr.setDefaultMaxPerRoute(maxPerRoute);
- } else {
- conMgr.setDefaultMaxPerRoute(10);
- }
- ConnectionConfig connectionConfig = ConnectionConfig.custom()
- .setMalformedInputAction(CodingErrorAction.IGNORE)
- .setUnmappableInputAction(CodingErrorAction.IGNORE)
- .setCharset(Consts.UTF_8).build();
- Lookup<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder
- .<AuthSchemeProvider> create()
- .register(AuthSchemes.BASIC, new BasicSchemeFactory())
- .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
- .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
- .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
- .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
- .build();
- conMgr.setDefaultConnectionConfig(connectionConfig);
- if (proxy) {
- return HttpAsyncClients.custom().setConnectionManager(conMgr)
- .setDefaultCredentialsProvider(credentialsProvider)
- .setDefaultAuthSchemeRegistry(authSchemeRegistry)
- .setProxy(new HttpHost(host, port))
- .setDefaultCookieStore(new BasicCookieStore())
- .setDefaultRequestConfig(requestConfig).build();
- } else {
- return HttpAsyncClients.custom().setConnectionManager(conMgr)
- .setDefaultCredentialsProvider(credentialsProvider)
- .setDefaultAuthSchemeRegistry(authSchemeRegistry)
- .setDefaultCookieStore(new BasicCookieStore()).build();
- }
- }
- public CloseableHttpAsyncClient getAsyncHttpClient() {
- return asyncHttpClient;
- }
- public CloseableHttpAsyncClient getProxyAsyncHttpClient() {
- return proxyAsyncHttpClient;
- }
- }</span>
2、定義同步的httpclient
- <span style="font-size:14px;">package com.studyproject.httpclient;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.nio.charset.Charset;
- import java.security.cert.CertificateException;
- import java.security.cert.X509Certificate;
- import java.util.List;
- import javax.net.ssl.SSLContext;
- import javax.net.ssl.TrustManager;
- import javax.net.ssl.X509TrustManager;
- import org.apache.commons.lang.StringUtils;
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpResponse;
- import org.apache.http.HttpVersion;
- import org.apache.http.auth.AuthScope;
- import org.apache.http.auth.UsernamePasswordCredentials;
- import org.apache.http.client.ClientProtocolException;
- import org.apache.http.client.entity.UrlEncodedFormEntity;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.client.methods.HttpPost;
- 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.StringEntity;
- import org.apache.http.impl.client.DefaultHttpClient;
- import org.apache.http.impl.conn.PoolingClientConnectionManager;
- import org.apache.http.message.BasicNameValuePair;
- import org.apache.http.params.BasicHttpParams;
- import org.apache.http.params.CoreConnectionPNames;
- import org.apache.http.params.CoreProtocolPNames;
- import org.apache.http.params.HttpParams;
- import org.apache.http.util.EntityUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * 同步的HTTP請求物件,支援post與get方法以及可設定代理
- */
- public class HttpSyncClient {
- private Logger logger = LoggerFactory.getLogger(HttpSyncClient.class);
- private static int socketTimeout = 1000;// 設定等待資料超時時間5秒鐘 根據業務調整
- private static int connectTimeout = 2000;// 連線超時
- private static int maxConnNum = 4000;// 連線池最大連線數
- private static int maxPerRoute = 1500;// 每個主機的併發最多隻有1500
- private static PoolingClientConnectionManager cm;
- private static HttpParams httpParams;
- private static final String DEFAULT_ENCODING = Charset.defaultCharset()
- .name();
- // proxy代理相關配置
- private String host = "";
- private int port = 0;
- private String username = "";
- private String password = "";
- private DefaultHttpClient httpClient;
- private DefaultHttpClient proxyHttpClient;
- // 應用啟動的時候就應該執行的方法
- public HttpSyncClient() {
- this.httpClient = createClient(false);
- this.proxyHttpClient = createClient(true);
- }
- public DefaultHttpClient createClient(boolean proxy) {
- SchemeRegistry sr = new SchemeRegistry();
- sr.register(new Scheme("http", 80, PlainSocketFactory
- .getSocketFactory()));
- SSLSocketFactory sslFactory;
- try {
- SSLContext sslContext = SSLContext.getInstance("SSL");
- X509TrustManager tm = new X509TrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] chain,
- String authType) throws CertificateException {
- }
- @Override
- public void checkServerTrusted(X509Certificate[] chain,
- String authType) throws CertificateException {
- }
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return null;
- }
- };
- sslContext.init(null, new TrustManager[] { tm },
- new java.security.SecureRandom());
- sslFactory = new SSLSocketFactory(sslContext,
- SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
- sr.register(new Scheme("https", 443, sslFactory));
- } catch (Exception e) {
- e.printStackTrace();
- }
- // 初始化連線池
- cm = new PoolingClientConnectionManager(sr);
- cm.setMaxTotal(maxConnNum);
- cm.setDefaultMaxPerRoute(maxPerRoute);
- httpParams = new BasicHttpParams();
- httpParams.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
- HttpVersion.HTTP_1_1);
- httpParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
- connectTimeout);// 請求超時時間
- httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,
- socketTimeout);// 讀取資料超時時間
- // 如果啟用了NoDelay策略,httpclient和站點之間傳輸資料時將會盡可能及時地將傳送緩衝區中的資料傳送出去、而不考慮網路頻寬的利用率,這個策略適合對實時性要求高的場景
- httpParams.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);
- httpParams.setBooleanParameter(
- CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
- DefaultHttpClient httpclient = new DefaultHttpClient(cm, httpParams);
- if (proxy) {
- httpclient.getCredentialsProvider().setCredentials(
- new AuthScope(host, port),
- new UsernamePasswordCredentials(username, password));
- }
- return httpclient;
- }
- public DefaultHttpClient getHttpClient() {
- return httpClient;
- }
- public DefaultHttpClient getProxyClient() {
- return proxyHttpClient;
- }
- public String httpGet(String url, List<BasicNameValuePair> parameters) {
- DefaultHttpClient client = getHttpClient();// 預設會到池中查詢可用的連線,如果沒有就新建
- HttpGet getMethod = null;
- String returnValue = "";
- try {
- getMethod = new HttpGet(url);
- if (null != parameters) {
- String params = EntityUtils.toString(new UrlEncodedFormEntity(
- parameters, DEFAULT_ENCODING));
- getMethod.setURI(new URI(getMethod.getURI().toString() + "?"
- + params));
- logger.debug("httpGet-getUrl:{}", getMethod.getURI());
- }
- HttpResponse response = client.execute(getMethod);
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode == 200) {
- HttpEntity he = response.getEntity();
- returnValue = new String(EntityUtils.toByteArray(he),
- DEFAULT_ENCODING);
- return returnValue;
- }
- } catch (UnsupportedEncodingException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,Code error:" + e.getMessage());
- } catch (ClientProtocolException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,Protocol error:" + e.getMessage());
- } catch (IOException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,IO error:" + e.getMessage());
- } catch (URISyntaxException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,IO error:" + e.getMessage());
- } finally {// 釋放連線,將連線放回到連線池
- getMethod.releaseConnection();
- }
- return returnValue;
- }
- public String httpPost(String url, List<BasicNameValuePair> parameters,
- String requestBody) {
- DefaultHttpClient client = getHttpClient();// 預設會到池中查詢可用的連線,如果沒有就新建
- HttpPost postMethod = null;
- String returnValue = "";
- try {
- postMethod = new HttpPost(url);
- if (null != parameters) {
- String params = EntityUtils.toString(new UrlEncodedFormEntity(
- parameters, DEFAULT_ENCODING));
- postMethod.setURI(new URI(postMethod.getURI().toString() + "?"
- + params));
- logger.debug("httpPost-getUrl:{}", postMethod.getURI());
- }
- if (StringUtils.isNotBlank(requestBody)) {
- StringEntity se = new StringEntity(requestBody,
- DEFAULT_ENCODING);
- postMethod.setEntity(se);
- }
- HttpResponse response = client.execute(postMethod);
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode == 200) {
- HttpEntity he = response.getEntity();
- returnValue = new String(EntityUtils.toByteArray(he),
- DEFAULT_ENCODING);
- return returnValue;
- }
- } catch (UnsupportedEncodingException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,Code error:" + e.getMessage());
- } catch (ClientProtocolException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,Protocol error:" + e.getMessage());
- } catch (IOException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,IO error:" + e.getMessage());
- } catch (URISyntaxException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,IO error:" + e.getMessage());
- } finally {// 釋放連線,將連線放回到連線池
- postMethod.releaseConnection();
- // 釋放池子中的空閒連線
- // client.getConnectionManager().closeIdleConnections(30L,
- // TimeUnit.MILLISECONDS);
- }
- return returnValue;
- }
- }</span>
3、定義httpClient工廠類
- <span style="font-size:14px;">package com.studyproject.httpclient;
- /**
- *
- * httpclient 工廠類
- * */
- public class HttpClientFactory {
- private static HttpAsyncClient httpAsyncClient = new HttpAsyncClient();
- private static HttpSyncClient httpSyncClient = new HttpSyncClient();
- private HttpClientFactory() {
- }
- private static HttpClientFactory httpClientFactory = new HttpClientFactory();
- public static HttpClientFactory getInstance() {
- return httpClientFactory;
- }
- public HttpAsyncClient getHttpAsyncClientPool() {
- return httpAsyncClient;
- }
- public HttpSyncClient getHttpSyncClientPool() {
- return httpSyncClient;
- }
- }</span>
4、定義httpclient業務邏輯處理類,對外的方法可以通過這個類來封裝
- package com.studyproject.httpclient;
- import java.io.IOException;
- import java.net.URI;
- import java.util.List;
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpResponse;
- import org.apache.http.ParseException;
- import org.apache.http.client.entity.UrlEncodedFormEntity;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.client.methods.HttpRequestBase;
- import org.apache.http.client.protocol.HttpClientContext;
- import org.apache.http.concurrent.FutureCallback;
- import org.apache.http.impl.client.BasicCookieStore;
- import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
- import org.apache.http.message.BasicNameValuePair;
- import org.apache.http.util.EntityUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- *
- * http client 業務邏輯處理類
- * */
- public class HttpClientService {
- private static Logger LOG = LoggerFactory
- .getLogger(HttpClientService.class);
- protected void exeAsyncReq(String baseUrl, boolean isPost,
- List<BasicNameValuePair> urlParams,
- List<BasicNameValuePair> postBody, FutureCallback callback)
- throws Exception {
- if (baseUrl == null) {
- LOG.warn("we don't have base url, check config");
- throw new Exception("missing base url");
- }
- HttpRequestBase httpMethod;
- CloseableHttpAsyncClient hc = null;
- try {
- hc = HttpClientFactory.getInstance().getHttpAsyncClientPool()
- .getAsyncHttpClient();
- hc.start();
- HttpClientContext localContext = HttpClientContext.create();
- BasicCookieStore cookieStore = new BasicCookieStore();
- if (isPost) {
- httpMethod = new HttpPost(baseUrl);
- if (null != postBody) {
- LOG.debug("exeAsyncReq post postBody={}", postBody);
- UrlEncodedFormEntity entity = new UrlEncodedFormEntity(
- postBody, "UTF-8");
- ((HttpPost) httpMethod).setEntity(entity);
- }
- if (null != urlParams) {
- String getUrl = EntityUtils
- .toString(new UrlEncodedFormEntity(urlParams));
- httpMethod.setURI(new URI(httpMethod.getURI().toString()
- + "?" + getUrl));
- }
- } else {
- httpMethod = new HttpGet(baseUrl);
- if (null != urlParams) {
- String getUrl = EntityUtils
- .toString(new UrlEncodedFormEntity(urlParams));
- httpMethod.setURI(new URI(httpMethod.getURI().toString()
- + "?" + getUrl));
- }
- }
- System.out.println("exeAsyncReq getparams:" + httpMethod.getURI());
- localContext.setAttribute(HttpClientContext.COOKIE_STORE,
- cookieStore);
- hc.execute(httpMethod, localContext, callback);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- protected String getHttpContent(HttpResponse response) {
- HttpEntity entity = response.getEntity();
- String body = null;
- if (entity == null) {
- return null;
- }
- try {
- body = EntityUtils.toString(entity, "utf-8");
- } catch (ParseException e) {
- LOG.warn("the response's content inputstream is corrupt", e);
- } catch (IOException e) {
- LOG.warn("the response's content inputstream is corrupt", e);
- }
- return body;
- }
- }
5、使用httpclient,這裡只介紹了非同步的用法
- package com.studyproject.httpclient;
- import java.util.ArrayList;
- import java.util.List;
- import org.apache.http.HttpResponse;
- import org.apache.http.client.utils.HttpClientUtils;
- import org.apache.http.concurrent.FutureCallback;
- import org.apache.http.message.BasicNameValuePair;
- /**
- * http client 使用
- * */
- public class HttClientUseDemo extends HttpClientService {
- public static void main(String[] args) {
- new HttClientUseDemo().getConfCall();
- }
- public void getConfCall() {
- String url = "http://220.181.14.110/xxxxx/xxxxx/searchbyappid.do";
- List<BasicNameValuePair> urlParams = new ArrayList<BasicNameValuePair>();
- urlParams.add(new BasicNameValuePair("appid", "2"));
- exeHttpReq(url, false, urlParams, null, new GetConfCall());
- }
- public void exeHttpReq(String baseUrl, boolean isPost,
- List<BasicNameValuePair> urlParams,
- List<BasicNameValuePair> postBody,
- FutureCallback<HttpResponse> callback) {
- try {
- System.out.println("enter exeAsyncReq");
- exeAsyncReq(baseUrl, isPost, urlParams, postBody, callback);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 被回撥的物件,給非同步的httpclient使用
- *
- * */
- class GetConfCall implements FutureCallback<HttpResponse> {
- /**
- * 請求完成後呼叫該函式
- */
- @Override
- public void completed(HttpResponse response) {
- System.out.println(response.getStatusLine().getStatusCode());
- System.out.println(getHttpContent(response));
- HttpClientUtils.closeQuietly(response);
- }
- /**
- * 請求取消後呼叫該函式
- */
- @Override
- public void cancelled() {
- }
- /**
- * 請求失敗後呼叫該函式
- */
- @Override
- public void failed(Exception e) {
- }
- }
- }
上述程式碼中的有些引數,在使用的過程中需要替換。