参考文献:http://hc.apache.org/
http://hc.apache.org/httpcomponents-client-ga/tutorial/html/
http://hc.apache.org/httpcomponents-client-ga/examples.html
HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java.net 包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。
Apache HttpComponents项目旨在创建、维护一组低层次的用于Java的HTTP及其先关协议组件。
HttpComponents可用于:
HttpComponents 架构
HttpComponents 包括:HttpCore 、HttpClient 、Asynch HttpClient 、Commons HttpClient(被废弃的代码,不建议使用)
HTTPClient 是兼容HTTP/1.1的、基于HTTPCore的HTTP agent实现,支持认证、HTTP状态维护、HTTP连接管理。HTTPClient 是为了取代
Commons HttpClient 3.x而存在的,另外,强烈建议使用Commons HttpClient 的用户升级到HTTPClient。
虽然java.net包中提供了基础的HTTP方法,但是缺乏伸缩性、同时缺乏完善的功能。HttpClient用于填补这个空白。
注意:
4. HttpClient并不是一个浏览器,HttpClient是一个Http client端的开发库。
5. HttpClient只是用于发送、接收Http信息,并不会帮你处理信息里面的内容。其实,它也不知道怎么处理。比如服务器给你回复了一个response,里面有个1,这是表示1块钱,还是1百万,这是具体的调用应用才直到解析的。而HttpClient是一个通用的库。它存在的目的是你不用去使用socket,去连接server,一点一点拼装消息体。另外,Httpclient还有复杂的功能,如池化连接等。
使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。(需要导入httpclient-Versin.jar, httpcore-version.jar两个包。在使用过程中,有时候会抛出logging class not found 异常,这时可将commons-logging包导入工程即可)
创建HttpClient对象。
创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
释放连接。无论执行方法是否成功,都必须释放连接
HttpClient的最基本使用方法是:创建请求对象,使用httpclient将该请求转交给Server,然后返回一个 response对象或是抛出异常。
上述过程使用代码就是:
CloseableHttpClient httpclient = HttpClients.createDefault();
//创建请求对象
HttpGet httpget = new HttpGet("http://localhost/");
//httpclient和server通信,将该请求转交给Server,并返回reposne
CloseableHttpResponse response = httpclient.execute(httpget);
try {
<...>
} finally {
response.close();
}
HttpClient支持 HTTP/1.1规范定义的所有方法:GET, HEAD, POST, PUT, DELETE, TRACE 和OPTIONS。不同的方法,在HttpClient中,使用不同的类进行封装,代表不同的请求方法,对应的是HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace, 和HttpOptions。
构造请求对象时,需要传入一个资源定位符(URI)【 URI包括协议、主机名、端口(可选)、资源路径、查选参数(可选)、分片参数(可选)】,表示是对那个server的那个资源请求的。
例如:
HttpGet httpget = new HttpGet(
"http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
//或是
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);
Http reposne对象包含的内容包括:使用的协议的版本、Http 状态码、关联的文本内容。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
System.out.println(response.getProtocolVersion()); //HTTP/1.1
System.out.println(response.getStatusLine().getStatusCode()); //200
System.out.println(response.getStatusLine().getReasonPhrase()); //OK
System.out.println(response.getStatusLine().toString()); //HTTP/1.1 200 OK
设置请求、响应头部信息。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie",
"c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie",
"c2=b; path=\"/\", c3=c; domain=\"localhost\"");
Header h1 = response.getFirstHeader("Set-Cookie");
System.out.println(h1); //Set-Cookie: c1=a; path=/; domain=localhost
Header h2 = response.getLastHeader("Set-Cookie");
System.out.println(h2); //Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
Header[] hs = response.getHeaders("Set-Cookie");
System.out.println(hs.length); //2
Http Entity用于描述 Http请求或是响应中的内容(消息体、正文)。在Http规范中,Post和Put方法可以携带Http消息体,另外,Http Response中也可以包含一个消息体(除了 head方法的响应、或是状态码为204【无内容】,304【未修改】 205【内容重置】的响应。)。
HttpClient将上述不同场景下的entity分为三类:
streamed:从流中产生的或是运行中生成的。该类entity包括从http response中获取的。通常请求下,该类entity不是可重复的。
self-contained:内容来源与内存或是通过非连接的方式获取的(不是正常的过程http connection中获取的)。该类entity通常用于封装http request中的内容。(一般是可重复的)
wrapping:内容来源于另外一个entity对象;
关于entity 的可重复性
一个entity是可重复的,说明该entity可重复读取。只有self-contained是可重复的(例如,ByteArrayEntity或是StringEntity)。
entity怎么使用
entity可包括二进制数据也可以包括字符数据(支持设置字符编码)。当执行包含内容的request或是当获取到包含消息体的response时,就会创建出entity对象。
你可以通过如下方法获取到entity中内容:
StringEntity myEntity = new StringEntity("important message", ContentType.create("text/plain", "UTF-8"));
System.out.println(myEntity.getContentType()); //Content-Type: text/plain; charset=utf-8
System.out.println(myEntity.getContentLength()); //17
System.out.println(EntityUtils.toString(myEntity)); //important message
System.out.println(EntityUtils.toByteArray(myEntity).length); //17
entity使用注意事项
如果通过流的方式获取entity中的数据,请确保读取完毕后,主动将流关闭
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
try {
// do something useful
} finally {
instream.close(); //关闭流,但是底层的connection是alive的
}
}
} finally {
response.close(); // 立即关闭,丢弃connection
}
PS:关于strea.close与response的区别:
另外,使用HttpEntity#writeTo(OutputStream)方法也需要自己确保将底层的流关闭了。
如果你不想手动的处理流的化,你可以选择使用EntityUtils#consume(HttpEntity)类来获取数据,该方法可确保你将所有的数据读取出来了,然后帮你关闭底层的流。
另外,如果你只需要entity中的部分、少量的数据的话(读取其他的数据代价大,其他的剩下的数据量比较大)、且你不需要复用该http connection,那么你可以直接关闭该connection,不用理会底层的流(当关闭connection【通过colse response】,底层的流会自动关闭)。示例代码如下:
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
// Do not need the rest
//忽略stream的关闭
}
} finally {
response.close();//当response关闭时,关闭connection,关闭stream
}
获取entity的完整内容
获取entity完整的内容的方法包括:
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();
}
填充entity的内容(用于post与put)
你可以从字符串String、字节数组byte array、输入流input stream、文件中获取获取内容填充enttiy对象。HttpClient中使用StringEntity, ByteArrayEntity, InputStreamEntity, and FileEntity来表示内容来源不同的entity。
File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file,
ContentType.create("text/plain", "UTF-8"));
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);
最简单的处理response的方法是使用hander接口,该接口包含一个方法 handleResponse(HttpResponse response)。
使用response handler的好处是你可以在方法中获取数据,但是不同关系connection的管理问题。Httpclient会自动的将connection释放,还给connection manager(不管请求是否执行成功失败或是抛出了异常)
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json");
ResponseHandler rh = new ResponseHandler() {
@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);
HttpClient是对Http请求中执行的抽象表示。它对请求处理执行过程没有添加任何限制,将对连接管理、状态维护、认证、重定向的处理交给具体的实现类。
通常来说,HttpClient实现类采用门面模式,将一系列的handler与策略组合起来。
例如:
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()
.setKeepAliveStrategy(keepAliveStrat)
.build();
(1)HttpClient thread safety
HttpClient的实现应该是线程安全的。同时,推荐复用HttpClient对象,创建一个对象,处理一批的请求。
(2)HttpClient resource deallocation
当CloseableHttpClient 的实例对象不再需要时,则需要在适当的地方关闭该实例。
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
<...>
} finally {
httpclient.close();
}
通常来说,Http该设计为一个 无状态的、请求-响应模式的协议。但是实际上,应用希望能够对Http状态进行都某些状态进行持久化处理。
为了应用能够对当前的处理状态进行维护,HttpClient允许在特定的执行上下文中处理Http请求 。多个逻辑相关的请求可以放到同一个逻辑session中。Http context功能和map
HttpContext 可以包括任意类型的对象,因此在多线程之间共享httpcontext可能线程不安全的。推荐的作法是:某个线程都维护自己的一个httpcontext对象。
在http请求执行过程时,HttpClient会增加下列属性到httpcontext中:
可以HttpClientContext的适配器来简化HttpClient的处理:
HttpContext context = <...>
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpHost target = clientContext.getTargetHost();
HttpRequest request = clientContext.getRequest();
HttpResponse response = clientContext.getResponse();
RequestConfig config = clientContext.getRequestConfig();
Http协议拦截器是Http 协议面向方面编程中的一部分。通常来说,协议拦截器用于对具有某一种header或是某些相关的header的请求进行统一的处理。或是对response进行统一的header渲染,填充。另外,拦截器也可以对request、response的内容进行处理,例如透明的请求体内容的压缩、解压缩。
拦截器可以设置多个,多个拦截器依序执行,不同的拦截器可以通过Http Conext来进行信息共享。
通常来说,如果拦截器不依赖于特定的执行上下文,则当存在多个拦截器时,拦截器的执行顺序应该对最终结果没有影响。如果拦截器之间存在依赖关系,则拦截器必须按照特定的顺序进行注册,以限制拦截器的执行顺序。
拦截器必须实现为线程安全的。和servlet相同,拦截器不应该使用实例变量,除非对它们的访问都是进行了同步。
CloseableHttpClient httpclient = HttpClients.custom()
.addInterceptorLast(new HttpRequestInterceptor() {
public void process(
final HttpRequest request,
final HttpContext context) throws HttpException, IOException {
AtomicInteger count = (AtomicInteger) context.getAttribute("count");
request.addHeader("Count", Integer.toString(count.getAndIncrement()));
}
})
.build();
AtomicInteger count = new AtomicInteger(1);
HttpClientContext localContext = HttpClientContext.create();
localContext.setAttribute("count", count);
HttpGet httpget = new HttpGet("http://localhost/");
for (int i = 0; i < 10; i++) {
CloseableHttpResponse response = httpclient.execute(httpget, localContext);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
}
在Http请求处理过程中,会抛出两类异常: java.io.IOException(如socket超时)、HttpException (Http协议处理异常)
在错误处理时,需要注意如下几点:
(1)取消请求
可以通过HttpUriRequest.abort()方法将请求取消。该方法是线程安全的,可以同时被多个线程调用。如果线程处理(发起request)的线程阻塞在该请求的执行操作上,如果该请求被取消后,该线程会抛出InterruptedIOException异常。
(2) 处理重定向
HttpClient可以自动处理重定向(除了Http规范禁止的需要用户交互的重定向之外)。用户自定义自己的重定向处理策略,来处理请求中的重定向;
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
.setRedirectStrategy(redirectStrategy)
.build();