首先请大牛们见谅菜鸟重复造轮子的学习方式,本文适合新手看~html
下面使用的同步http是HttpClient 3.X的版本,不过早已不在维护,若是刚开始使用http,建议你们都换成4.X版本,别看下面的有关同步http的部分了,4.x效率有质地提升,总结3.X只是由于无奈旧项目还在使用。后面再更新一篇有关4.x的,最新的HttpClient 4.X官方地址:http://hc.apache.org/httpcomponents-client-4.5.x/index.htmlgit
但鉴于可能有些旧的系统仍是采用3.X版本的HttpClient,因此本文仍是先记录下使用方法。github
相反下面的异步http是Async Http Client 的1.9.8版本,这个版本仍是挺好的。API请见:http://asynchttpclient.github.io/async-http-client/apidocs/com/ning/http/client/AsyncHttpClient.htmlapache
http使用场景不少,据以往经验,对于客户端来讲,咱们使用http通常会发出如下几种常见的场景:设计模式
以上的场景通常能够知足通常的需求,而后,咱们能够在这基础上扩展一点点:假如遇到一个相似于报表的子系统,主系统要在关键的逻辑链路中“打点”,经过http调用报表子系统记录一些相关的信息时,那么若是咱们使用同步http来请求报表子系统的话,一旦报表子系统挂了,那么确定会影响到主系统的运行。api
为了避免影响到主系统的运行,咱们能够采用“异步” 的方式经过http(AsyncHttpClient )请求报表子系统,那么即便子系统挂了,对主系统的关键链路的执行也不会产生多大的影响。因此,封装一个http组件,天然而然少不了封装异步http请求。而异步http所可以作的事情,也应该覆盖上面提到的几种场景。数组
再者,考虑到效率问题,除非有足够的理由,不然每次调用http接口,都建立立一个新的链接,是至关没效率的,因此MultiThreadedHttpConnectionManager 诞生了,HttpClient在内部维护一个链接池,经过MultiThreadedHttpConnectionManager 咱们能够设置“默认链接数”、“最大链接数”、“链接超时”、“读取数据超时”等等配置,从而来提升效率。服务器
废话完了,怎么实现以上需求呢。app
包的引用:异步
同步的http我使用的是org.apache.commons.httpclient的HttpClient的3.1版本。
maven配置为:
<!-- httpClient --> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency>
异步的http我使用的是com.ning.http.client的AsyncHttpClien的1.9.8版本。注意,其须要依赖几个日志相关的组件、分别为log4j、slf4j、slf4j-log4j
maven配置为:
<!-- slf4j-log4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency> <!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <!-- 异步IO --> <dependency> <groupId>com.ning</groupId> <artifactId>async-http-client</artifactId> <version>1.9.8</version> </dependency>
为了实现链接池,咱们经过一个工厂类来生成httpClient,为了上一层方便调用,咱们定义了一个接口,规范了同步、异步http应该实现的方法。包结构以下:
1、同步的HttpClient 3.X
从工厂入手,工厂负责初始化httpClient的配置,包括“默认链接数”、“最大链接数”、“链接超时”、“读取数据超时”等等,不一样的服务咱们应该建立不一样的manager,由于不可能咱们调服务A和调服务B使用同一套配置是吧,好比超时时间,应该考虑会有所差别。初始化完配置后,把 manager传到实现类,在实现类中new HttpClient。
工厂代码以下:
// 专门针对xx服务器的链接管理对象 // 由于不一样服务可能超时等参数不用,因此针对不一样服务,把链接管理对象区分开来,这只是其中一个 private static MultiThreadedHttpConnectionManager xxconnectionManager = new MultiThreadedHttpConnectionManager(); static { // 专门针对xx服务器的链接参数 xxconnectionManager = new MultiThreadedHttpConnectionManager(); HttpConnectionManagerParams paramsSearch = new HttpConnectionManagerParams(); paramsSearch.setDefaultMaxConnectionsPerHost(1000); // 默认链接数 paramsSearch.setMaxTotalConnections(1000); // 最大链接数 paramsSearch.setConnectionTimeout(30000); // 链接超时 paramsSearch.setSoTimeout(20000); // 读数据超时 xxconnectionManager.setParams(paramsSearch); } /* * 返回针对XX服务的httpClient包装类 */ public static SyncHttpClientWapperImpl getXXSearchHttpClient() { return new SyncHttpClientWapperImpl(xxconnectionManager); }
注意一点,这些链接数,超时等的配置,要作要调查工做以后再定夺,是根据访问服务的不一样,咱们本身的机器能有多少剩余的可用空间的不一样而不一样的,而不是随随便便就设置一个参数。
实现类的构造方法以下:
private HttpClient client;// httpClient private final static String CHARACTER = "UTF-8"; // 构造器,由工厂调用 public SyncHttpClientWapperImpl(MultiThreadedHttpConnectionManager connectionManager) { client = new HttpClient(connectionManager); // 字符集 client.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, CHARACTER); }
这里有一个挺困惑的点:HttpClient有必要弄成静态的吗?即直接在工厂里面为每种服务生成一个静态的HttpClient,而后传到实现类?经测试,改为静态的效率并无提升,在文件传输的测试中,甚至降低了,这个有点困惑,你们能够试一试一块儿讨论一下。
而后,在实现类中实现各类方法。
第一种,经过URL,以get方式请求服务器,返回字节数组。
public byte[] getWithQueryURL(String queryURL) throws HttpClientException { if(queryURL == null) { throw new HttpClientException("queryURL is null."); } byte[] newbuf = executeByGet(queryURL); if ((newbuf == null) || (newbuf.length == 0)) { throw new HttpClientException("Server response is null: " + queryURL); } return newbuf; } private byte[] executeByGet(String url) throws HttpClientException { HttpMethod method = new GetMethod(url); // RequestHeader method.setRequestHeader("Content-type" , "text/html; charset=UTF-8"); // 提交请求 try { client.executeMethod(method); } catch (Exception e) { method.releaseConnection(); throw new HttpClientException(url, e); } // 返回字节流 byte[] responseBody = null; try { responseBody = getBytesFromInpuStream(method.getResponseBodyAsStream()); } catch (IOException e) { throw new HttpClientException(e); } finally { method.releaseConnection(); } return responseBody; }
接着,写一个通用的流解析方法,负责把返回的流解析成字节数组。
private byte[] getBytesFromInpuStream(InputStream instream) throws IOException { ByteArrayOutputStream outstream = new ByteArrayOutputStream(); try { int length; byte[] tmp = new byte[8096]; while ((length = instream.read(tmp)) != -1) { outstream.write(tmp, 0, length); } return outstream.toByteArray(); } finally { instream.close(); outstream.close(); } }
这样就完成了最简单的get请求的调用了。
第二种:经过URL和paramsMap参数,以post方式请求服务器,返回字节数组。
public byte[] postWithParamsMap( String queryURL, Map<String,String> paramsMap) throws HttpClientException{ if(queryURL == null) { throw new HttpClientException("queryURL is null."); } byte[] newbuf = executeByPostWithParamsMap(queryURL,paramsMap); if ((newbuf == null) || (newbuf.length == 0)) { throw new HttpClientException("Server response is null: " + queryURL); } return newbuf; } private byte[] executeByPostWithParamsMap(String URL, Map<String,String> paramsMap) throws HttpClientException { PostMethod method = new PostMethod(URL); // 构造参数 if(paramsMap != null) { Set<Entry<String, String>> entrySet = paramsMap.entrySet(); Iterator<Entry<String, String>> iterator = entrySet.iterator(); NameValuePair[] nvps = new NameValuePair[paramsMap.size()]; int i = 0 ; while(iterator.hasNext()) { Entry<String, String> entry = iterator.next(); if(entry.getKey() != null) { NameValuePair nvp = new NameValuePair(entry.getKey(),entry.getValue()); nvps[i++] = nvp; } } method.setRequestBody(nvps); } // RequestHeader,key-value对的话,httpClient自动带上application/x-www-form-urlencoded method.setRequestHeader("Content-type" , "application/x-www-form-urlencoded; charset=UTF-8"); // 提交请求 try { client.executeMethod(method); } catch (Exception e) { method.releaseConnection(); throw new HttpClientException(URL, e); } // 返回字节流 byte[] responseBody = null; try { responseBody = getBytesFromInpuStream(method.getResponseBodyAsStream()); } catch (IOException e) { throw new HttpClientException(e); } finally { method.releaseConnection(); } return responseBody; }
第三种:经过URL和bytes参数,以post方式请求服务器,返回字节数组。
public byte[] postWithBytes(String queryURL , byte[] bytes) throws HttpClientException{ if(queryURL == null) { throw new HttpClientException("queryURL is null."); } byte[] newbuf = executeByPostWithBytes(queryURL,bytes); if ((newbuf == null) || (newbuf.length == 0)) { throw new HttpClientException("Server response is null: " + queryURL); } return newbuf; } private byte[] executeByPostWithBytes(String queryURL, byte[] bytes) throws HttpClientException { PostMethod method = new PostMethod(queryURL); RequestEntity requestEntity = new ByteArrayRequestEntity(bytes); method.setRequestEntity(requestEntity); // RequestHeader method.setRequestHeader("Content-type" , "text/plain; charset=UTF-8"); // 提交请求 try { client.executeMethod(method); } catch (Exception e) { method.releaseConnection(); throw new HttpClientException(queryURL, e); } // 返回字节流 byte[] responseBody = null; try { responseBody = getBytesFromInpuStream(method.getResponseBodyAsStream()); } catch (IOException e) { throw new HttpClientException(e); } finally { method.releaseConnection(); } return responseBody; }
第四种:经过URL、fileList、paramMap参数,以post方式请求服务器,返回字节数组。
public byte[] postWithFileListAndParamMap(String queryURL,List<File> fileList,Map<String,String> paramMap) throws HttpClientException, HttpException, IOException { if(queryURL == null) { throw new HttpClientException("queryURL is null."); } if(fileList == null) { throw new HttpClientException("file is null."); } if(paramMap == null){ throw new HttpClientException("paramMap is null."); } return executeByPostWithFileListAndParamMap(queryURL, fileList, paramMap); } private byte[] executeByPostWithFileListAndParamMap (String queryURL,List<File> fileList,Map<String,String> paramMap) throws HttpException, IOException, HttpClientException { if(queryURL != null && fileList != null && fileList.size() > 0) { // post方法 PostMethod method = new PostMethod(queryURL); // Part[] Part[] parts = null; if(paramMap != null) { parts = new Part[fileList.size()+paramMap.size()]; } else { parts = new Part[fileList.size()]; } int i = 0 ; // FilePart for(File file : fileList){ Part filePart = new FilePart(file.getName(),file); parts[i++] = filePart; } // StringPart if(paramMap != null ) { Set<Entry<String, String>> entrySet = paramMap.entrySet(); Iterator<Entry<String, String>> it = entrySet.iterator(); while(it.hasNext()) { Entry<String, String> entry = it.next(); Part stringPart = new StringPart(entry.getKey(),entry.getValue(),CHARACTER); parts[i++] = stringPart; } } // Entity RequestEntity requestEntity = new MultipartRequestEntity(parts, method.getParams()); method.setRequestEntity(requestEntity); // RequestHeader,文件的话,HttpClient自动加上multipart/form-data // method.setRequestHeader("Content-type" , "multipart/form-data; charset=UTF-8"); // excute try { client.executeMethod(method); } catch (Exception e) { method.releaseConnection(); throw new HttpClientException(queryURL, e); } // return byte[] responseBody = null; try { responseBody = getBytesFromInpuStream(method.getResponseBodyAsStream()); } catch (IOException e) { throw new HttpClientException(e); } finally { method.releaseConnection(); } return responseBody; } return null; }
注意Part stringPart = new StringPart(entry.getKey(),entry.getValue(),CHARACTER);这句代码,必定要加上编码格式,否则服务端会乱码。
2、异步的AsyncHttpClient
一样的,按照这种思路,异步的AsyncHttpClient也有相似的实现,不过写法不一样而已,在工厂中,AsyncHttpClient使用的是AsyncHttpClientConfig.Builder做为管理配置的类,也有相似链接超时,最大链接数等配置。
工厂类:
// 专门针对xx服务器的链接管理对象 // 由于不一样服务可能超时等参数不用,因此针对不一样服务,把链接管理对象区分开来,这只是其中一个 private static AsyncHttpClientConfig.Builder xxbuilder = new AsyncHttpClientConfig.Builder(); static { xxbuilder.setConnectTimeout(3000); // 链接超时 xxbuilder.setReadTimeout(2000); // 读取数据超时 xxbuilder.setMaxConnections(1000); // 最大链接数 } /* * 返回针对XX服务的httpClient包装类 */ public static AsyncHttpClientWapperImpl getXXSearchHttpClient() { return new AsyncHttpClientWapperImpl(xxbuilder); }
其使用了builder 的设计模式,活生生的一个例子,值得学习。
实现类的构造方法:
private AsyncHttpClient client; public AsyncHttpClientWapperImpl(Builder xxbuilder) { client = new AsyncHttpClient(xxbuilder.build()); }
这样,AsyncHttpClient对象就建立完毕了。接下来是各类场景的实现,感受异步的AsyncHttpClient封装得比HttpClient 3.X更加容易使用,设计得更好。
第一种:经过URL,以get方式请求服务器,返回字节数组。
public byte[] getWithQueryURL(String queryURL) throws HttpClientException, HttpClientException { if(queryURL == null) { throw new HttpClientException("queryURL为空"); } byte[] newbuf = executeByGet(queryURL); if ((newbuf == null) || (newbuf.length == 0)) { throw new HttpClientException("Server response is null: " + queryURL); } return newbuf; } private byte[] executeByGet(String queryURL) throws HttpClientException { byte[] responseBody = null; try { Future<Response> f = client.prepareGet(queryURL).execute(); responseBody = getBytesFromInpuStream(f.get().getResponseBodyAsStream()); } catch (Exception e) { throw new HttpClientException(e); } return responseBody; }
一样的,咱们写了一个getBytesFromInputStream()方法解析服务端返回的流,咱们发现,两个实现类里面都有一些共同的方法,这里能够考虑写一个父类,把这些方法提取出来。
第二种:经过URL和paramsMap参数,以post方式请求服务器,返回字节数组。
public byte[] postWithParamsMap(String queryURL, Map<String, String> paramsMap) throws HttpClientException { if(queryURL == null) { throw new HttpClientException("queryURL为空"); } byte[] newbuf = executeByPostByParamMap(queryURL,paramsMap); if ((newbuf == null) || (newbuf.length == 0)) { throw new HttpClientException("Server response is null: " + queryURL); } return newbuf; } private byte[] executeByPostByParamMap(String queryURL,Map<String,String> paramsMap) throws HttpClientException { byte[] responseBody = null; try { RequestBuilder requestBuilder = new RequestBuilder(); // 添加 key-value参数 if(paramsMap != null && paramsMap.size() > 0) { Set<Entry<String, String>> entrySet = paramsMap.entrySet(); Iterator<Entry<String, String>> iterator = entrySet.iterator(); while(iterator.hasNext()) { Entry<String, String> entry = iterator.next(); if(entry.getKey() != null) { requestBuilder.addFormParam(entry.getKey(), entry.getValue()); } } } // 添加RequestHeader,key requestBuilder.addHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8"); requestBuilder.setMethod("POST"); // 添加URL requestBuilder.setUrl(queryURL); // request Request request = requestBuilder.build(); // 提交 ListenableFuture<Response> f = client.executeRequest(request); responseBody = getBytesFromInpuStream(f.get().getResponseBodyAsStream()); } catch (Exception e) { throw new HttpClientException(e); } return responseBody; }
第三种:经过URL和bytes参数,以post方式请求服务器,返回字节数组。
public byte[] postWithBytes(String queryURL, byte[] bytes) throws HttpClientException { if(queryURL == null) { throw new HttpClientException("queryURL is null."); } byte[] newbuf = executeByPostWithBytes(queryURL,bytes); if ((newbuf == null) || (newbuf.length == 0)) { throw new HttpClientException("Server response is null: " + queryURL); } return newbuf; } private byte[] executeByPostWithBytes(String queryURL, byte[] bytes) throws HttpClientException { byte[] responseBody = null; try { RequestBuilder requestBuilder = new RequestBuilder(); // 添加 bytes参数 requestBuilder.setBody(bytes); // 添加RequestHeader,key requestBuilder.addHeader("Content-type", "text/plain; charset=UTF-8"); requestBuilder.setMethod("POST"); // 添加URL requestBuilder.setUrl(queryURL); // request Request request = requestBuilder.build(); // 提交 ListenableFuture<Response> f = client.executeRequest(request); responseBody = getBytesFromInpuStream(f.get().getResponseBodyAsStream()); } catch (Exception e) { throw new HttpClientException(e); } return responseBody; }
第四种:经过URL、fileList、paramMap参数,以post方式请求服务器,返回字节数组。
public byte[] postWithFileListAndParamMap(String queryURL, List<File> fileList, Map<String, String> paramMap) throws HttpClientException, HttpException, IOException { if(queryURL == null) { throw new HttpClientException("queryURL is null."); } if(fileList == null || fileList.size() == 0) { throw new HttpClientException("fileList is null."); } if(paramMap == null || paramMap.size() == 0) { throw new HttpClientException("paramMap is null."); } return executeByPostWithFileListAndParamMap(queryURL, fileList, paramMap); } private byte[] executeByPostWithFileListAndParamMap (String queryURL,List<File> fileList,Map<String,String> paramsMap) throws HttpException, IOException, HttpClientException { if(queryURL != null && fileList != null && fileList.size() > 0) { byte[] responseBody = null; try { RequestBuilder requestBuilder = new RequestBuilder(); // FilePart for(File file : fileList){ Part filePart = new FilePart(file.getName(),file); requestBuilder.addBodyPart(filePart); } // StringPart if(paramsMap != null ) { Set<Entry<String, String>> entrySet = paramsMap.entrySet(); Iterator<Entry<String, String>> it = entrySet.iterator(); while(it.hasNext()) { Entry<String, String> entry = it.next(); Part stringPart = new StringPart(entry.getKey(),entry.getValue()); requestBuilder.addBodyPart(stringPart); } } // 添加RequestHeader,key requestBuilder.addHeader("Content-type", "multipart/form-data; charset=UTF-8"); requestBuilder.setMethod("POST"); // 添加URL requestBuilder.setUrl(queryURL); // request Request request = requestBuilder.build(); // 提交 ListenableFuture<Response> f = client.executeRequest(request); responseBody = getBytesFromInpuStream(f.get().getResponseBodyAsStream()); } catch (Exception e) { throw new HttpClientException(e); } return responseBody; } return null; }
OK,入了个门后,更多的用法能够本身去看文档了,请不要局限以上几种经常使用的场景。