1.1. 执行请求
HttpClient最基本的功能就是执行HTTP方法。 一个HTTP方法的执行包含一次或多次HTTP请求与响应,通常由HttpClient的内部处理。
用户提供一个请求对象,HttpClient发送该请求到目标服务器,服务器返回相应的响应对象,如果执行未成功则抛出一个异常。
很自然地,HttpClient的API的主要入口点就是定义了上述协议的HttpClient接口。下面是一个最简单的请求执行过程例子
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.execute(httpget); try { <...> } finally { response.close(); }
所有HTTP请求都有由方法名,请求URI和HTTP协议版本组成的请求行。
HttpClient支持开箱即用HTTP/1.1规范中定义的所有HTTP方法:GET
, HEAD
,POST
, PUT
, DELETE
,TRACE
and OPTIONS
。它们都有一个特定的类对应这些方法类型: HttpGet
,HttpHead
, HttpPost
,HttpPut
, HttpDelete
,HttpTrace
, and HttpOptions
.
请求的URI是统一资源定位符,它标识了应用于哪个请求之上的资源。HTTP请求的URI包含协议方案,主机名,可选的端口,资源路径,可选查询和可选片段。
HttpGet httpget = new HttpGet( "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient 提供 URIBuilder
实用类来简化请求 URL的创建和修改.
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=
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
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"
它还提供了方便的方法来解析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
HTTP 消息可以携带与其相关联的请求或响应的内容实体。实体可以在一些请求和响应中找到,因为它们也是可选的。使用了实体的请求被称为封闭实体请求。HTTP规范定义了两种封闭实体的方法: POST
和PUT
。响应通常期望包含一个内容实体。 这个规则也有特例,就像HEAD
方法和 204 No Content
,304 Not Modified
, 205 Reset Content
响应。
HttpClient根据其内容来源以此区分三种类型的实体:
-
streamed(流式):
从流中获取或者是动态生成内容。尤其是这个类型包含了从HTTP响应中获取的实体。流式实体是不可重复生成的。 -
self-contained(自包含式):
通过内存、使用独立的连接、其他实体的方式来获得内容。自包含实体可以重复生成。这种类型的实体将主要被用于封闭HTTP请求。 -
wrapping(包装式):
通过其他实体来获得内容.
当一个HTTP请求中正在流式输出内容时,这个区分对于连接管理来说很重要。而对于由应用程序创建,并且只使用 HttpClient 发送的请求实体,流式和自包含的区分是没有多大意义的。在这种情况下,建议考虑如流式这样的非可重复的实体,或者是像自包含式那样的可重复实体。
由于一个实体可以同时表示二进制和字符内容,因此它拥有对字符编码的支持(对后者支持,即支持字符内容)。
当执行一次使用封闭内容的请求或者是当请求成功后,响应体被当做返回结果发回客户端的时候创建实体。
要读取实体中的内容, 可以通过 HttpEntity#getContent()
方法来从输入流中获取, 它会返回 java.io.InputStream
对象。 或者提供一个输出流到 HttpEntity#writeTo(OutputStream)
方法,
这会返回提供的流中已经写入的全部内容。
当实体已经接收到传入的消息, 使用HttpEntity#getContentType()
方法和HttpEntity#getContentLength()
方法能够读取通用元数据,例如 Content-Type
和Content-Length
头不 (如果它们是可用的). 当Content-Type
头部包含对文本 mime类型的字符编码比如 text/plain 和text/html时,HttpEntity#getContentEncoding()
方法可以用来读取这些信息. 当这些头部不可用时, 将会返回长度为 -1 和内容类型为NULL .如果 Content-Type
头部是可用的, 将会返回一个 Header
对象
当要为一个传出消息创建实体时,元数据必须由实体创建者来提供。
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
为了确保能够系统资源的正确释放,必须关闭实体或响应自身相关的内容流。
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(); }
关闭内容流和关闭响应之间的差别在于,前者将试图通过消耗实体内容来维持底层连接可用,而后者将立即关闭和丢弃连接。
请注意, HttpEntity#writeTo(OutputStream)
方法也同样确保系统资源在实体被全部写出的时候必须正确的释放,如果该方法获得的实例通过类java.io.InputStream
调用了HttpEntity#getContent()
方法, 那么它会被预期在finally子句关闭流。
当流实体工作时, 能够使用EntityUtils#consume(HttpEntity)
方法确保
实体内容被充分的消耗和关闭底层流.
有可能出现这样的特殊情况,只需要获取响应内容的其中一小部分或者是通过消耗剩余内容来使连接可重用造成性能损失太高,这样的情况下可以通过关闭响应来终止内容流。
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(); }
这样做了之后,连接将不能被重用,而且其所持有的所有级别资源会被正确释放.
通常推荐消耗实体内容的方式是使用HttpEntity#getContent()
或是HttpEntity#writeTo(OutputStream)
方法。 HttpClient
同样也配备了 EntityUtils
类, 它公开了一些静态方法,这些方法能够更容易地从实体中读取内容或信息,
替代通过 java.io.InputStream
类这样直接读取的方式,并且也可以使用这个类中的方法以字符串/字节数组的形式获取整个内容体。
但是, 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(); }
在某些情况下可能需要能够读取的实体内容超过一次。在这种情况下的实体内容必须以某种方式缓存在内存或磁盘上。解决这个问题最简单的方式是通过
the BufferedHttpEntity
类来包装源实体。这将使源实体的内容被读入到内存缓冲区。其他任何方式的实体包装都会包含源实体。
CloseableHttpResponse response = <...> HttpEntity entity = response.getEntity(); if (entity != null) { entity = new BufferedHttpEntity(entity); }
HttpClient提供了一些可以通过 HTTP 连接来高效地流出内容的类。 这些类的实例可以与封闭像 POST
和 PUT
请求的实体相关联,
使得实体内容装入流出的HTTP 请求。HttpClient为大多数的通用的数据容器比如字符串,字节数组,输入流和文件提供了一些类: 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
类, 这个是自包含式的类来替代通用类 InputStreamEntity
.FileEntity
也是一个很不错的点子.
许多应用程序需要模拟一个提交 HTML 表单的过程,例如,登录web应用或提交数据。 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编码来编码参数,生成如下的内容::
param1=value1¶m2=value2
开始传输前,通常建议让 HttpClient 选择基于HTTP消息属性的最适当的传输编码方式。
不过当设置HttpEntity#setChunked()
方法为true时,通知HttpClient该块编码是首选。 请注意
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);
处理响应最简单方便的方式是使用 ResponseHandler
接口, 它包含了handleResponse(HttpResponse response)
方法。
这个方法彻底的解决了用户对于连接管理的困扰。当使用ResponseHandler
接口时, 无论是否请求执行成功或引发异常,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);
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/114259.html