在Java领域,谈到网络编程,可能大家脑海里第一反应就是MINA,NETTY,GRIZZLY等优秀的开源框架。没错,不过在深入探究这些框架之前,我们需要先从最original的技术探究开始(当然,需要大家先熟悉java.net.*类库)。这里,我要和大家分享一下HttpComponents项目的部分组件特性。HttpClient,想必大家早都接触过了吧。HttpComponents和HttpClient的”血缘“有点像guava和google-collection的关系。目前,HttpComponents已经是Apache的顶级项目了,它旨在为我们提供一个Http协议相关的Java平台工具集。它的代码组织很精妙,主要分两部分,一部分是核心工具集(包括HttpCore-bio,HttpCore-nio,HttpClient,HttpMIme,HttpCookie等),一部分是扩展工具集(目前主要包括ssl)

HttpClient主要包括Connection management,Status management,Authentication Management三部分。下面给出对它的二次封装,经过了线上的接近半年的验证(这里指的是httpClient 3,httpClient 4还有待检验),可以看做是一个高性能的Client封装吧。感兴趣的朋友可以根据apache的MPM IO模型进行部分参数的调整。

先来段httpClient 4的封装,代码如下:

/**
* @author von gosling 2012-3-2
*/
public class HttpComponentsClientExecutor implements DisposableBean {
private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100; private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5; //notice IE 6,7,8 private static final int DEFAULT_CONN_TIMEOUT_MILLISECONDS = 5 * 1000; private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60 * 1000; private static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
private static final String ENCODING_GZIP = "gzip"; private HttpClient httpClient; /**
* Create a new instance of the HttpComponentsClient with a default
* {@link HttpClient} that uses a default
* {@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager}.
*/
public HttpComponentsClientExecutor() {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); ThreadSafeClientConnManager connectionManager = new ThreadSafeClientConnManager(
schemeRegistry);
connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
this.httpClient = new DefaultHttpClient(connectionManager); setConnectTimeout(DEFAULT_CONN_TIMEOUT_MILLISECONDS);
setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
} /**
* Create a new instance of the HttpComponentsClient with the given
* {@link HttpClient} instance.
*
* @param httpClient the HttpClient instance to use for this request
*/
public HttpComponentsClientExecutor(HttpClient httpClient) {
Validate.notNull(httpClient, "HttpClient must not be null");
//notice: if you want to custom exception recovery mechanism
//you should provide an implementation of the HttpRequestRetryHandler interface.
this.httpClient = httpClient;
} /**
* Set the {@code HttpClient} used by this request.
*/
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
} /**
* Return the {@code HttpClient} used by this request.
*/
public HttpClient getHttpClient() {
return this.httpClient;
} /**
* Set the connection timeout for the underlying HttpClient. A timeout value
* of 0 specifies an infinite timeout.
*
* @param timeout the timeout value in milliseconds
*/
public void setConnectTimeout(int timeout) {
Validate.isTrue(timeout >= 0, "Timeout must be a non-negative value");
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
timeout);
} /**
* Set the socket timeout (SO_TIMEOUT) in milliseconds, which is the timeout
* for waiting for data or, put differently, a maximum period inactivity
* between two consecutive data packets.A timeout value of 0 specifies an
* infinite timeout.
*
* @param timeout the timeout value in milliseconds
*/
public void setReadTimeout(int timeout) {
Validate.isTrue(timeout >= 0, "Timeout must be a non-negative value");
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
} /**
* Create a Commons HttpMethodBase object for the given HTTP method and URI
* specification.
*
* @param httpMethod the HTTP method
* @param uri the URI
* @return the Commons HttpMethodBase object
*/
protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
switch (httpMethod) {
case GET:
return new HttpGet(uri);
case DELETE:
return new HttpDelete(uri);
case HEAD:
return new HttpHead(uri);
case OPTIONS:
return new HttpOptions(uri);
case POST:
return new HttpPost(uri);
case PUT:
return new HttpPut(uri);
case TRACE:
return new HttpTrace(uri);
default:
throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
}
} /**
* Execute the given method on the provided URI.
*
* @param method the HTTP method to execute (GET, POST, etc.)
* @param url the fully-expanded URL to connect to
* @param responseHandler httpClient will automatically take care of
* ensuring release of the connection back to the connection
* manager regardless whether the request execution succeeds or
* causes an exception,if using this response handler
* @return an response object's string representation
* @throws IOException
* @throws ClientProtocolException
*/
public String doExecuteRequest(HttpMethod httpMethod, URI uri,
ResponseHandler<String> responseHandler)
throws ClientProtocolException, IOException {
return httpClient.execute(createHttpUriRequest(httpMethod, uri), responseHandler);
} public InputStream doExecuteRequest(HttpMethod httpMethod, URI uri)
throws ClientProtocolException, IOException {
//1.
HttpUriRequest httpUriRequest = createHttpUriRequest(httpMethod, uri);
//2.
HttpResponse response = httpClient.execute(httpUriRequest);
//3.
validateResponse(response);
//4.
return getResponseBody(response);
} /**
* Validate the given response, throwing an exception if it does not
* correspond to a successful HTTP response.
* <p>
* Default implementation rejects any HTTP status code beyond 2xx, to avoid
* parsing the response body and trying to deserialize from a corrupted
* stream.
*
* @param config the HTTP invoker configuration that specifies the target
* service
* @param response the resulting HttpResponse to validate
* @throws NoHttpResponseException
* @throws java.io.IOException if validation failed
*/
protected void validateResponse(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine();
if (status.getStatusCode() >= 300) {
throw new NoHttpResponseException(
"Did not receive successful HTTP response: status code = "
+ status.getStatusCode() + ", status message = ["
+ status.getReasonPhrase() + "]");
}
} /**
* Extract the response body
* <p>
* The default implementation simply fetches the response body stream. If
* the response is recognized as GZIP response, the InputStream will get
* wrapped in a GZIPInputStream.
*
* @param httpResponse the resulting HttpResponse to read the response body
* from
* @return an InputStream for the response body
* @throws java.io.IOException if thrown by I/O methods
* @see #isGzipResponse
* @see java.util.zip.GZIPInputStream
*/
protected InputStream getResponseBody(HttpResponse httpResponse) throws IOException { if (isGzipResponse(httpResponse)) {
return new GZIPInputStream(httpResponse.getEntity().getContent());
} else {
return httpResponse.getEntity().getContent();
}
} /**
* Determine whether the given response indicates a GZIP response.
* <p>
* The default implementation checks whether the HTTP "Content-Encoding"
* header contains "gzip" (in any casing).
*
* @param httpResponse the resulting HttpResponse to check
* @return whether the given response indicates a GZIP response
*/
protected boolean isGzipResponse(HttpResponse httpResponse) {
Header encodingHeader = httpResponse.getFirstHeader(HTTP_HEADER_CONTENT_ENCODING);
return (encodingHeader != null && encodingHeader.getValue() != null && encodingHeader
.getValue().toLowerCase().contains(ENCODING_GZIP));
} /**
* Shutdown hook that closes the underlying
* {@link org.apache.http.conn.ClientConnectionManager
* ClientConnectionManager}'s connection pool, if any.
*/
public void destroy() {
getHttpClient().getConnectionManager().shutdown();
} enum HttpMethod {
GET,
POST,
HEAD,
OPTIONS,
PUT,
DELETE,
TRACE
}
}

