1.0 一个简单的Demo程序
下面是一个HttpClient通过Get方法访问www.baidu.com的请求Demo。我们将根据这个Demo介绍HttpClient的一些基本使用。
public class HttpClientDemo {
CloseableHttpClient httpClient;
@Before
public void init() {
httpClient = HttpClients.createDefault();
}
/**
* httpClient执行get请求
*/
@Test
public void getMethodDemo() throws IOException {
HttpGet httpget = new HttpGet("http://www.baidu.com/");
CloseableHttpResponse response = httpClient.execute(httpget);
try {
// TODO
HttpEntity entity = response.getEntity(); //http实体
InputStream inputStream = entity.getContent();
String str = IOUtils.toString(inputStream, "utf-8");
System.out.println(str);
EntityUtils.consume(entity); //关闭了entity.getContent()的流 如果未关闭,则connection manager会关闭并丢弃这个连接
System.out.println(response);
} finally {
response.close(); //关闭Http连接,execute方法中应该已经关闭
httpClient.close();
}
}
}
1.1 Http请求
Http请求包含三部分:方法名称(Get,Post等),请求URI和HTTP协议版本。
HttpClient为每一个Http方法封装了一个类,例如常用的HttpGet,HttpPost。我们可以通过下面的方式来创建一个Http请求:
HttpGet httpget = new HttpGet(
"http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
这样直接将请求固定在字符串中。事实上,一个Request-URI由协议方案,主机名,可选端口,资源路径,可选查询和可选片段组成。我们也可以通过这样的方式创建请求:
URI uri = new URIBuilder()
.setScheme("http")
.setHost("www.google.com")
.setPath("/search")
.setParameter("q", "httpclient")
.setParameter("btnG", "Google Search")
.setParameter("aq", "f")
.setParameter("oq", "")
.build();
HttpGet httpget = new HttpGet(uri);
1.2 HttpEntity
HTTP消息可以携带与请求或响应相关联的内容实体。HTTP规范定义了两个实体封装请求方法:POST和 PUT。响应通常期望包含内容实体。
1.2.1 创建HttpEntity
我们可以通过下面的方式创建一个HttpEntity。在HttpClient中有几种内容实体的类:StringEntity, ByteArrayEntity,InputStreamEntity和 FileEntity。
StringEntity myEntity = new StringEntity("important message",
ContentType.create("text/plain", "UTF-8"));
使用较多的是Post提交表单的情况,HttpClient提供实体类 UrlEncodedFormEntity来处理这种情况:
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
1.2.1 读取HttpEntity内容
要从实体读取内容,可以通过HttpEntity#getContent()方法来获取输入流,该方法返回一个java.io.InputStream。我们可以通过下面的方式读取HttpEntity的内容。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
long len = entity.getContentLength();
if (len != -1 && len < 2048) {
System.out.println(EntityUtils.toString(entity));
} else {
// Stream content out
}
}
} finally {
response.close();
}
HttpClient提供了对HttpEntity操作的工具类EntityUtils。我们可以使用EntityUtils#toString()方法来将InputStream转换成一个String(apache.commons.io的IOUtils#toString()方法也可以实现相同的功能)
多次读取实体内容*
由于InputStream是不能够多次读取的,如果我们需要多次读取实体内容,可以使用BufferedHttpEntity类包装原始实体。
HttpEntity entity = response.getEntity();
if (entity != null) {
entity = new BufferedHttpEntity(entity);
}
1.2.2 HttpEntity关闭
关闭内容流HttpEntity#getContent()和关闭response之间的区别是,前者将尝试通过占用实体内容来保持底层连接,而后者会立即关闭并丢弃连接。
当使用流实体时,可以使用该 EntityUtils#consume(HttpEntity)方法来确保实体内容已被完全消耗,底层流已经被关闭。如果不使用in.close(),而仅仅使用response.close(),结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。
1.3 处理HttpResponse
处理响应的最简单和最方便的方法是使用ResponseHandler包含该handleResponse(HttpResponse response)方法的接口。该方法完全可以缓解用户不必担心连接管理。使用ResponseHandlerHttpClient 时 ,无论请求执行是成功还是导致异常,HttpClient都将自动保证将连接释放回连接管理器。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json");
ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
@Override
public JsonObject handleResponse(
final HttpResponse response) throws IOException {
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
throw new HttpResponseException(
statusLine.getStatusCode(),
statusLine.getReasonPhrase());
}
if (entity == null) {
throw new ClientProtocolException("Response contains no content");
}
Gson gson = new GsonBuilder().create();
ContentType contentType = ContentType.getOrDefault(entity);
Charset charset = contentType.getCharset();
Reader reader = new InputStreamReader(entity.getContent(), charset);
return gson.fromJson(reader, MyJsonObject.class);
}
};
MyJsonObject myjson = client.execute(httpget, rh);
1.4 自定义HttpClient
之前我们都通过HttpClients#createDefault()方法来创建HttpClient,实际使用时我们希望订制适用于业务场景的HttpClient。例如连接时间的管理,见下面的例子:
ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(
HttpResponse response,
HttpContext context) {
long keepAlive = super.getKeepAliveDuration(response, context);
if (keepAlive == -1) {
// Keep connections alive 5 seconds if a keep-alive value
// has not be explicitly set by the server
keepAlive = 5000;
}
return keepAlive;
}
};
CloseableHttpClient httpclient = HttpClients.custom() //自定义HttpClient
.setKeepAliveStrategy(keepAliveStrat)
.build();
HttpClient是线程安全的,建议将此类的同一个实例重用于多个请求执行,而不是发起一次请求就创建一个HttpClient。
当一个实例CloseableHttpClient不再需要时,与它关联的连接管理器必须通过调用该CloseableHttpClient#close() 方法来关闭。
1.5 HttpContext
最初HTTP被设计为无状态的,响应请求的协议。对于需要多次交互的请求,我们需要在连续请求之间重复使用相同的上下文,则多个逻辑相关请求可以参与逻辑会话。
在HTTP请求执行过程中,HttpClient将以下属性添加到执行上下文中:
- HttpConnection 表示与目标服务器的实际连接的实例
- HttpHost 表示连接目标的实例
- HttpRoute 表示完整的连接路由的实例
- HttpRequest表示实际HTTP请求的实例。执行上下文中的最终HttpRequest对象总是表示消息的状态与发送到目标服务器的状态完全相同。默认HTTP / 1.0和HTTP / 1.1使用相对请求URI
- HttpResponse 表示实际的HTTP响应。
- java.lang.Boolean 表示表示实际请求是否被完全发送到连接目标的标志的对象。
- RequestConfig 表示实际请求配置的对象。
- java.util.List 表示在请求执行过程中接收到的所有重定向位置的集合的对象。
需要说明的是,HttpContext不是线程安全的。这一点很好理解。因此我们需要每个发起请求的线程维护自己的HttpContext。
参考:
httpclient官方文档