HttpClient4.5教程-基础 1.1

1.1 Request 的执行

HttpClient最必不可少的功能就是执行HTTP的方法,执行HTTP方法会涉及到一个或者多个HTTP request/HTTP response交换,而这些过程通常会在HttpClient内部完成。使用者提交一个request的对象去执行,HttpClient会发送这个request到目标服务器并且获得一个对应的response对象,如果不成功的话则抛出一个异常。

自然而然,满足了上面描述的HttpClient接口就是HttpClient API的主要入口。

下面是一个最简单的request执行过程:

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

1.1.1 HTTP request

所有的HTTP request都有一个请求线,包含了请求的方法名称,请求的URI和HTTP协议版本。

HttpClient开箱即用的支持所有HTTP/1.1中定义的HTTP方法,包括GET,HEAD,POST,PUT,DELETE,TRACE 和 OPTIONS,这些HTTP方法类型都有一个与之对应的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace 和 HttpOptions。

Request-URI是一个统一资源定位符,它指明了用于处理该request的资源的位置。HTTP request URIs包含了一个协议类型,主机名,可选端口,资源路径,可选查询参数,可选片段。

HttpGet httpget = new HttpGet(
     "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient提供URIBuilder实用类用于简化request 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 response

HTTP response是服务器在接收并且处理完request消息之后返回给客户端的消息,消息的第一行包含了协议版本,status code和与之关联的文本。

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消息包含多个header,这些header描述了消息的属性比如content length,content type等等,HttpClient提供了取出,添加,删除和遍历header的方法。

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

获取所有指定headers的最高效的方式是使用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"

该接口同时也提供HTTP消息转换到独立header元素的便利方法。

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 entity

HTTP消息可以携带跟request和response相关联的content entity。由于这些Entity是可选的,所以他们在某些request和response中可以查找到,有些则查不到。使用了entity的request被称为entity封装请求,HTTP定义了两种entity封装请求方法:POST 和 PUT。Response通常会封装一个content entity。不同的方法会有对应的异常,比如对于HEAD来说就有204 No Content,304 Not Modified, 205 Reset Content异常。

HttpClient根据内容来源将Entities区分为3种,根据内容来源区分为:

streamed: 内容来自于流或者实时产生。特别的是,这个分类包含了从HTTP responses接收到的entities,Streamed entities通常不可重复。

self-contained: 内容来自于内存或者通过独立于connection或者其他entity的手段获得,self-contained entities通常可以重复,其也是封装了entity的HTTP request最常用的Entities类型。

wrapping : 内容来自于其他的entity。

这种区分对于HTTP response输出内容时的连接管理是非常重要的。对于request entites来说,由于其是被应用创建并且通过HttpClient发送,streamed和self-contained有什么不同就没有那么重要。

由此,建议将不可重复的entites视为streamed,可以重复的entities视为self-contained。


1.1.4.1 可重复的entities

一个entity能够重复,表明它的内容可以被读取多次,这种情况只会在self contained entities中发生(比如ByteArrayEntity 或者 StringEntity)


1.1.4.2 使用HTTP entities

Entity可以同时代表二进制和文字内容,支持对字符进行编码,比如对character content进行编码。

Entity产生于request(封装内容 )执行时,或者request成功请求并且response body用于发送结果数据到客户端时。

从Entity中读取内容,可以通过HttpEntity#getContent()获取input stream,也可以向HttpEntity#writeTo(OutputStream)提供一个output stream,该方法会将内容一次性全部写回给指派的stream。

当消息到达并且Entity已经被接收时,可以用 HttpEntity#getContentType() 和 HttpEntity#getContentLength() 方法读取常用的元数据,如Content-Type 和 Content-Length 头信息,Content-Type头信息包含文本的mime-type编码信息如 text/plain 或者 text/html,该信息可以通过 HttpEntity#getContentType() 获取到,如果Header里面不包含这些信息,那么 HttpEntity#getContentLength()  会返回-1 并且  HttpEntity#getContentType()  返回 NULL,如果Header包含这些信息,那么 HttpEntity#getContentType() 会返回 Header 对象。

当创建一个出站消息的entity时,必须同时指定其相关的参数。

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 确保释放低级资源

为了保证恰当的释放系统资源,使用完毕后必须关闭entity相关的stream和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();
        try {
            // do something useful
        } finally {
            instream.close();
        }
    }
} finally {
    response.close();
}

关闭内容流和关闭response的不同之处在于,关闭内容流会尝试通过消费entity的content来保持底层连接,而关闭response则是直接关闭和丢弃掉这个连接。

注意 HttpEntity#writeTo(OutputStream)  方法也需要在entity被写出到流之后恰当的释放系统资源。如果通过HttpEntity#getContent()获取了java.io.InputStream流的实例,那么在finally阶段也需要关闭该流。

当处理streaming entites时,可以使用 EntityUtils#consume(HttpEntity) 方法来确保entity内容已经被完全消费并且底层stream已经被关闭。

有另一种情况是,当我们只需要从response content里面获取一小部分数据,但是消费剩余数据和保持连接复用的性能损失又太高时,我们可以直接关闭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
    }
} finally {
    response.close();
}

这样这个连接不会被复用,连接的所有资源都会被正确的释放掉。


1.1.6 消费entity内容

消费Entity内容的推荐方式是使用 HttpEntity#getContent() 或者 HttpEntity#writeTo(OuptputStream) 方法,也可以使用EntityUtils类,该类提供了一些静态方法来简化读取Entity内容或者其他信息。通过使用该类的某些方法,你可以以String /byte[] 方式来获取整个content body,从而替代直接读取 InputStream 的方式。但是,除非你知道response entity来自于可信HTTP server 并且其长度有限,否则不推荐使用EntityUtils。

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 content不止一次,这时就需要将内容通过内存或者磁盘的方式缓存起来,最简单的实现方式就是使用BufferedHttpEntity包装源Entity,该类会将源 Entity 的内容读取到内存中。如果通过其他方式来实现,那么就必须要保存一个源entity了。

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

1.1.7 生产 entity content

HttpClient提供多个可以通过HTTP连接高效的输出内容的类,这些类的实例可以跟POST和PUT等request相关联并且为request封装entity内容,HttpClient提供了最常用的数据容器如string, byte array , input stream 和文件 : 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的实现(self-contained)来替代普通的InputStreamEntity,FileEntity是一个不错的起始点。


1.1.7.1 HTML表单

许多应用程序需要模拟表单提交的过程,比如登录web应用或者提交input 数据,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);

UrlEncodedFormEntity实例会使用URL encoding来编码参数并且产生出如下内容:

param1=value1¶m2=value2

1.1.7.2 内容分块

通常来说,建议让HttpClient选择最合适的传输编码,HttpClient选择的传输编码会基于被传输的HTTP消息的属性而定。你可以通过设置HttpEntity#setChunked()为true来通知HttpClient需要进行chunk编码。注意HttpClient只是把这一标识当做一个提示使用,该值在不支持chunk编码的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

最简单和方便处理response的方式是使用ResponseHandler,该类包含了handleResponse(HttpResponse response)方法。该方法使得用户完全不需要去操心连接管理的事情。当使用ResponseHandler时,HttpClient会自动确保将连接释放回连接管理器,无论request是否执行成功。

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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值