下面是久经考验的httpClient 3的二次封装,如下:

/**
* @author von gosling 2011-12-12
*/
public class HttpClientUtils { private static final Logger log = LoggerFactory
.getLogger(HttpClientUtils.class); private static int timeOut = 100;
private static int retryCount = 1;
private static int connectionTimeout = 100;
private static int maxHostConnections = 32; //根据apache work MPM设置此值
private static int maxTotalConnections = 512; //同上
private static String charsetName = "UTF-8"; public static JSONObject executeMethod(HttpClient httpClient, HttpMethod method) { JSONObject result = new JSONObject();
StopWatch watch = new StopWatch();
int status = -1;
try {
log.info("Execute method({}) begin...", method.getURI()); watch.start();
status = httpClient.executeMethod(method);
watch.stop(); if (status == HttpStatus.SC_OK) {
InputStream inputStream = method.getResponseBodyAsStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(inputStream, baos);
String response = new String(baos.toByteArray(), charsetName); log.info("Response is:{}", response); result = JSONObject.parseObject(response);
} else {
log.error("Http request failure! status is {}", status);
}
} catch (SocketTimeoutException e) {
log.error("Request time out!");//只关注请求超时,对于其它两类超时,使用通用异常捕获
} catch (Exception e) {
log.error("Error occur!", e);
} finally {
method.releaseConnection();
log.info("Method {},statusCode {},consuming {} ms", new Object[] { method.getName(),
status, watch.getTime() });
}
return result;
} /**
* @param uri
* @param nameValuePairs
* @return
*/
public static PostMethod createPostMethod(String uri, NameValuePair[] nameValuePairs) {
PostMethod method = new PostMethod(uri);
method.addParameters(nameValuePairs);
method.getParams().setContentCharset(charsetName);
return method;
} /**
* @param uri
* @param nameValuePairs
* @return
*/
public static GetMethod createGetMethod(String uri, NameValuePair[] nameValuePairs) {
GetMethod method = new GetMethod(uri);
List<NameValuePair> list = Lists.newArrayList();
if (nameValuePairs != null) {
Collections.addAll(list, nameValuePairs);
method.setQueryString(list.toArray(new NameValuePair[nameValuePairs.length]));
}
method.getParams().setContentCharset(charsetName);
return method;
} public static HttpClient createHttpClient() {
//1.
HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); //2.
HttpConnectionManagerParams httpConnectionManagerParams = httpClient
.getHttpConnectionManager().getParams();
httpConnectionManagerParams.setConnectionTimeout(connectionTimeout);
httpConnectionManagerParams.setTcpNoDelay(true);//Nagle's algorithm
httpConnectionManagerParams.setSoTimeout(timeOut);
httpConnectionManagerParams.setDefaultMaxConnectionsPerHost(maxHostConnections);
httpConnectionManagerParams.setMaxTotalConnections(maxTotalConnections); //3.
HttpClientParams httpClientParam = httpClient.getParams();
//httpClientParam.setConnectionManagerTimeout(connectionTimeout);//暂且不关注这个超时设置,后面根据性能酌情考虑
httpClientParam.setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(retryCount, false));
httpClientParam.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); return httpClient;
} public static JSONObject doGet(String url, NameValuePair[] params) {
return executeMethod(createHttpClient(), createGetMethod(url, params));
} public static JSONObject doPost(String url, NameValuePair[] params) {
return executeMethod(createHttpClient(), createPostMethod(url, params));
} protected HttpClientUtils() { } public void setTimeOut(int timeOut) {
HttpClientUtils.timeOut = timeOut;
} public static int getTimeOut() {
return timeOut;
} public static int getRetryCount() {
return retryCount;
} public void setRetryCount(int retryCount) {
HttpClientUtils.retryCount = retryCount;
} public static int getConnectionTimeout() {
return connectionTimeout;
} public void setConnectionTimeout(int connectionTimeout) {
HttpClientUtils.connectionTimeout = connectionTimeout;
} public static int getMaxHostConnections() {
return maxHostConnections;
} public void setMaxHostConnections(int maxHostConnections) {
HttpClientUtils.maxHostConnections = maxHostConnections;
} public static int getMaxTotalConnections() {
return maxTotalConnections;
} public void setMaxTotalConnections(int maxTotalConnections) {
HttpClientUtils.maxTotalConnections = maxTotalConnections;
} public static String getCharsetName() {
return charsetName;
} public void setCharsetName(String charsetName) {
HttpClientUtils.charsetName = charsetName;
}
}

 

