HttpClient 4.3教程

前言
Http协议应该是互联网中最重要的协议。持续增长的web服务、可联网的家用电器等都在继承并拓展着Http协议,向着浏览器之外的方向发展。

虽然jdk中的java.net包中提供了一些基本的方法,通过http协议来访问网络资源,但是大多数场景下,它都不够灵活和强大。HttpClient致力于填补这个空白,它可以提供有效的、最新的、功能丰富的包来实现http客户端。

为了拓展,HttpClient即支持基本的http协议,还支持http-aware客户端程序,如web浏览器,Webservice客户端,以及利用or拓展http协议的分布式系统。

1、HttpClient的范围/特性

是一个基于HttpCore的客户端Http传输类库
基于传统的(阻塞)IO
内容无关
2、HttpClient不能做的事情

HttpClient不是浏览器,它是一个客户端http协议传输类库。HttpClient被用来发送和接受Http消息。HttpClient不会处理http消息的内容,不会进行javascript解析,不会关心content type,如果没有明确设置,httpclient也不会对请求进行格式化、重定向url,或者其他任何和http消息传输相关的功能。

第一章 基本概念
1.1. 请求执行
HttpClient最基本的功能就是执行Http方法。一个Http方法的执行涉及到一个或者多个Http请求/Http响应的交互,通常这个过程都会自动被HttpClient处理,对用户透明。用户只需要提供Http请求对象,HttpClient就会将http请求发送给目标服务器,并且接收服务器的响应,如果http请求执行不成功,httpclient就会抛出异样。

下面是个很简单的http请求执行的例子:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet(“http://localhost/“);
CloseableHttpResponse response = httpclient.execute(httpget);
try {
<…>
} finally {
response.close();
}

1.1.1. HTTP请求

所有的Http请求都有一个请求行(request line),包括方法名、请求的URI和Http版本号。

HttpClient支持HTTP/1.1这个版本定义的所有Http方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。对于每一种http方法,HttpClient都定义了一个相应的类:HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace和HttpOpquertions。

Request-URI即统一资源定位符,用来标明Http请求中的资源。Http request URIs包含协议名、主机名、主机端口(可选)、资源路径、query(可选)和片段信息(可选)。

HttpGet httpget = new HttpGet(
http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=“);

HttpClient提供URIBuilder工具类来简化URIs的创建和修改过程。

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);
System.out.println(httpget.getURI());

上述代码会在控制台输出:

http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

1.1.2. HTTP响应

服务器收到客户端的http请求后,就会对其进行解析,然后把响应发给客户端,这个响应就是HTTP response.HTTP响应第一行是协议版本,之后是数字状态码和相关联的文本段。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, “OK”);

System.out.println(response.getProtocolVersion());
System.out.println(response.getStatusLine().getStatusCode());
System.out.println(response.getStatusLine().getReasonPhrase());
System.out.println(response.getStatusLine().toString());

上述代码会在控制台输出:

HTTP/1.1
200
OK
HTTP/1.1 200 OK

1.1.3. 消息头

一个Http消息可以包含一系列的消息头,用来对http消息进行描述,比如消息长度,消息类型等等。HttpClient提供了方法来获取、添加、移除、枚举消息头。

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);
Header h2 = response.getLastHeader(“Set-Cookie”);
System.out.println(h2);
Header[] hs = response.getHeaders(“Set-Cookie”);
System.out.println(hs.length);

上述代码会在控制台输出:

Set-Cookie: c1=a; path=/; domain=localhost
Set-Cookie: c2=b; path=”/”, c3=c; domain=”localhost”
2

最有效的获取指定类型的消息头的方法还是使用HeaderIterator接口。

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\”“);

HeaderIterator it = response.headerIterator(“Set-Cookie”);

while (it.hasNext()) {
System.out.println(it.next());
}

上述代码会在控制台输出:

Set-Cookie: c1=a; path=/; domain=localhost
Set-Cookie: c2=b; path=”/”, c3=c; domain=”localhost”

HeaderIterator也提供非常便捷的方式,将Http消息解析成单独的消息头元素。

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\”“);

HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(“Set-Cookie”));

while (it.hasNext()) {
HeaderElement elem = it.nextElement();
System.out.println(elem.getName() + ” = ” + elem.getValue());
NameValuePair[] params = elem.getParameters();
for (int i = 0; i < params.length; i++) {
System.out.println(” ” + params[i]);
}
}

