HttpComponents组件探究 - HttpClient篇

转自:http://blog.csdn.net/fengjia10/article/details/7315279

在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的封装,代码如下:

    

[java]  view plain copy
  1. /** 
  2.  * @author von gosling 2012-3-2 
  3.  */  
  4. public class HttpComponentsClientExecutor implements DisposableBean {  
  5.     private static final int    DEFAULT_MAX_TOTAL_CONNECTIONS     = 100;  
  6.   
  7.     private static final int    DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;                 //notice IE 6,7,8  
  8.   
  9.     private static final int    DEFAULT_CONN_TIMEOUT_MILLISECONDS = 5 * 1000;  
  10.   
  11.     private static final int    DEFAULT_READ_TIMEOUT_MILLISECONDS = 60 * 1000;  
  12.   
  13.     private static final String HTTP_HEADER_CONTENT_ENCODING      = "Content-Encoding";  
  14.     private static final String ENCODING_GZIP                     = "gzip";  
  15.   
  16.     private HttpClient          httpClient;  
  17.   
  18.     /** 
  19.      * Create a new instance of the HttpComponentsClient with a default 
  20.      * {@link HttpClient} that uses a default 
  21.      * {@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager}. 
  22.      */  
  23.     public HttpComponentsClientExecutor() {  
  24.         SchemeRegistry schemeRegistry = new SchemeRegistry();  
  25.         schemeRegistry.register(new Scheme("http"80, PlainSocketFactory.getSocketFactory()));  
  26.         schemeRegistry.register(new Scheme("https"443, SSLSocketFactory.getSocketFactory()));  
  27.   
  28.         ThreadSafeClientConnManager connectionManager = new ThreadSafeClientConnManager(  
  29.                 schemeRegistry);  
  30.         connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);  
  31.         connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);  
  32.         this.httpClient = new DefaultHttpClient(connectionManager);  
  33.   
  34.         setConnectTimeout(DEFAULT_CONN_TIMEOUT_MILLISECONDS);  
  35.         setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);  
  36.     }  
  37.   
  38.     /** 
  39.      * Create a new instance of the HttpComponentsClient with the given 
  40.      * {@link HttpClient} instance. 
  41.      *  
  42.      * @param httpClient the HttpClient instance to use for this request 
  43.      */  
  44.     public HttpComponentsClientExecutor(HttpClient httpClient) {  
  45.         Validate.notNull(httpClient, "HttpClient must not be null");  
  46.         //notice: if you want to custom exception recovery mechanism   
  47.         //you should provide an implementation of the HttpRequestRetryHandler interface.  
  48.         this.httpClient = httpClient;  
  49.     }  
  50.   
  51.     /** 
  52.      * Set the {@code HttpClient} used by this request. 
  53.      */  
  54.     public void setHttpClient(HttpClient httpClient) {  
  55.         this.httpClient = httpClient;  
  56.     }  
  57.   
  58.     /** 
  59.      * Return the {@code HttpClient} used by this request. 
  60.      */  
  61.     public HttpClient getHttpClient() {  
  62.         return this.httpClient;  
  63.     }  
  64.   
  65.     /** 
  66.      * Set the connection timeout for the underlying HttpClient. A timeout value 
  67.      * of 0 specifies an infinite timeout. 
  68.      *  
  69.      * @param timeout the timeout value in milliseconds 
  70.      */  
  71.     public void setConnectTimeout(int timeout) {  
  72.         Validate.isTrue(timeout >= 0"Timeout must be a non-negative value");  
  73.         getHttpClient().getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,  
  74.                 timeout);  
  75.     }  
  76.   
  77.     /** 
  78.      * Set the socket timeout (SO_TIMEOUT) in milliseconds, which is the timeout 
  79.      * for waiting for data or, put differently, a maximum period inactivity 
  80.      * between two consecutive data packets.A timeout value of 0 specifies an 
  81.      * infinite timeout. 
  82.      *  
  83.      * @param timeout the timeout value in milliseconds 
  84.      */  
  85.     public void setReadTimeout(int timeout) {  
  86.         Validate.isTrue(timeout >= 0"Timeout must be a non-negative value");  
  87.         getHttpClient().getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);  
  88.     }  
  89.   
  90.     /** 
  91.      * Create a Commons HttpMethodBase object for the given HTTP method and URI 
  92.      * specification. 
  93.      *  
  94.      * @param httpMethod the HTTP method 
  95.      * @param uri the URI 
  96.      * @return the Commons HttpMethodBase object 
  97.      */  
  98.     protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {  
  99.         switch (httpMethod) {  
  100.             case GET:  
  101.                 return new HttpGet(uri);  
  102.             case DELETE:  
  103.                 return new HttpDelete(uri);  
  104.             case HEAD:  
  105.                 return new HttpHead(uri);  
  106.             case OPTIONS:  
  107.                 return new HttpOptions(uri);  
  108.             case POST:  
  109.                 return new HttpPost(uri);  
  110.             case PUT:  
  111.                 return new HttpPut(uri);  
  112.             case TRACE:  
  113.                 return new HttpTrace(uri);  
  114.             default:  
  115.                 throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);  
  116.         }  
  117.     }  
  118.   
  119.     /** 
  120.      * Execute the given method on the provided URI. 
  121.      *  
  122.      * @param method the HTTP method to execute (GET, POST, etc.) 
  123.      * @param url the fully-expanded URL to connect to 
  124.      * @param responseHandler httpClient will automatically take care of 
  125.      *            ensuring release of the connection back to the connection 
  126.      *            manager regardless whether the request execution succeeds or 
  127.      *            causes an exception,if using this response handler 
  128.      * @return an response object's string representation 
  129.      * @throws IOException 
  130.      * @throws ClientProtocolException 
  131.      */  
  132.     public String doExecuteRequest(HttpMethod httpMethod, URI uri,  
  133.                                    ResponseHandler<String> responseHandler)  
  134.             throws ClientProtocolException, IOException {  
  135.         return httpClient.execute(createHttpUriRequest(httpMethod, uri), responseHandler);  
  136.     }  
  137.   
  138.     public InputStream doExecuteRequest(HttpMethod httpMethod, URI uri)  
  139.             throws ClientProtocolException, IOException {  
  140.         //1.  
  141.         HttpUriRequest httpUriRequest = createHttpUriRequest(httpMethod, uri);  
  142.         //2.  
  143.         HttpResponse response = httpClient.execute(httpUriRequest);  
  144.         //3.  
  145.         validateResponse(response);  
  146.         //4.  
  147.         return getResponseBody(response);  
  148.     }  
  149.   
  150.     /** 
  151.      * Validate the given response, throwing an exception if it does not 
  152.      * correspond to a successful HTTP response. 
  153.      * <p> 
  154.      * Default implementation rejects any HTTP status code beyond 2xx, to avoid 
  155.      * parsing the response body and trying to deserialize from a corrupted 
  156.      * stream. 
  157.      *  
  158.      * @param config the HTTP invoker configuration that specifies the target 
  159.      *            service 
  160.      * @param response the resulting HttpResponse to validate 
  161.      * @throws NoHttpResponseException 
  162.      * @throws java.io.IOException if validation failed 
  163.      */  
  164.     protected void validateResponse(HttpResponse response) throws IOException {  
  165.   
  166.         StatusLine status = response.getStatusLine();  
  167.         if (status.getStatusCode() >= 300) {  
  168.             throw new NoHttpResponseException(  
  169.                     "Did not receive successful HTTP response: status code = "  
  170.                             + status.getStatusCode() + ", status message = ["  
  171.                             + status.getReasonPhrase() + "]");  
  172.         }  
  173.     }  
  174.   
  175.     /** 
  176.      * Extract the response body 
  177.      * <p> 
  178.      * The default implementation simply fetches the response body stream. If 
  179.      * the response is recognized as GZIP response, the InputStream will get 
  180.      * wrapped in a GZIPInputStream. 
  181.      *  
  182.      * @param httpResponse the resulting HttpResponse to read the response body 
  183.      *            from 
  184.      * @return an InputStream for the response body 
  185.      * @throws java.io.IOException if thrown by I/O methods 
  186.      * @see #isGzipResponse 
  187.      * @see java.util.zip.GZIPInputStream 
  188.      */  
  189.     protected InputStream getResponseBody(HttpResponse httpResponse) throws IOException {  
  190.   
  191.         if (isGzipResponse(httpResponse)) {  
  192.             return new GZIPInputStream(httpResponse.getEntity().getContent());  
  193.         } else {  
  194.             return httpResponse.getEntity().getContent();  
  195.         }  
  196.     }  
  197.   
  198.     /** 
  199.      * Determine whether the given response indicates a GZIP response. 
  200.      * <p> 
  201.      * The default implementation checks whether the HTTP "Content-Encoding" 
  202.      * header contains "gzip" (in any casing). 
  203.      *  
  204.      * @param httpResponse the resulting HttpResponse to check 
  205.      * @return whether the given response indicates a GZIP response 
  206.      */  
  207.     protected boolean isGzipResponse(HttpResponse httpResponse) {  
  208.         Header encodingHeader = httpResponse.getFirstHeader(HTTP_HEADER_CONTENT_ENCODING);  
  209.         return (encodingHeader != null && encodingHeader.getValue() != null && encodingHeader  
  210.                 .getValue().toLowerCase().contains(ENCODING_GZIP));  
  211.     }  
  212.   
  213.     /** 
  214.      * Shutdown hook that closes the underlying 
  215.      * {@link org.apache.http.conn.ClientConnectionManager 
  216.      * ClientConnectionManager}'s connection pool, if any. 
  217.      */  
  218.     public void destroy() {  
  219.         getHttpClient().getConnectionManager().shutdown();  
  220.     }  
  221.   
  222.     enum HttpMethod {  
  223.         GET,  
  224.         POST,  
  225.         HEAD,  
  226.         OPTIONS,  
  227.         PUT,  
  228.         DELETE,  
  229.         TRACE  
  230.     }  
  231. }  

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

    

