HttpClient4.4 进行Http连接通讯
以前一直使用jdk自带的urlConnection来进行http通讯,HttpClient与之相比,HttpClient更具有灵活度和易用性。HttpClient能够方便使用连接池,使用时需要重新创建连接,耗费巨大的连接时间。
HTTP协议
目前Http协议版本为1.1班,支持长连接,j2ee支持get,put,post,option,等方法。
- HTTP请求头
GET /link?url=chDeV_MryviuBdyQlKlkh0KwL0T4zzEU2jxSeOz2yD8ZPthCpAgRRufNz_4IKFP0AYKVcMkt2fLCmDsQ5a6m6p0PN1IcRz7KIN4R0ONTMrN97rr2JaH0bnFMD-M3Q1eX7W4b-5o-t96AIbNBOP_X05rXjAuw8bX-uNf_I3jPXAe HTTP/1.1 Host: baike.baidu.com User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:38.0) Gecko/20100101 Firefox/38.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate DNT: 1 Referer: http://www.baidu.com/link?url=chDeV_MryviuBdyQlKlkh0KwL0T4zzEU2jxSeOz2yD8ZPthCpAgRRufNz_4IKFP0AYKVcMkt2fLCmDsQ5a6m6p0PN1IcRz7KIN4R0ONTMrN97rr2JaH0bnFMD-M3Q1eX7W4b-5o-t96AIbNBOP_X05rXjAuw8bX-uNf_I3jPXAe&wd=&eqid=9adadb00000108a800000004555ea3a9 Cookie: bdshare_firstime=1423034727932; BIDUPSID=06ACBFD888B048E65E5EE3B6B3409EC8; BDUSS=XZtbDJ5eUZrcjNJTn5oTlF4dHFQQnVHbkt0TUJxZ1YwcEd4QndweW5KVWpsRzVWQVFBQUFBJCQAAAAAAAAAAAEAAACyEbBHem91cWYyMDA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMHR1UjB0dVa; ATS_PASS=9; locale=zh; BAIDUID=F3A2EE0D70C7D935C8A2F1505F627F7D:FG=1; BDRCVFR[gltLrB7qNCt]=mk3SLVN4HKm; re__f=%20; BDRCVFR[2hu4KsUxzef]=mk3SLVN4HKm; BDRCVFR[i7zL5H8GeBs]=mk3SLVN4HKm; BDRCVFR[EJVTmGcXpQm]=mk3SLVN4HKm; H_PS_PSSID=1466_14335_13518_13075_10812_12868_14167_13692_10562_12723_14156_14173_14329_12038_13937_14177_8498_14194 Connection: keep-alive
- HTTP响应头
HTTP/1.1 200 OK Server: JSP3/2.0.7 Date: Fri, 22 May 2015 06:11:46 GMT Content-Type: text/css Content-Length: 310 Connection: keep-alive Etag: "1886989" Last-Modified: Thu, 21 May 2015 01:38:06 GMT Expires: Sat, 20 Jun 2015 06:48:35 GMT Age: 84175 Cache-Control: max-age=2592000 Access-Control-Allow-Origin: * Accept-Ranges: bytes Vary: Accept-Encoding Content-Encoding: gzip
HttpClient
- HttpClient通讯示例
public class HttpTranfer { /* 定义日志管理器 */ private final static Logger logger = Logger.getLogger(HttpTranfer.class); /* Http连接池只需要创建一个,因此采用单例模式的饿汉模式 */ private static PoolingHttpClientConnectionManager httpPoolManager = null; /* HttpClient */ private CloseableHttpClient httpClient = null; /* 连接池最大生成连接数200 */ private static int Pool_MaxTotal = 0; /* 连接池默认路由最大连接数,默认为20 */ private static int Pool_MaxRoute = 0; /* 请求超时时间 */ private static int Request_TimeOut = 0; /*文件地址*/ private String fileUrl; /*判断返回的是字符串还是数据流*/ private boolean isString = true; /* 连接池参数配置 */ static { /*连接池总的最大生成连接数500 */ Pool_MaxTotal = 500; /* 连接池每个路由的最大连接数,默认为20 */ Pool_MaxRoute = 100; /* 请求超时时间 60 * 1000 */ Request_TimeOut = 60000; /* 创建连接池 */ if (httpPoolManager == null) { /* 初始化连世界管理器 */ httpPoolManager = new PoolingHttpClientConnectionManager(); // 连接池总的最大生成连接数 httpPoolManager.setMaxTotal(Pool_MaxTotal); // 设置每个route最大连接数 httpPoolManager.setDefaultMaxPerRoute(Pool_MaxRoute); } } /** * 创建HttpClient * */ public HttpClient getHttpClient() { CookieStore cookieStore = new BasicCookieStore(); CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); RequestConfig defaultRequestConfig = RequestConfig .custom() .setConnectTimeout(Request_TimeOut) .setConnectionRequestTimeout(Request_TimeOut) .setSocketTimeout(Request_TimeOut) .setCookieSpec(CookieSpecs.DEFAULT) .setExpectContinueEnabled(true) .setTargetPreferredAuthSchemes( Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST)) .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)) .build(); // 设置重定向策略 LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); // 创建httpClient CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(httpPoolManager) .setDefaultCookieStore(cookieStore) .setDefaultCredentialsProvider(credentialsProvider) .setDefaultRequestConfig(defaultRequestConfig) .setRedirectStrategy(redirectStrategy) .build(); this.httpClient = httpClient; return httpClient; } /** * form的enctype=application/x-www-form-urlencoded或text/plain的posten通讯 * * @throws IOException * @throws ClientProtocolException * */ public Object httpPostString(String url, HashMap<String, String> postMap) throws ClientProtocolException, IOException { long beginTime = System.currentTimeMillis(); /* 返回内容 */ Object returnContent = null; /* 创建HttpPost */ HttpPost httpPost = new HttpPost(url); /* 在header中添加token */ httpPost.setHeader("token", "puji"); /* 拼接Post传输参数 */ UrlEncodedFormEntity encoderFormData = formatData(postMap); httpPost.setEntity(encoderFormData); /* 执行post请求 */ CloseableHttpResponse httpResponse = (CloseableHttpResponse) getHttpClient() .execute(httpPost); /* 读取通讯返回的status code */ int responseStatus = httpResponse.getStatusLine().getStatusCode(); /* 若通讯正常 */ if (responseStatus == HttpStatus.SC_OK) { /* 获取相应的消息实体 */ HttpEntity responsehttpEntity = httpResponse.getEntity(); /* 读取utf-8格式的返回内容 */ returnContent = getResponseData(responsehttpEntity); logger.error(returnContent); } /* 关闭响应实体 */ httpResponse.close(); /* 关闭httpClient连接 */ closeHttpClient(); long endTime = System.currentTimeMillis(); /* post耗时时间 */ logger.error(String.format("===== post cost time =[%d] =====", endTime - beginTime)); return returnContent; } /** * form的multipart/form的posten通讯 multipart/form只能通过get方式传递参数 * * @throws IOException * @throws ClientProtocolException * * 目前该方法存在服务端无法接收到addPart提交的数据,需要进一步调试 * */ public Object httpPostStream(String url, HashMap<String, Object> postMap) throws ClientProtocolException, IOException { long beginTime = System.currentTimeMillis(); /* 返回内容 */ Object returnContent = null; /* 创建HttpPost */ HttpPost httpPost = new HttpPost(url); /* 在header中添加token */ httpPost.setHeader("token", "puji"); MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder .create(); /* 拼接字符串,传输参数 */ if (postMap != null && !postMap.isEmpty()) { Iterator<Entry<String, Object>> iterator = postMap.entrySet() .iterator(); while (iterator.hasNext()) { Entry<String, Object> entry = iterator.next(); String keyName = entry.getKey(); Object keyValue = entry.getValue(); ContentType contentType = ContentType.create("text/plain", Consts.UTF_8); /* 若是字符参数 */ if (keyValue instanceof String) { StringBody stringBody = new StringBody((String) keyValue, contentType); multipartEntityBuilder.addPart(keyName, stringBody); /* 文件参数 */ } else { FileBody fileBody = new FileBody((File) keyValue); multipartEntityBuilder.addPart(keyName, fileBody); } } } HttpEntity requestHttpEntity = multipartEntityBuilder.build(); httpPost.setEntity(requestHttpEntity); /* 执行post请求 */ CloseableHttpResponse httpResponse = (CloseableHttpResponse) getHttpClient() .execute(httpPost); /* 读取通讯返回的status code */ int responseStatus = httpResponse.getStatusLine().getStatusCode(); /* 若通讯正常 */ if (responseStatus == HttpStatus.SC_OK) { /* 获取相应的消息实体 */ HttpEntity responseHttpEntity = httpResponse.getEntity(); /* 读取utf-8格式的返回内容 */ returnContent = getResponseData(responseHttpEntity); logger.error(returnContent); } /* 关闭响应实体 */ httpResponse.close(); /* 关闭httpClient连接 */ closeHttpClient(); long endTime = System.currentTimeMillis(); /* post耗时时间 */ logger.error(String.format("===== post cost time =[%d] =====", endTime - beginTime)); return returnContent; } /** * get通讯 * * @throws IOException * @throws ClientProtocolException * */ public Object httpGet(String url) throws ClientProtocolException, IOException { long beginTime = System.currentTimeMillis(); /* 返回内容 */ Object returnContent = null; /* 创建HttpGet */ HttpGet httpGet = new HttpGet(url); /* 执行请求 */ CloseableHttpResponse httpResponse = (CloseableHttpResponse) getHttpClient() .execute(httpGet); // 获取响应状态码 int statusCode = httpResponse.getStatusLine().getStatusCode(); /* 通讯正常 */ if (statusCode == HttpStatus.SC_OK) { /* 获得响应的消息实体 */ HttpEntity responseHttpEntity = httpResponse.getEntity(); /* 读取utf-8格式的返回内容 */ returnContent = getResponseData((HttpEntity) responseHttpEntity); logger.error(returnContent); } /* 关闭响应实体 */ httpResponse.close(); /* 关闭httpClient连接 */ closeHttpClient(); long endTime = System.currentTimeMillis(); /* post耗时时间 */ logger.error(String.format("===== post cost time =[%d] =====", endTime - beginTime)); return returnContent; } /* 拼接Post传输参数,并设定为utf-8,只支持拼接String的字符,不支持流模式 */ private UrlEncodedFormEntity formatData(HashMap<String, String> postMap) throws UnsupportedEncodingException { List<NameValuePair> nvps = new ArrayList<NameValuePair>(); if (postMap != null && !postMap.isEmpty()) { Iterator<Entry<String, String>> iterator = postMap.entrySet() .iterator(); while (iterator.hasNext()) { Map.Entry<String, String> map = iterator.next(); String keyName = (String) map.getKey(); String keyValue = (String) map.getValue(); nvps.add(new BasicNameValuePair(keyName, keyValue)); } } UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(nvps, "UTF-8"); return uefEntity; } /* 判断返回的http实体是字符串还是流,还是文件 */ private Object getResponseData(HttpEntity responseHttpEntity) throws ParseException, IOException { if (responseHttpEntity != null) { Header contentType = responseHttpEntity.getContentType(); if (contentType != null) { /* 若是数据流 */ if (contentType.getValue().indexOf("application/octet-stream") >= 0) { isString = false; InputStream inputStream = responseHttpEntity.getContent(); /*数据流处理*/ File file = doInputStream(inputStream); EntityUtils.consume(responseHttpEntity); return file; } else { /* 对返回的内容进行编码 */ String responseContent = EntityUtils.toString( responseHttpEntity, "UTF-8"); EntityUtils.consume(responseHttpEntity); return responseContent; } } else { return null; } } else { return null; } } /*返回数据流处理*/ private File doInputStream(InputStream inputStream) throws IOException{ int size = 0; byte[] buffer = new byte[4096]; if(fileUrl != null && fileUrl.length() > 0){ File file = new File(fileUrl); FileOutputStream fileOutputStream = new FileOutputStream(file); while((size = inputStream.read(buffer)) != -1){ fileOutputStream.write(buffer, 0, size); } fileOutputStream.close(); inputStream.close(); return file; }else{ while((size = inputStream.read(buffer)) != -1){} inputStream.close(); return null; } } /*关闭客户端*/ private void closeHttpClient() throws IOException { //httpClient.close(); } /*设定文件地址*/ public void setFileUrl(String fileUrl){ this.fileUrl = fileUrl; } /*判断返回的是字符串还是数据流*/ public boolean isString(){ return this.isString; } }
PoolingHttpClientConnectionManager连接池中HttpClient默认的路由参数为2个,默认的最大连接数为20个。连接池中路由的概念就是客户端连接到目标服务器的通道。比如client 连接到 目标服务器A 和client连接到目标服务器B,表示client 有两个路由通道。如果是单台目标服务器连接池中的路由最大连接参数参数和最大连接数可以设置一致。
若是多台目标服务器,PoolingHttpClientConnectionManager中的每个路由连接数参数不能过小,否则即使最大连接数设置很大,但是每个路由连接参数偏小,仍然无法实现并发效果。比如按照默认设定连接池参数,目标服务器为一个,那么最大并发连接数只能为2个,那么剩下的18个连接只能空闲等待,并非为这台目标服务器工作。
Servlet3.0后台接收文件
要实现Servlet 3.0的原生态文件上传支持,需要配置@MultipartConfig注解。配置完毕后,可以通过request.getPart(“inputName”)接收上传文件。若是多文件上传可以通过遍历request.getParts()接收文件。
1、@MultipartConfig有四个属性
fileSizeThreshold:当数据量大于该值时,内容将被写入文件。
location:存放生成的文件地址。当调用完write()方法后,自动删除该地址文件
maxFileSize:允许上传的文件最大值。默认值为 -1,表示没有限制。
maxRequestSize:针对该 multipart/form-data 请求的最大数量,默认值为 -1,表示没有限制。
2、通过getPart()或getParts()接收上传文件
/*服务端遍历所有上传数据*/ try { /*文件存储路径*/ String path = "/home/files"; Iterator<Part>iterator=request.getParts().iterator(); while (iterator.hasNext()) { Part part = (Part) iterator.next(); String partName = part.getName(); /*若是input的name属性含有file 表示是file域, name属性在客户端中根据value类型设定*/ if(partName.indexOf("file") >= 0){ /*获取文件名*/ String header = part.getHeader("content-disposition"); String fileName = header.substring(header.indexOf("filename=\"") + 10, header.lastIndexOf("\"")); String filePath = path + fileName; part.write(filePath) }else{ /*输出字符串参数*/ String value = ""; InputStream inputStream = part.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); while ((value += bufferedReader.readLine()) != null) {} System.out.println("________________value="+value); } } }catch (Exception e) { e.printStackTrace(); }
指定文件接收路径也可以通过@MultipartConfig(location="/home/files")方式来指定
1、request.getParameter("name") 可以获取到通过addTextBody()方法添加的String类型的数据 。
2、request.getPart("name") 可以获取到通过addPart()、addBinaryBody()、addTextBody()添加的数据,返回类型为Part。
3、request.getParts() 得到Collection<Part>类型对象,可以接受addBinaryBody()、addPart()、addTextBody()添加的数据。
4、上面接收文件也可以通过FileOutputStream 接收
private void write(String fileName, InputStream in, String path) throws IOException, FileNotFoundException { OutputStream out = new FileOutputStream(filePath + filename); byte[] buffer = new byte[1024]; int length = -1; while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } in.close(); out.close(); }
Servletf服务端返回文件流
String url = "D:\\rrtong\\xvid1.h"; File file = new File(url); String fileName = file.getName(); int fileSize = (int) file.length(); /*定义返回的编码*/ response.setStatus(200); /*定义编码格式*/ response.setCharacterEncoding("UTF-8"); /*定义内容类型,application/octet-stream表示通用类型*/ response.setContentType("application/octet-stream; charset=utf-8"); /*定义返回文件名称*/ response.setHeader("Content-disposition", "attachment; filename="+ fileName); /*定义文件长度*/ response.setIntHeader("Content_Length", fileSize); /*生成文件流*/ InputStream fileInputStrream = new FileInputStream(file); OutputStream outputStream = response.getOutputStream(); /*输出文件流*/ int byteSize = 0; byte arrByte[] = new byte[1024]; while((byteSize = fileInputStrream.read(arrByte)) != -1){ outputStream.write(arrByte,0, byteSize); } outputStream.flush(); fileInputStrream.close(); outputStream.close();
- 参考资料
http://backend.blog.163.com/blog/static/2022941262014029105618173/
http://niuzhenxin.iteye.com/blog/2100100
http://www.yeetrack.com/?p=773
http://hc.apache.org/httpcomponents-client-4.4.x/httpclient/apidocs/org/apache/http/client/methods/CloseableHttpResponse.html
http://blog.csdn.net/fengyuzhengfan/article/details/39941851
http://www.cppblog.com/iuranus/archive/2010/07/04/119311.html
http://www.yeetrack.com/?p=773
http://www.yeetrack.com/?p=782
http://www.yeetrack.com/?p=822
http://www.yeetrack.com/?p=825
http://www.yeetrack.com/?p=832
http://www.yeetrack.com/?p=844