一、开始使用httpclient
本文档需要使用的依赖有如下几个:
1、创建HttpClient对象。
创建httpclient对象的方式有两种,
第一种是使用默认配置创建:
CloseableHttpClient client = HttpClients.createDefault();
第二种是使用HttpClients.custom()定制:
HttpClients.custom().setDefaultRequestConfig(RequestConfig)
这其中RequestConfig的设计方式可以学习一下。RequestConfig只有一个默认访问修饰符的构造函,这就意味着我们在使用这个类的时候不能之间构建示例;RequestConfig有很多私有属性,这些属性没有get/set方法,所以,即便是我们拿到了RequestConfig类的实例,也不能去修改其中的属性值。那么RequestConfig是如何构造实例并且为属性赋值的呢?RequestConfig在实例化的时候会利用其公共内部类Builder。使用方式为RequestConfig.custom().setXXX().builder()。调用custom方法返回一个Builder类实例,改类里有设置RequestConfig同名属性的各种方法,设置完毕后调用builder方法构造一个新的RequestConfig实例。从这个过程中可以看出来,RequestConfig对象的属性是不可变的。
有几个参数我们自己必须设置一下:
(1)connectionRequestTimeout:从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出:ConnectionPoolTimeoutException: Timeout waiting for connection from pool
(2)connectTimeout:连接上服务器(握手成功)的时间,超出该时间抛出connect timeout
(3)socketTimeout:服务器返回数据(response)的时间,超过该时间抛出read timeout
以上3个超时相关的参数如果未配置,默认为-1,意味着无限大,就是一直阻塞等待。
2、创建请求方法的实例
创建请求方法的实例并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象:
HttpGet httpGet = new HttpGet("https://www.baidu.com");
3、组装请求参数
对于get请求,可以直接在构建HttpGet对象的时候在url中加入,也可以使用URIBuilder构建参数,具体可以参考下面的两种方式:
1 URIBuilder builder = new URIBuilder("http://example.com/");
2 builder.setParameter("var1", "value1").setParameter("var2", "value2");
3 HttpGet request = new HttpGet(builder.build());
或使用了建造者模式的URIBuilder
URI uri = new URIBuilder()
.setScheme("http")
.setHost("www.example.cn")
.setPath("/search")
.setParameter("var1", "value1")
.build();
对于HttpPost对象而言,可调用setEntity(HttpEntity entity)方法来设置请求参数:
//拼接参数
List
params.add(new BasicNameValuePair("username", "vip"));
params.add(new BasicNameValuePair("password", "secret"));
httpPost.setEntity(new UrlEncodedFormEntity(params));
其中setEntity的参数种类可以有很多种,比如:
JSONObject params = new JSONObject();
params.put("var1", "value1");
params.put("var2", "value2");
httpPost.setEntity(new StringEntity(params.toJSONString(), "utf-8"));
或者使用mutltipar形式传输参数:
InputStream in = httpResponse.getEntity().getContent();
InputStreamBody inputStreamBody = new InputStreamBody(in, "filename");
HttpEntity entity = MultipartEntityBuilder.create()
.addPart("image", inputStreamBody)
.addPart("sign", new StringBody("", ContentType.APPLICATION_XML))
.build();
使用MultipartEntityBuilder需要依赖httpmime包。其中的inputStreamBody是从http查询返回中获得的一个图片的输入流。
4、发送请求
调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse:
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
5、获取返回内容
调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
// 获取响应状态码
int statusCode = httpResponse.getStatusLine().getStatusCode();
// 获取返回内容
HttpEntity httpEntity = httpResponse.getEntity();
String content = EntityUtils.toString(httpEntity);
6、释放连接。
无论执行方法是否成功,都必须释放连接
httpClient.close();
二、http连接池
1、 为什么使用连接池
降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗
支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接
2、 创建连接池
PoolingHttpClientConnectionManager cm = null;
LayeredConnectionSocketFactory sslsf = null;
try {
sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
Registry
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
三、注意事项
1、 错误关闭连接
连接池中连接都是在发起请求的时候建立,并且都是长连接。Client.java[附录]中的in.close()作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close()而仅仅使用response.close(),结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。 连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中。
2、 未关闭连接
try{
HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost request = new HttpPost(httpUrl);
StringEntity params = new StringEntity(new ObjectMapper().writeValueAsString(body), "UTF-8");
request.addHeader("content-type", "application/json");
request.setEntity(params);
httpClient.execute(request); //问题代码
} catch (Exception ex) {
logger.error("Http post error. body={}", body, ex);
}
上段代码中,httpClient.execute()执行的返回值response没有被close,导致线程一直在等待。可以改用CloseableHttpClient,并保证httpClient.execute()执行的response最终被close掉。而httpClient无需close,可以重复使用。如果使用CloseableHttpResponse,则response的close也不用显示调用。
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpPost request = new HttpPost(httpUrl);
StringEntity params = new StringEntity(new ObjectMapper().writeValueAsString(body), "UTF-8");
request.addHeader("content-type", "application/json");
request.setEntity(params);
//httpClient.execute(request);
try {
CloseableHttpResponse response = httpClient.execute(request);
//response.close()会被自动调用
} catch (Exception ex) {
logger.error("Http post execute error. body={}", body, ex);
}
四、附录
1、client.java
public class Client {
HttpConnectionManager connManager;
public
CloseableHttpClient httpClient=connManager.getHttpClient();
HttpGet httpget = new HttpGet(path);
String json=null;
CloseableHttpResponse response=null;
try {
response = httpClient.execute(httpget);
InputStream in=response.getEntity().getContent();
json=IOUtils.toString(in);
in.close();
} catch (UnsupportedOperationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(response!=null){
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return JSON.parseObject(json, clazz);
}
}
参考:
HttpClient中文网.http://www.httpclient.cn/