上述代码会在控制台输出:

c1 = a
path=/
domain=localhost
c2 = b
path=/
c3 = c
domain=localhost

1.1.4. HTTP实体

Http消息可以携带http实体,这个http实体既可以是http请求,也可以是http响应的。Http实体,可以在某些http请求或者响应中发现,但不是必须的。Http规范中定义了两种包含请求的方法:POST和PUT。HTTP响应一般会包含一个内容实体。当然这条规则也有异常情况,如Head方法的响应,204没有内容,304没有修改或者205内容资源重置。

HttpClient根据来源的不同,划分了三种不同的Http实体内容。

streamed流式: 内容是通过流来接受或者在运行中产生。特别是,streamed这一类包含从http响应中获取的实体内容。一般说来,streamed实体是不可重复的。
self-contained自我包含式:内容在内存中或通过独立的连接或其它实体中获得。self-contained类型的实体内容通常是可重复的。这种类型的实体通常用于关闭http请求。
wrapping包装式: 这种类型的内容是从另外的http实体中获取的。
当从Http响应中读取内容时,上面的三种区分对于连接管理器来说是非常重要的。对于由应用程序创建而且只使用HttpClient发送的请求实体,streamed和self-contained两种类型的不同就不那么重要了。这种情况下,建议考虑如streamed流式这种不能重复的实体,和可以重复的self-contained自我包含式实体。

1.1.4.1. 可重复的实体

一个实体是可重复的,也就是说它的包含的内容可以被多次读取。这种多次读取只有self contained(自包含)的实体能做到(比如ByteArrayEntity或者StringEntity)。

1.1.4.2. 使用Http实体

由于一个Http实体既可以表示二进制内容,又可以表示文本内容,所以Http实体要支持字符编码(为了支持后者,即文本内容)。

当需要执行一个完整内容的Http请求或者Http请求已经成功,服务器要发送响应到客户端时,Http实体就会被创建。

如果要从Http实体中读取内容,我们可以利用HttpEntity类的getContent方法来获取实体的输入流(java.io.InputStream),或者利用HttpEntity类的writeTo(OutputStream)方法来获取输出流,这个方法会把所有的内容写入到给定的流中。
当实体类已经被接受后,我们可以利用HttpEntity类的getContentType()和getContentLength()方法来读取Content-Type和Content-Length两个头消息(如果有的话)。由于Content-Type包含mime-types的字符编码,比如text/plain或者text/html,HttpEntity类的getContentEncoding()方法就是读取这个编码的。如果头信息不存在,getContentLength()会返回-1,getContentType()会返回NULL。如果Content-Type信息存在,就会返回一个Header类。

当为发送消息创建Http实体时,需要同时附加meta信息。

StringEntity myEntity = new StringEntity(“important message”,
ContentType.create(“text/plain”, “UTF-8”));

System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);

上述代码会在控制台输出:

Content-Type: text/plain; charset=utf-8
17
important message
17

1.1.5. 确保底层的资源连接被释放

为了确保系统资源被正确地释放,我们要么管理Http实体的内容流、要么关闭Http响应。

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();
}
}
} finally {
response.close();
}

关闭Http实体内容流和关闭Http响应的区别在于,前者通过消耗掉Http实体内容来保持相关的http连接,然后后者会立即关闭、丢弃http连接。

请注意HttpEntity的writeTo(OutputStream)方法,当Http实体被写入到OutputStream后,也要确保释放系统资源。如果这个方法内调用了HttpEntity的getContent()方法,那么它会有一个java.io.InpputStream的实例,我们需要在finally中关闭这个流。

但是也有这样的情况,我们只需要获取Http响应内容的一小部分,而获取整个内容并、实现连接的可重复性代价太大,这时我们可以通过关闭响应的方式来关闭内容输入、输出流。

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
}
} finally {
response.close();
}

上面的代码执行后,连接变得不可用,所有的资源都将被释放。

1.1.6. 消耗HTTP实体内容

HttpClient推荐使用HttpEntity的getConent()方法或者HttpEntity的writeTo(OutputStream)方法来消耗掉Http实体内容。HttpClient也提供了EntityUtils这个类,这个类提供一些静态方法可以更容易地读取Http实体的内容和信息。和以java.io.InputStream流读取内容的方式相比,EntityUtils提供的方法可以以字符串或者字节数组的形式读取Http实体。但是,强烈不推荐使用EntityUtils这个类,除非目标服务器发出的响应是可信任的,并且http响应实体的长度不会过大。

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();
}