好了,有了活生生的代码,我们来总结一下httpClient封装过程中需要注意的一些事项吧。恩,其实更多的是体现在安全,性能上面:

(1)多线程模型,尤其注意finally中collection的释放问题。除此之外,需要考虑池化连接的异常处理,这是我文中提到特别注意的三大异常之一;

(2)Retry机制中对幂等性的处理。尤其是在httpClient4中,put和post操作,未按照http规范行事,需要我们额外注意;

(3)SSL、TLS的定制化处理;

(4)并发标记的处理,这里使用了Concurrency in practice中的并发annotation,有什么用?感兴趣的朋友可以了解下SureLogic(http://www.surelogic.com/concurrency-tools.html),别问我要license,因为俺也不是apache开源社区的developer呀;

(5)拦截器对header的处理;

(6)collection stale check机制;

(7)Cookie specification choose或者是自定义实现;

恩,今天就写到这里吧。感谢大家的阅读,如果哪里有疑问,欢迎留言~

参考文献:

1.http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html

2.http://hc.apache.org/httpcomponents-client-ga/tutorial/pdf/httpclient-tutorial.pdf

HttpComponents组件探究 - HttpClient篇的更多相关文章

  1. 数百个 HTML5 例子学习 HT 图形组件 – 3D建模篇

    http://www.hightopo.com/demo/pipeline/index.html <数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇>里提到 HT 很 ...

  2. 数百个 HTML5 例子学习 HT 图形组件 – 3D 建模篇

    http://www.hightopo.com/demo/pipeline/index.html <数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇>里提到 HT 很 ...

  3. [转帖]虚拟内存探究 -- 第二篇:Python 字节

    虚拟内存探究 -- 第二篇:Python 字节 http://blog.coderhuo.tech/2017/10/15/Virtual_Memory_python_bytes/ 是真看不懂哦     ...

  4. [转帖]虚拟内存探究 -- 第一篇:C strings & /proc

    虚拟内存探究 -- 第一篇:C strings & /proc http://blog.coderhuo.tech/2017/10/12/Virtual_Memory_C_strings_pr ...

  5. Java:HttpClient篇,HttpClient4.2在Java中的几则应用:Get、Post参数、Session(会话)保持、Proxy(代理服务器)设置,多线程设置...

    新版HttpClient4.2与之前的3.x版本有了很大变化,建议从http://hc.apache.org/处以得到最新的信息. 关于HttpCore与HttpClient:HttpCore是位于H ...

  6. 数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇

    <数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇>一文让读者了解了 HT的 2D 拓扑图组件使用,本文将对 HT 的 3D 功能做个综合性的介绍,以便初学者可快速上手使用 HT ...

  7. COM组件(ATL篇)

    目录 第1章创建进程内组件    1 1.1 目标    1 1.2 创建项目    3 1.2.1 VC++6.0    3 1.2.2 VC++2010    5 1.3 增加COM类    6 ...

  8. 漫谈Nuclear Web组件化入门篇

    目前来看,团队内部前端项目已全面实施组件化开发.组件化的好处太多,如:按需加载.可复用.易维护.可扩展.少挖坑.不改组件代码直接切成服务器端渲染(如Nuclear组件化可以做到,大家叫同构)... 怎 ...

  9. COM组件(MFC篇)

    目录 第1章创建进程内组件    1 1.1 目标    1 1.2 创建项目    3 1.2.1 VC++6.0    3 1.2.2 VC++2010    4 1.2.3 VC++6.0与VC ...

随机推荐

  1. java中File的delete和deleteOnExit区别(转)

    Java的File类中有两个delete方法:delete和deleteOnExit delete无需解释,为直接删除,deleteOnExit文档解释为:在虚拟机终止时,请求删除此抽象路径名表示的文 ...

  2. cmd运行命令

    winver检查Windows版本 dxdiag检查DirectX信息 mem.exe显示内存使用情况 Sndvol32音量控制程序 sfc.exe系统文件检查器 gpedit.msc 组策略 reg ...

  3. Apache Struts 2 Documentation Big Picture

    http://struts.apache.org/docs/big-picture.html 1. HttpServletRequest 穿越各个过滤器到达FilterDispatcher(这个已经不 ...

  4. 字符串"k:1“” 处理成字典 {'k':1,'k1':2....}

    1.有字符串"k:1|k1:2|k2:3|k3:4" 处理成字典 {'k':1,'k1':2....} #第一种方法 s1 = "k:1|k1:2|k2:3|k3:4&q ...

  5. 一种基于Redis的10行代码实现IP频率控制方法

    优点:可支持海量访问的频率控制,只需要增加Redis机器,单个Redis节点(只占用一个cpu core)即可支持10万/s以上的处理. 基于IP频率限制是种常见需求,基于Redis可以十分简单实现对 ...

  6. virtualenv 安装及使用[转]

    一如既往,官方文档: https://virtualenv.pypa.io/en/latest/   下载地址:https://pypi.python.org/pypi/virtualenv#down ...

  7. oracle中row_number() over()分析函数用法

    row_number()over(partition by col1 order by col2)表示根据col1分组,在分组内部根据col2排序,而此函数计算的值就表示每组内部排序后的顺序编号(组内 ...

  8. 洛谷P1600 天天爱跑步(线段树合并)

    小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 nn ...

  9. hdu 3664 Permutation Counting(水DP)

    Permutation Counting Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  10. 破解Oracle ERP密码

    前提:你有apps的数据库账户,想知道某个用户的密码,因为fnd_user中的密码为加密的,所以无法看懂,你可以尝试用下边的方式来查看用户密码. SQL> desc fnd_user; Name ...