1.1、Http消息
1.1.1、概述
Http请求由请求头(header)和可选的请求体(body)组成。消息请求头包含请求指令行和一系列头字段。Http响应头包含状态行和一系列头字段。它们都必须包含Http协议版本号。一些Http的的内容体是可选的。HttpCore完全依照http协议定义消息模型对象,并为解析或反解析HTTP消息提供丰富的扩展。
1.1.2、基本操作
1.1.2.1、HTTP请求消息
Http请求消息为从客户端发送到服务器端的消息。第一行包含获取资源的方法、资源标识、协议版本。
<br> HttpRequest request = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);</p> <p>System.out.println(request.getRequestLine().getMethod());<br> System.out.println(request.getRequestLine().getUri());<br> System.out.println(request.getProtocolVersion());<br> System.out.println(request.getRequestLine().toString());<br>
stdout >
<br> GET<br> /<br> HTTP/1.1<br> GET / HTTP/1.1<br>
1.1.2.2、Http响应消息
响应消息为服务器接受并解析客户端请求的消息后,发送给客户端的消息。第一行包含使用的协议版本号、响应码、响应短语(响应码对应的语义)。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");</p> <p>System.out.println(response.getProtocolVersion());<br> System.out.println(response.getStatusLine().getStatusCode());<br> System.out.println(response.getStatusLine().getReasonPhrase());<br> System.out.println(response.getStatusLine().toString());
stdout >
<br> HTTP/1.1<br> 200<br> OK<br> HTTP/1.1 200 OK<br>
1.1.2.3、Http消息通用属性和方法
一个Http消息可以包含一系列头字段来表述消息,如内容长度、内容类型等。HttpCore提供了查询、添加、删除、迭代头字段的方法。
<br> HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC_OK, "OK");<br> response.addHeader("Set-Cookie","c1=a; path=/; domain=localhost");<br> response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");<br> Header h1 = response.getFirstHeader("Set-Cookie");<br> System.out.println(h1);<br> Header h2 = response.getLastHeader("Set-Cookie");<br> System.out.println(h2);<br> Header[] hs = response.getHeaders("Set-Cookie");<br> System.out.println(hs.length);
stdout >
Set-Cookie: c1=a; path=/; domain=localhost<br> Set-Cookie: c2=b; path="/", c3=c; domain="localhost"<br> 2<br>
一个更好的方法去迭代头信息是使用HeaderIterator接口
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC_OK, "OK");<br> response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");<br> response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");</p> <p>HeaderIterator it = response.headerIterator("Set-Cookie");</p> <p>while (it.hasNext()) {<br> System.out.println(it.next());<br> }
stdout >
Set-Cookie: c1=a; path=/; domain=localhost<br> Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
针对某些头元素,还提供了更为方便的方法。
<br> HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");<br> response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");<br> response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");</p> <p>HeaderElementIterator it = new BasicHeaderElementIterator( response.headerIterator("Set-Cookie"));</p> <p>while (it.hasNext()) {<br> HeaderElement elem = it.nextElement();<br> System.out.println(elem.getName() + " = " + elem.getValue());<br> NameValuePair[] params = elem.getParameters();<br> for (int i = 0; i < params.length; i++) {<br> System.out.println(" " + params[i]);<br> }<br> }
stdout >
<br> c1 = a<br> path=/<br> domain=localhost<br> c2 = b<br> path=/<br> c3 = c<br> domain=localhost<br>
只有当需要时,HTTP头才实时转化成相应的特殊元素。通过HTTP连接,在内部,HTTP头部信息被存储成字符序列,只有当访问时,才转化成相应的头元素。
1.1.3、HTTP实体(entity)
request和response都可能会携带一个HTTP实体,实体在request和response中都是可选的。在请求时需要隐藏数据时则使用实体请求,HTTP标准定义两种需要使用实体的请求方法:POST和PUT。响应中一般会使用实体来进行响应。以下例外情况不需要实体:HEAD请求、204(无实体)、304(未变化)、205(重置响应)。
HttpCore根据实体内容产生的地方不同,区分三种类型的实体:
- 流式(streamed): 实体来自一个流或者不间断地产生内容。通常情况,这种实体来自一个连接。流式实体通常不可重复。
- 自包含(self-contained): 实体存在于内存中,或者从一个独立的连接或实体中获得。这种实体通常是可重复的。
- 包装型(wrapping): 从另一个实体中获得。
实体的类型不同对于连接处理接收到的实体是非常关键的。但对于一个应用程序来说,如果仅使用HttpCore组件来发送创建发送实体的话,就不需要太关心streamed和self-contained的区别了。通常认为非重复性实体为流式,可重复实体为self-contained。
1.1.3.1、可重复实体
如果一个实体是可重复实体,就意味着可以重复读取。只有使用self-contained实体时是可能的(比如:ByteArrayEntity或StringEntity)。
1.1.3.2、HTTP实体的用法
由于一个HTTP实体可以表示字节及字符内容,所以它必须支持字符编码。
当发送一条封装了内容的HTTP请求,或者当请求成功向客户端返回响应时,实体会被创建。
针对从实体中获取内容,可以使用HttpEntity#getContent()方法获得输入流java.io.InputStream,也可以使用HttpEntity#writeTo(OutputStream)将内容写入指定的流中。
EntityUtils类提供了一系列从实体(entity)中获取内容或信息的静态方法。使得这些方法可以将完整的body读入String或者byte数组,而不是直接操作java.io.InputStream。
当接受到一条HTTP消息,HttpEntity#getContentType()和 HttpEntity#getContentLength()方法帮助读取 Content-Type和Content-Length头信息(如果可用)。因为Content-Type头可能包含内容的mime编码类型,如text/plain或 text/html,所以HttpEntity#getContentEncoding()可以方便获取这些信息。如果头信息不可用的话,获取长度将返回-1,获取内容类型将会返回NULL;如果头信息可用,将会返回object。
为输入消息创建实体时,一些必要的无数据是必须传给实体对象的。
StringEntity myEntity = new StringEntity("important message", Consts.UTF_8);</p> <p>System.out.println(myEntity.getContentType());<br> System.out.println(myEntity.getContentLength());<br> System.out.println(EntityUtils.toString(myEntity));<br> System.out.println(EntityUtils.toByteArray(myEntity).length);<br>
stdout >
<br> Content-Type: text/plain; charset=UTF-8<br> 17<br> important message<br> 17<br>
1.1.3.3、确保系统资源的释放
及时关闭和实体entity相关联的流对象,以确保系统资源的关闭。
<br> HttpResponse response;<br> HttpEntity entity = response.getEntity();<br> if (entity != null) {<br> InputStream instream = entity.getContent();<br> try {<br> // do something useful<br> } finally {<br> instream.close();<br> }<br> }
需要注意的是方法HttpEntity#writeTo(OutputStream)在执行时,如果内容已经完全输出,也需要及时关闭相关的输出流;如果调用方法HttpEntity#getContent()时,关联一个java.io.InputStream对象,也需要在finally块中正确关闭。
当使用流操作实体时,可以使用EntityUtils#consume(HttpEntity)方法来确保数据完全获取或者关闭关联的流资源。
1.1.4、实体封装
HttpCore提供了多种封装实体的方法:
1.1.4.1、BasicHttpEntity
正如名字所示,基本的实体需要接收一个输入流。通常,会使用此类来接收一个实体消息。
此类有一个无参构造方法,实例化后,它的内容为空,内容长度为负值。
可以使用方法BasicHttpEntity#setContent(InputStream)来设置内容,使用BasicHttpEntity#setContentLength(long)方法来设置内容长度(可选)。
<br> BasicHttpEntity myEntity = new BasicHttpEntity();<br> myEntity.setContent(someInputStream);<br> myEntity.setContentLength(340); // sets the length to 340<br>
1.1.4.2、ByteArrayEntity
ByteArrayEntity是一个self-contained类型的实体类(可重复使用),包含的内容来自给定的字节数组。包含一个传递字节数组的构造方法。
<br> ByteArrayEntity myEntity = new ByteArrayEntity(new byte[] {1,2,3}, ContentType.APPLICATION_OCTET_STREAM);<br>
1.1.4.3、 StringEntity
StringEntity是一个包含String的self-contained、可复用实体。提供了三种构造器,最简单的只需要传入一个String对象;第二种需要传入字符编码类型;第二种允许指定mimi类型。
<br> StringBuilder sb = new StringBuilder();<br> Map<String, String> env = System.getenv();<br> for (Map.Entry<String, String> envEntry : env.entrySet()) {<br> sb.append(envEntry.getKey())<br> .append(": ").append(envEntry.getValue())<br> .append("\r\n");<br> }</p> <p>// construct without a character encoding (defaults to ISO-8859-1)<br> HttpEntity myEntity1 = new StringEntity(sb.toString());</p> <p>// alternatively construct with an encoding (mime type defaults to "text/plain")<br> HttpEntity myEntity2 = new StringEntity(sb.toString(), Consts.UTF_8);</p> <p>// alternatively construct with an encoding and a mime type<br> HttpEntity myEntity3 = new StringEntity(sb.toString(),<br> ContentType.create("text/plain", Consts.UTF_8));<br>
1.1.4.4、 InputStreamEntity
InputStreamEntity是一个封装了输入流的流式、不可复用的实体。构造它需要传入输入流和内容长度,使用传入的内容长度来限制从输入流数据读取的长度。当传入的长度和输入流的可用长度符合,所有的数据将会被发送。另一种方式是传递一个负值,此方法也可以读取全部的数据。尽量使用适当的长度来限制读取数据的长度。
<br> InputStream instream = getSomeInputStream();<br> InputStreamEntity myEntity = new InputStreamEntity(instream, 16);<br>
1.1.4.5、 FileEntity
FileEntity是一个封装文件的self-contained、可复用的实体。通常用来封装不同类型的大文件流,需要指定文件的类型,如发送一个zip文件,需要指定类型为application/zip;XML: application/xml。
<br> HttpEntity entity = new FileEntity(staticFile, ContentType.create("application/java-archive"));<br>
1.1.4.6、 HttpEntityWrapper
封装实体的基础类。所有封装实体操作都必须调用它。实现这个类并定制化。
1.1.4.7. BufferedHttpEntity
BufferedHttpEntity是HttpEntityWrapper的子类。它实现的实体,可以使用buffers的方式从内存中读取内容。
它使得一个不可复用实体变成可复用实体,如果实体本身已经是可复用的,那么它将仅仅是个简单的调用而已。
<br> myNonRepeatableEntity.setContent(someInputStream);<br> BufferedHttpEntity myBufferedEntity = new BufferedHttpEntity(myNonRepeatableEntity);<br>
1.2、阻塞型HTTP连接
HTTP连接负责HTTP消息的解析和反解析。开发者不需要直接操作HTTP连接。有更高级协议来执行HTTP请求。但是在某些情况下,操作HTTP连接是比较容易的,如访问连接状态、socket超时时间或者本地和远程连接地址。
HTTP连接是非线程安全的。强烈建议不要在同一个线程中操作多个HTTP连接。使用HttpConnection或者子接口操作另一个线程时,仅有一个方法是安全的,那就是HttpConnection#shutdown()。
1.2.1、使用阻塞型HTTP连接进行工作
HttpCore并不对所有的连接方式进行支持,因为当需要更多的验证或代理的,建立一个连接是非常复杂的(尤其是在客户端侧),相反,阻塞型HTTP连接几乎适用于所有网络socket。
<br> Socket socket = null;</p> <p>DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);<br> conn.bind(socket);<br> System.out.println(conn.isOpen());<br> HttpConnectionMetrics metrics = conn.getMetrics();<br> System.out.println(metrics.getRequestCount());<br> System.out.println(metrics.getResponseCount());<br> System.out.println(metrics.getReceivedBytesCount());<br> System.out.println(metrics.getSentBytesCount());<br>
无论是客户端还是服务器端,HTTP连接需要两个步骤。消息头先传输。基于头部消息,消息体会随后传输。请注意在消息处理完成后,关闭相关的流非常重要。在向客户端发送消息之前,必须保证从HTTP连接中获取全部输入消息(请求消息),此连接或许会用于下一次请求。
在客户端侧比较简单的消息处理如下:
<br> Socket socket = null;</p> <p>DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);<br> conn.bind(socket);<br> HttpRequest request = new BasicHttpRequest("GET", "/");<br> conn.sendRequestHeader(request);<br> HttpResponse response = conn.receiveResponseHeader();<br> conn.receiveResponseEntity(response);<br> HttpEntity entity = response.getEntity();<br> if (entity != null) {<br> // Do something useful with the entity and, when done, ensure all<br> // content has been consumed, so that the underlying connection<br> // can be re-used<br> EntityUtils.consume(entity);<br> }<br>
服务器侧的处理如下:
<br> Socket socket = null;</p> <p>DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(8 * 1024);<br> conn.bind(socket);<br> HttpRequest request = conn.receiveRequestHeader();<br> if (request instanceof HttpEntityEnclosingRequest) {<br> conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);<br> HttpEntity entity = ((HttpEntityEnclosingRequest) request)<br> .getEntity();<br> if (entity != null) {<br> // Do something useful with the entity and, when done, ensure all<br> // content has been consumed, so that the underlying connection<br> // could be re-used<br> EntityUtils.consume(entity);<br> }<br> }<br> HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,<br> 200, "OK") ;<br> response.setEntity(new StringEntity("Got it") );<br> conn.sendResponseHeader(response);<br> conn.sendResponseEntity(response);<br>
但是需要注意的是,尽量不要使用这些封装性比较低的服务器端代码,取而代之的是比较成熟的HTTP服务器。
1.2.2、使用阻塞型I/O传输数据
HTTP连接处理由HttpEntity生成的内容。HTTP连接将输入的消息封装成一个entity对象。注意HttpServerConnection#receiveRequestEntity() 和 HttpClientConnection#receiveResponseEntity()这两个方法并不检索或缓存任务输入数据。它们仅仅注入基于输入消息属性的内容编码。可以使用HttpEntity#getContent()来从内容输入流中获取数据。输入消息将会自动被解码并对消息费者完全透明。同样的方法,HTTP连接使用HttpEntity#writeTo(OutputStream)生成输出消息进行响应。输出消息将自动根据消息属性编码的内容封装成一个实体。
1.2.3、支持的传输机制
默认的HTTP连接支持HTTP/1.1定义的三种传输机制:
- 指定Content-Length:内容的结束标记被Content-Length头定义。最大的长度限制:Long#MAX_VALUE。
- 明确指定(Identity coding): 使用HTTP连接的关闭来界定消息的结束。显而易见,这种方式只能在服务器侧使用。最大长度限制:无限制。
- 块传输(Chunk coding): 内容的传输使用块来进行。最大限制: 无限制。
根据消息相关的实体属性,自动创建最合适的内容流。
1.2.4、HTTP连接的中断
即可以使用HttpConnection#close()方法来优雅地中断HTTP连接,也可以使用HttpConnection#shutdown()方法来强制中断链接。前者在关闭连接之前会尝试刷新buffered数据或者在不确定的情况下进行阻塞。HttpConnection#close()不是线程安全的。第二个之后的中断连接操作将不会刷新任务buff数据,并会及时回到当前线程中,而不进行阻塞。HttpConnection#shutdown()是线程安全的。
1.3、HTTP异常处理
所有的HttpCore组件仅会抛出两种类型的异常:由socket超时或socket重置产生的I/O异常IOException,由于HTTP协议错误产生的HttpException。一般情况下,I/O 错误被认为是非致命的和可恢复的,而HttpException将是毁灭级的,不可从中恢复的异常。
1.3.1、协议异常
ProtocolException的异常一般情况会导致HTTP消息处理的立即中断。
1.4、HTTP协议的处理
HTTP协议拦截器是一个实现了HTTP协议特性的程序。通常协议拦截器通常会按照输入消息的一个头或者一系列头特性来处理,或者根据输出消息的头信息来处理。协议拦截器一般也会处理消息实体内容;很显然,内容压缩和解压缩是一个很好的例子。通常使用“Decorator”模式(用于将原始实体进行装饰)是一种好选择。几个协议拦截器可以合并成一个合理的组件。
HTTP协议处理器是使用了Chain of Responsibility模式的一系列协议拦截器组成,每个拦截器在处理HTTP协议时,各司其职。
拦截器的执行顺序,并不确定,它不需要按照上下文的执行顺序来执行。但是如果拦截器之间如果有相互依赖性,那么它必须使用合适顺序添加至协议处理器中去。
协议拦截器必须是线程安全的。和servlet类似,除非所使用变量为synchronized,那么请不要使用实例变量。
1.4.1、标准协议拦截器
HttpCore提供了一系列基本的协议拦截器来简化客户端和服务端的HTTP处理。
1.4.1.1、RequestContent
对于输出request,RequestContent是一个非常重要的拦截器。它的职责是:基于封装实体和协议版本特性来增加 Content-Length头或者Transfer-Content头,从而达到界定内容长度。在处理客户端侧的协议时,这个拦截器是必须的。
1.4.1.2、ResponseContent
和RequestContent类似,它主要是处理服务端消息的内容长度。
1.4.1.3、RequestConnControl
RequestConnControl负责在发送request时增加Connection头信息,这在HTTP/1.0连接中是必需的。主要用于客户端侧。
1.4.1.4、ResponseConnControl
和RequestConnControl类似,它主要用户服务器端侧,在HTTP/1.0中是必需的。
1.4.1.5、RequestDate
RequestDate职责是在输出request的时候增加Date头。这个拦截器在客户侧是可选的。
1.4.1.6、ResponseDate
ResponseDate职责是在输出response时增加Date头字段。在服务器侧是强烈推荐使用的。
1.4.1.7、RequestExpectContinue
RequestExpectContinue通过增加Expect头来使用expect-continue握手协议。在客户端侧是强烈建议使用。
1.4.1.8、RequestTargetHost
RequestTargetHost主要用于在客户端侧增加Host头字段,是必需的。
1.4.1.9、RequestUserAgent
RequestUserAgent用于增加User-Agent头字段,在客户端侧是强烈建议使用的。
1.4.1.10、ResponseServer
ResponseServer用户在服务器端侧增加Server头字段,强烈建议使用。
1.4.2、协议处理器的使用
通常来说,HTTP协议处理器是在输入消息处理程序正式逻辑之前进行预处理,在输出消息之后进行后处理。
<br> HttpProcessor httpproc = HttpProcessorBuilder.create()<br> // Required protocol interceptors<br> .add(new RequestContent())<br> .add(new RequestTargetHost())<br> // Recommended protocol interceptors<br> .add(new RequestConnControl())<br> .add(new RequestUserAgent("MyAgent-HTTP/1.1"))<br> // Optional protocol interceptors<br> .add(new RequestExpectContinue(true))<br> .build();</p> <p>HttpCoreContext context = HttpCoreContext.create();<br> HttpRequest request = new BasicHttpRequest("GET", "/");<br> httpproc.process(request, context);<br>
将消息发送至目标host后获取一个响应
<br> HttpResponse = null;<br> httpproc.process(response, context);<br>
注意BasicHttpProcessor不能同步访问其内部结构,因此它也不是线程安全的。
1.4.3、HTTP上下文
在一个HTTP执行上下文中,协议处理器是可以共享使用信息的,如处理状态。HTTP上下文是一个Key-Value的map结构,其内部实现为一个HashMap。其设计的目的是在一些逻辑相关的组件之间共享数据。它可以用来记录消息的处理状态或者连续的处理消息。可以在同一个Context中,为连续的消息处理创建一个共享的逻辑session。
<br> HttpProcessor httpproc = HttpProcessorBuilder.create()<br> .add(new HttpRequestInterceptor() {<br> public void process( HttpRequest request, HttpContext context) throws HttpException, IOException {<br> String id = (String) context.getAttribute("session-id");<br> if (id != null) {<br> request.addHeader("Session-ID", id);<br> }<br> }<br> })<br> .build();</p> <p>HttpCoreContext context = HttpCoreContext.create();<br> HttpRequest request = new BasicHttpRequest("GET", "/");<br> httpproc.process(request, context);<br>
1.5、阻塞型HTTP协议处理程序
正如之前提到的,编写一个HTTP服务器是不必要的,尽量采用现有的、成熟的HTTP服务器。
1.5.1、HTTP服务器
HttpService是一个服务端的处理程序,它是基于实现了RFC 2616规定的基本的HTTP协议阻塞型I/O模型。
针对于所有的输入和输出消息,HttpService依赖于HttpProcessor实例去强制生成通用的传输协议头信息,而HTTP请求程序则关心应用程序特定的内容生成和处理。
<br> HttpProcessor httpproc = HttpProcessorBuilder.create()<br> .add(new ResponseDate())<br> .add(new ResponseServer("MyServer-HTTP/1.1"))<br> .add(new ResponseContent())<br> .add(new ResponseConnControl())<br> .build();<br> HttpService httpService = new HttpService(httpproc, null);<br>
1.5.1.1、HTTP请求处理器
HttpRequestHandler是用来处理指定的一组请求的程序。HttpService被设计为关注协议方面,然而自己实现的request处理器只关心应用程序指定的HTTP处理。请求处理器的主要目的是生成一个携带内容实体响应对象,并将其传回发送请求的客户端。
<br> HttpRequestHandler myRequestHandler = new HttpRequestHandler() {</p> <p>public void handle( HttpRequest request, HttpResponse response,<br> HttpContext context) throws HttpException, IOException {<br> response.setStatusCode(HttpStatus.SC_OK);<br> response.setEntity(<br> new StringEntity("some important message",<br> ContentType.TEXT_PLAIN));<br> }</p> <p>};<br>
1.5.1.2、请求处理解析器
通常HttpRequestHandlerResolver用来将请求URI映射至一个正确的请求处理器。HttpCore提供了一个非常简单的解析器实现,它基于简单的匹配算法模式:HttpRequestHandlerRegistry仅支持三种匹配规则:*, <uri>* 和 *<uri>。
<br> HttpProcessor httpproc = null;</p> <p>HttpRequestHandler myRequestHandler1 = null;<br> HttpRequestHandler myRequestHandler2 = null;<br> HttpRequestHandler myRequestHandler3 = null;</p> <p>UriHttpRequestHandlerMapper handlerMapper = new UriHttpRequestHandlerMapper();<br> handlerMapper.register("/service/*", myRequestHandler1);<br> handlerMapper.register("*.do", myRequestHandler2);<br> handlerMapper.register("*", myRequestHandler3);<br> HttpService httpService = new HttpService(httpproc, handlerMapper);<br>
鼓励用户实现复杂的解析器(HttpRequestHandlerResolver),例如:基于正则匹配。
1.5.1.3. 使用HTTP Service来处理请求
当配置完全初始化后,HttpService可以用来处理活动的HTTP连接。HttpService#handleRequest()读取一个输入请求,生成一个响应并将其传回客户端。这个方法可以在一个持久连接中循环处理多个请求。来自多个线程调用HttpService#handleRequest()也是安全的。允许同时在不同的连接上处理请求,同时使用HttpService注册的协议拦截器和请求处理器也是安全的。
<br> HttpService httpService = null;<br> HttpServerConnection conn = null;<br> HttpContext context = null;</p> <p>boolean active = true;<br> try {<br> while (active &amp;&amp; conn.isOpen()) {<br> httpService.handleRequest(conn, context);<br> }<br> } finally {<br> conn.shutdown();<br> }<br>
1.5.2、HTTP请求执行器
HttpRequestExecutor是客户端侧的HTTP协议处理器,它是基于实现了RFC 2616规定的基本客户端侧HTTP协议的阻塞型I/O模型。依赖HttpProcessor生成通用的输出协议头信息。可以根据请求的执行和收到的响应来实现基于特定逻辑的执行器(HttpRequestExecutor )。
<br> HttpClientConnection conn = null;</p> <p>HttpProcessor httpproc = HttpProcessorBuilder.create()<br> .add(new RequestContent())<br> .add(new RequestTargetHost())<br> .add(new RequestConnControl())<br> .add(new RequestUserAgent("MyClient/1.1"))<br> .add(new RequestExpectContinue(true))<br> .build();<br> HttpRequestExecutor httpexecutor = new HttpRequestExecutor();</p> <p>HttpRequest request = new BasicHttpRequest("GET", "/");<br> HttpCoreContext context = HttpCoreContext.create();<br> httpexecutor.preProcess(request, httpproc, context);<br> HttpResponse response = httpexecutor.execute(request, conn, context);<br> httpexecutor.postProcess(response, httpproc, context);</p> <p>HttpEntity entity = response.getEntity();<br> EntityUtils.consume(entity);<br>
HttpRequestExecutor方法在多线程中执行也是安全的。它允许在不同的连接中同时执行,同样的,所有使用HttpRequestExecutor的拦截器也是安全的。
1.5.3、长连接(可复用连接)
ConnectionReuseStrategy根据情况来决定当前连接在处理完当前的消息请求后是否可以处理更多的请求。默认的连接可复用策略是尽可能地保持连接状态。首先它会考虑HTTP协议使用的版本。HTTP/1.1连接默认是长连接,而HTTP/1.0默认不是。第二,需要根据Connection头信息。根据一端传递的Connection头传递的是Keep-Alive或Close来决定是否启用长连接。第三种策略是根据实体的特征来决定使用长连接是否安全。
1.6、连接池管理
高效的客户端HTTP请求一般需要长连接。HttpCore通过使用连接池来方便管理长连接。连接池是线程安全的,可以被不同的请求共享。
默认连接池仅允许20个共存连接,并且一条路由仅允许使用2个连接。2个连接的限制是HTTP协议规定的。正常来说,这是非常有限的。应用程序可以在运行时修改池的限制来允许更多的连接。
<br> HttpHost target = new HttpHost("localhost");<br> BasicConnPool connpool = new BasicConnPool();<br> connpool.setMaxTotal(200);<br> connpool.setDefaultMaxPerRoute(10);<br> connpool.setMaxPerRoute(target, 20);<br> Future<BasicPoolEntry> future = connpool.lease(target, null);<br> BasicPoolEntry poolEntry = future.get();<br> HttpClientConnection conn = poolEntry.getConnection();<br>
请注意连接池没有办法知道一个连接是否还在使用,所以连接池的使用者应该保证在连接不再使用时,将其归还给连接池,即使连接不再可用。
<br> BasicConnPool connpool = null;<br> Future<BasicPoolEntry> future = connpool.lease(target, null);<br> BasicPoolEntry poolEntry = future.get();<br> try {<br> HttpClientConnection conn = poolEntry.getConnection();<br> } finally {<br> connpool.release(poolEntry, conn.isOpen());<br> }<br>
运行时可以访问连接池状态
<br> HttpHost target = new HttpHost("localhost");<br> BasicConnPool connpool = null;<br> PoolStats totalStats = connpool.getTotalStats();<br> System.out.println("total available: " + totalStats.getAvailable());<br> System.out.println("total leased: " + totalStats.getLeased());<br> System.out.println("total pending: " + totalStats.getPending());<br> PoolStats targetStats = connpool.getStats(target);<br> System.out.println("target available: " + targetStats.getAvailable());<br> System.out.println("target leased: " + targetStats.getLeased());<br> System.out.println("target pending: " + targetStats.getPending());<br>
请注意连接池不会主动清理掉不可用的连接,即使失效连接不能被请求者使用,连接池可以通过计算连接的静止时间来释放连接。聪明的做法是将长时静止的连接进行回收。
<br> BasicConnPool connpool = null;<br> connpool.closeExpired();<br> connpool.closeIdle(1, TimeUnit.MINUTES);<br>
1.7、TLS/SSL 支持
阻塞型连接可以绑定至任意socket。这使得SSL的支持非常方便。任意SSLSocket实例可以绑定一个阻塞型连接,从而使所有的消息传输都经过TLS/SSL加密。
<br> SSLContext sslcontext = SSLContext.getInstance("Default");<br> sslcontext.init(null, null, null);<br> SocketFactory sf = sslcontext.getSocketFactory();<br> SSLSocket socket = (SSLSocket) sf.createSocket("somehost", 443);<br> socket.setEnabledCipherSuites(new String[] {<br> "TLS_RSA_WITH_AES_256_CBC_SHA",<br> "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",<br> "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" });<br> DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1204);<br> conn.bind(socket);<br>
英文地址:http://hc.apache.org/httpcomponents-core-4.3.x/tutorial/html/fundamentals.html
转载请注明:子暃之路 » Apache HttpComponet(2)–HttpCore4.3基于原始的同步I/O的同步Http消息操作