[java]  view plain copy
  1. /** 
  2.  * @author von gosling 2011-12-12 
  3.  */  
  4. public class HttpClientUtils {  
  5.   
  6.     private static final Logger log                 = LoggerFactory  
  7.                                                             .getLogger(HttpClientUtils.class);  
  8.   
  9.     private static int          timeOut             = 100;  
  10.     private static int          retryCount          = 1;  
  11.     private static int          connectionTimeout   = 100;  
  12.     private static int          maxHostConnections  = 32;                                     //根据apache work MPM设置此值  
  13.     private static int          maxTotalConnections = 512;                                    //同上  
  14.     private static String       charsetName         = "UTF-8";  
  15.   
  16.     public static JSONObject executeMethod(HttpClient httpClient, HttpMethod method) {  
  17.   
  18.         JSONObject result = new JSONObject();  
  19.         StopWatch watch = new StopWatch();  
  20.         int status = -1;  
  21.         try {  
  22.             log.info("Execute method({}) begin...", method.getURI());  
  23.   
  24.             watch.start();  
  25.             status = httpClient.executeMethod(method);  
  26.             watch.stop();  
  27.   
  28.             if (status == HttpStatus.SC_OK) {  
  29.                 InputStream inputStream = method.getResponseBodyAsStream();  
  30.                 ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  31.                 IOUtils.copy(inputStream, baos);  
  32.                 String response = new String(baos.toByteArray(), charsetName);  
  33.   
  34.                 log.info("Response is:{}", response);  
  35.   
  36.                 result = JSONObject.parseObject(response);  
  37.             } else {  
  38.                 log.error("Http request failure! status is {}", status);  
  39.             }  
  40.         } catch (SocketTimeoutException e) {  
  41.             log.error("Request time out!");//只关注请求超时,对于其它两类超时,使用通用异常捕获  
  42.         } catch (Exception e) {  
  43.             log.error("Error occur!", e);  
  44.         } finally {  
  45.             method.releaseConnection();  
  46.             log.info("Method {},statusCode {},consuming {} ms"new Object[] { method.getName(),  
  47.                     status, watch.getTime() });  
  48.         }  
  49.         return result;  
  50.     }  
  51.   
  52.     /** 
  53.      * @param uri 
  54.      * @param nameValuePairs 
  55.      * @return 
  56.      */  
  57.     public static PostMethod createPostMethod(String uri, NameValuePair[] nameValuePairs) {  
  58.         PostMethod method = new PostMethod(uri);  
  59.         method.addParameters(nameValuePairs);  
  60.         method.getParams().setContentCharset(charsetName);  
  61.         return method;  
  62.     }  
  63.   
  64.     /** 
  65.      * @param uri 
  66.      * @param nameValuePairs 
  67.      * @return 
  68.      */  
  69.     public static GetMethod createGetMethod(String uri, NameValuePair[] nameValuePairs) {  
  70.         GetMethod method = new GetMethod(uri);  
  71.         List<NameValuePair> list = Lists.newArrayList();  
  72.         if (nameValuePairs != null) {  
  73.             Collections.addAll(list, nameValuePairs);  
  74.             method.setQueryString(list.toArray(new NameValuePair[nameValuePairs.length]));  
  75.         }  
  76.         method.getParams().setContentCharset(charsetName);  
  77.         return method;  
  78.     }  
  79.   
  80.     public static HttpClient createHttpClient() {  
  81.         //1.  
  82.         HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());  
  83.   
  84.         //2.  
  85.         HttpConnectionManagerParams httpConnectionManagerParams = httpClient  
  86.                 .getHttpConnectionManager().getParams();  
  87.         httpConnectionManagerParams.setConnectionTimeout(connectionTimeout);  
  88.         httpConnectionManagerParams.setTcpNoDelay(true);//Nagle's algorithm  
  89.         httpConnectionManagerParams.setSoTimeout(timeOut);  
  90.         httpConnectionManagerParams.setDefaultMaxConnectionsPerHost(maxHostConnections);  
  91.         httpConnectionManagerParams.setMaxTotalConnections(maxTotalConnections);  
  92.   
  93.         //3.  
  94.         HttpClientParams httpClientParam = httpClient.getParams();  
  95.         //httpClientParam.setConnectionManagerTimeout(connectionTimeout);//暂且不关注这个超时设置,后面根据性能酌情考虑  
  96.         httpClientParam.setParameter(HttpMethodParams.RETRY_HANDLER,  
  97.                 new DefaultHttpMethodRetryHandler(retryCount, false));  
  98.         httpClientParam.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);  
  99.   
  100.         return httpClient;  
  101.     }  
  102.   
  103.     public static JSONObject doGet(String url, NameValuePair[] params) {  
  104.         return executeMethod(createHttpClient(), createGetMethod(url, params));  
  105.     }  
  106.   
  107.     public static JSONObject doPost(String url, NameValuePair[] params) {  
  108.         return executeMethod(createHttpClient(), createPostMethod(url, params));  
  109.     }  
  110.   
  111.     protected HttpClientUtils() {  
  112.   
  113.     }  
  114.   
  115.     public void setTimeOut(int timeOut) {  
  116.         HttpClientUtils.timeOut = timeOut;  
  117.     }  
  118.   
  119.     public static int getTimeOut() {  
  120.         return timeOut;  
  121.     }  
  122.   
  123.     public static int getRetryCount() {  
  124.         return retryCount;  
  125.     }  
  126.   
  127.     public void setRetryCount(int retryCount) {  
  128.         HttpClientUtils.retryCount = retryCount;  
  129.     }  
  130.   
  131.     public static int getConnectionTimeout() {  
  132.         return connectionTimeout;  
  133.     }  
  134.   
  135.     public void setConnectionTimeout(int connectionTimeout) {  
  136.         HttpClientUtils.connectionTimeout = connectionTimeout;  
  137.     }  
  138.   
  139.     public static int getMaxHostConnections() {  
  140.         return maxHostConnections;  
  141.     }  
  142.   
  143.     public void setMaxHostConnections(int maxHostConnections) {  
  144.         HttpClientUtils.maxHostConnections = maxHostConnections;  
  145.     }  
  146.   
  147.     public static int getMaxTotalConnections() {  
  148.         return maxTotalConnections;  
  149.     }  
  150.   
  151.     public void setMaxTotalConnections(int maxTotalConnections) {  
  152.         HttpClientUtils.maxTotalConnections = maxTotalConnections;  
  153.     }  
  154.   
  155.     public static String getCharsetName() {  
  156.         return charsetName;  
  157.     }  
  158.   
  159.     public void setCharsetName(String charsetName) {  
  160.         HttpClientUtils.charsetName = charsetName;  
  161.     }  
  162. }  

         好了,有了活生生的代码,我们来总结一下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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: commons-httpclient是一个开源的Java HTTP客户端库,它提供了一组API,可以方便地进行HTTP请求和响应的处理。它支持HTTP/1.和HTTP/1.1协议,可以进行GET、POST、PUT、DELETE等HTTP方法的请求,还支持HTTPS和代理服务器。commons-httpclient已经不再维护,推荐使用Apache HttpComponents代替。 ### 回答2: commons-httpclient是一个Java语言编写的开源HTTP客户端库,它提供了许多功能和接口,可以帮助开发者在Java应用程序中更轻松地处理HTTP请求和响应。commons-httpclient具有很多优点,例如易用性、高可靠性和良好的性能。让我们来看看这些优点的详细说明。 易用性: commons-httpclient提供了一组简单的API,可以轻松创建HTTP客户端并发送请求。开发者只需要几行代码就可以完成HTTP请求,而且API文档也非常详细,提供了很多示例代码帮助开发人员快速上手。 高可靠性: commons-httpclient实现了HTTP协议的各种细节,包括连接管理、授权、重试机制等等。这些细节对于开发者来说可能是麻烦的,但是commons-httpclient却可以自动处理这些问题,保证了HTTP请求的稳定性和可靠性。 良好的性能: commons-httpclient通过多种方式提高了性能,例如复用HTTP连接、请求优化、UA伪装等等。同时,它还提供了异步请求和管道机制,可以进一步提高HTTP请求的效率和吞吐量。 总的来说,commons-httpclient是一个非常实用的Java HTTP客户端库,可以帮助开发者更轻松地处理HTTP请求和响应。如果你需要在Java应用程序中发起HTTP请求,那么commons-httpclient绝对是一个值得考虑的选择。 ### 回答3: commons-httpclient是一个开源的HTTP客户端工具集,它能够提供很多与HTTP相关的功能,例如:发送HTTP请求消息、接收HTTP响应消息、处理HTTP请求头、处理HTTP响应头、管理HTTP连接池等等。 commons-httpclient可以用来实现HTTP访问的功能,比如通过HTTP连接访问Web服务或HTTP服务器获取数据等。HTTP连接本质上就是TCP连接,因此commons-httpclient也可以用来处理TCP连接的数据传输。 commons-httpclient提供了抽象的HttpClient类,它是对HTTP客户端的抽象,可以用来发送HTTP请求和接收HTTP响应。在使用HttpClient时,我们可以指定请求的方法(GET/POST/PUT/DELETE等)、请求头、请求体等信息。 除了HttpClient类外,commons-httpclient还提供了许多实用的类和方法,例如HttpRequestBase类、HttpResponse类、HttpMethod类、HttpConnectionManager类等等。 使用commons-httpclient可以帮助我们更容易地进行HTTP通信,以及更好地处理HTTP数据传输中的问题。它是一个强大的工具,能够大大简化我们的HTTP编程工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值