有些情况下,我们希望可以重复读取Http实体的内容。这就需要把Http实体内容缓存在内存或者磁盘上。最简单的方法就是把Http Entity转化成BufferedHttpEntity,这样就把原Http实体的内容缓冲到了内存中。后面我们就可以重复读取BufferedHttpEntity中的内容。

CloseableHttpResponse response = <…>
HttpEntity entity = response.getEntity();
if (entity != null) {
entity = new BufferedHttpEntity(entity);
}

1.1.7. 创建HTTP实体内容

HttpClient提供了一些类,这些类可以通过http连接高效地输出Http实体内容。HttpClient提供的这几个类涵盖的常见的数据类型,如String,byte数组,输入流,和文件类型:StringEntity,ByteArrayEntity,InputStreamEntity,FileEntity。

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);
请注意由于InputStreamEntity只能从下层的数据流中读取一次,所以它是不能重复的。推荐,通过继承HttpEntity这个自包含的类来自定义HttpEntity类,而不是直接使用InputStreamEntity这个类。FileEntity就是一个很好的起点(FileEntity就是继承的HttpEntity)。

1.7.1.1. HTML表单

很多应用程序需要模拟提交Html表单的过程,举个例子,登陆一个网站或者将输入内容提交给服务器。HttpClient提供了UrlEncodedFormEntity这个类来帮助实现这一过程。

List formparams = new ArrayList();
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);

UrlEncodedFormEntity实例会使用所谓的Url编码的方式对我们的参数进行编码,产生的结果如下:

param1=value1&m2=value2

1.1.7.2. 内容分块

一般来说,推荐让HttpClient自己根据Http消息传递的特征来选择最合适的传输编码。当然,如果非要手动控制也是可以的,可以通过设置HttpEntity的setChunked()为true。请注意:HttpClient仅会将这个参数看成是一个建议。如果Http的版本(如http 1.0)不支持内容分块,那么这个参数就会被忽略。

StringEntity entity = new StringEntity(“important message”,
ContentType.create(“plain/text”, Consts.UTF_8));
entity.setChunked(true);
HttpPost httppost = new HttpPost(“http://localhost/acrtion.do“);
httppost.setEntity(entity);

1.1.8.RESPONSE HANDLERS

最简单也是最方便的处理http响应的方法就是使用ResponseHandler接口,这个接口中有handleResponse(HttpResponse response)方法。使用这个方法,用户完全不用关心http连接管理器。当使用ResponseHandler时,HttpClient会自动地将Http连接释放给Http管理器,即使http请求失败了或者抛出了异常。

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);

1.2. HttpClient接口
对于Http请求执行过程来说,HttpClient的接口有着必不可少的作用。HttpClient接口没有对Http请求的过程做特别的限制和详细的规定,连接管理、状态管理、授权信息和重定向处理这些功能都单独实现。这样用户就可以更简单地拓展接口的功能(比如缓存响应内容)。

一般说来,HttpClient实际上就是一系列特殊的handler或者说策略接口的实现,这些handler(测试接口)负责着处理Http协议的某一方面,比如重定向、认证处理、有关连接持久性和keep alive持续时间的决策。这样就允许用户使用自定义的参数来代替默认配置,实现个性化的功能。

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.2.1.HTTPCLIENT的线程安全性

HttpClient已经实现了线程安全。所以希望用户在实例化HttpClient时,也要支持为多个请求使用。

1.2.2.HTTPCLIENT的内存分配

当一个CloseableHttpClient的实例不再被使用,并且它的作用范围即将失效,和它相关的连接必须被关闭,关闭方法可以调用CloseableHttpClient的close()方法。

CloseableHttpClient httpclient = HttpClients.createDefault();
try {
<…>
} finally {
httpclient.close();
}

1.3.Http执行上下文
最初,Http被设计成一种无状态的、面向请求-响应的协议。然而,在实际使用中,我们希望能够在一些逻辑相关的请求-响应中,保持状态信息。为了使应用程序可以保持Http的持续状态,HttpClient允许http连接在特定的Http上下文中执行。如果在持续的http请求中使用了同样的上下文,那么这些请求就可以被分配到一个逻辑会话中。HTTP上下文就和一个java.util.Map

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值