在阅读了httpcore系列(一)初识httpcore后,我们浅显地了解了发送http请求需要构建一个从客户端到服务端的Httprequest对象,接受一个从服务端到客户端的HttpResponse对象,然而我们并不清楚其中的缘由,接下来我将聊聊如何将Httprequest作为参数输入,HttpResponse作为结果返回的来龙去脉。
官方样例
我们看看官方给的同步GET请求样例,代码如下。
package org.apache.http.examples;
import java.net.Socket;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.impl.DefaultBHttpClientConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpProcessorBuilder;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.http.util.EntityUtils;
/**
* Elemental example for executing multiple GET requests sequentially.
*/
public class ElementalHttpGet {
public static void main(String[] args) throws Exception {
HttpProcessor httpproc = HttpProcessorBuilder.create()
.add(new RequestContent())
.add(new RequestTargetHost())
.add(new RequestConnControl())
.add(new RequestUserAgent("Test/1.1"))
.add(new RequestExpectContinue(true)).build();
HttpRequestExecutor httpexecutor = new HttpRequestExecutor();
HttpCoreContext coreContext = HttpCoreContext.create();
HttpHost host = new HttpHost("localhost", 8080);
coreContext.setTargetHost(host);
DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);
ConnectionReuseStrategy connStrategy = DefaultConnectionReuseStrategy.INSTANCE;
try {
String[] targets = {
"/",
"/servlets-examples/servlet/RequestInfoExample",
"/somewhere%20in%20pampa"};
for (int i = 0; i < targets.length; i++) {
if (!conn.isOpen()) {
Socket socket = new Socket(host.getHostName(), host.getPort());
conn.bind(socket);
}
BasicHttpRequest request = new BasicHttpRequest("GET", targets[i]);
System.out.println(">> Request URI: " + request.getRequestLine().getUri());
httpexecutor.preProcess(request, httpproc, coreContext);
HttpResponse response = httpexecutor.execute(request, conn, coreContext);
httpexecutor.postProcess(response, httpproc, coreContext);
System.out.println("<< Response: " + response.getStatusLine());
System.out.println(EntityUtils.toString(response.getEntity()));
System.out.println("==============");
if (!connStrategy.keepAlive(response, coreContext)) {
conn.close();
} else {
System.out.println("Connection kept alive...");
}
}
} finally {
conn.close();
}
}
}
你肯定敏锐地发现了这么一行代码!
HttpResponse response = httpexecutor.execute(request, conn, coreContext);,返回值就是我们需要的HttpResponse对象,所以我们只要清楚函数的调用者httpexecutor:HttpRequestExecutor对象,参数conn:DefaultBHttpClientConnection对象,coreContext:HttpCoreContext,就会明白httpcore如何发送的GET请求。
聊聊样例中那些陌生的对象
HttpProcessor
接口HttpProcessor的定义public interface HttpProcessor extends HttpRequestInterceptor, HttpResponseInterceptor,可知其继承自HttpRequestInterceptor,HttpResponseInterceptor,所以先讲讲这两类接口。
- HttpRequestInterceptor 请求拦截器
实现类主要是为请求添加请求头。
观察其任意实现类,在process方法里大致实现都是判断是否是属于HttpEntityEnclosingRequest实例,是否含有某一请求头,然后再进行某类请求头的设置。
如例:RequestDate
@Override
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
Args.notNull(request, "HTTP request");
if ((request instanceof HttpEntityEnclosingRequest) &&
!request.containsHeader(HTTP.DATE_HEADER)) {
final String httpdate = DATE_GENERATOR.getCurrentDate();
request.setHeader(HTTP.DATE_HEADER, httpdate);
}
}
- HttpResponseInterceptor 响应拦截器
实现类主要是为响应添加响应头。
观察其任意实现类,大体与上述所说的请求拦截器的作用一致,唯一不同的点是其作用于响应,为响应添加响应头。
如例:ResponseDate
public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException {
Args.notNull(response, "HTTP response");
final int status = response.getStatusLine().getStatusCode();
if ((status >= HttpStatus.SC_OK) &&
!response.containsHeader(HTTP.DATE_HEADER)) {
final String httpdate = DATE_GENERATOR.getCurrentDate();
response.setHeader(HTTP.DATE_HEADER, httpdate);
}
}
- HttpProcessor
我们来看看HttpProcessor的实现类ImmutableHttpProcessor,可知其是一组请求拦截器、一组响应拦截器的管理类,在process方法里遍历每一类拦截器,通过调用各类拦截器各自的process实现,给请求添加请求头,给响应添加响应头。
private final HttpRequestInterceptor[] requestInterceptors;
private final HttpResponseInterceptor[] responseInterceptors;
@Override
public void process(final HttpRequest request, final HttpContext context) throws IOException, HttpException {
for (final HttpRequestInterceptor requestInterceptor : this.requestInterceptors) {
requestInterceptor.process(request, context);
}
}
@Override
public void process(final HttpResponse response, final HttpContext context) throws IOException, HttpException {
for (final HttpResponseInterceptor responseInterceptor : this.responseInterceptors) {
responseInterceptor.process(response, context);
}
}
样例中的这部分代码,是不是应该明白了呢?通过构造者模式为请求准备了一组请求拦截器,用于设置各种请求头。
HttpProcessor httpproc = HttpProcessorBuilder.create()
.add(new RequestContent())
.add(new RequestTargetHost())
.add(new RequestConnControl())
.add(new RequestUserAgent("Test/1.1"))
.add(new RequestExpectContinue(true)).build();
HttpCoreContext
我们经常会看见XXXContext对象,Context表示环境,上下文的意思。那么HttpCoreContext 我们可以理解为http的核心上下文,描述着http相关的核心信息。
HttpCoreContext的部分代码如下:
public static HttpCoreContext create() {
return new HttpCoreContext(new BasicHttpContext());
}
public void setAttribute(final String id, final Object obj) {
context.setAttribute(id, obj);
}
public Object getAttribute(final String id) {
return context.getAttribute(id);
}
样例代码中有一小部分代码如下:
HttpCoreContext coreContext = HttpCoreContext.create();
HttpHost host = new HttpHost("localhost", 8080);
coreContext.setTargetHost(host);
我们发现HttpCoreContext.create()的调用,其本质创建BasicHttpContext对象,它是HttpContext的基本实现类,通过调用HttpCoreContext 的setAttribute方法将相关的属性添加至context:BasicHttpContext对象中,而在BasicHttpContext中,通过private final Map<String, Object> map保存这些属性。
DefaultBHttpClientConnection
类图如下:
通过类图可知:DefaultBHttpClientConnection继承了BHttpConnectionBase类,实现了HttpClientConnection接口。
-
HttpConnection : A generic HTTP connection, useful on client and server side,描述了关于http连接的通用信息。
-
HttpInetConnection:An HTTP connection over the Internet Protocol (IP).描述了连接在传输层相关的信息。
-
HttpClientConnection: A client-side HTTP connection, which can be used for sending requests and receiving responses. 客户端的http连接,用于发送请求,接受响应。
所以我们可以把DefaultBHttpClientConnection理解为客户端与服务端的桥梁,用于请求的发送、响应接受。在创建连接对象时,我们需要指定连接的地址,也就是样例中创建的Socket对象,指定地址和端口,然后将其绑定至连接。
如何理解用于发送请求,接受响应?
见DefaultBHttpClientConnection部分源码如下:
@Override
public void sendRequestHeader(final HttpRequest request)
throws HttpException, IOException {
Args.notNull(request, "HTTP request");
ensureOpen();
this.requestWriter.write(request);
onRequestSubmitted(request);
incrementRequestCount();
}
@Override
public void sendRequestEntity(final HttpEntityEnclosingRequest request)
throws HttpException, IOException {
Args.notNull(request, "HTTP request");
ensureOpen();
final HttpEntity entity = request.getEntity();
if (entity == null) {
return;
}
final OutputStream outStream = prepareOutput(request);
entity.writeTo(outStream);
outStream.close();
}
@Override
public HttpResponse receiveResponseHeader() throws HttpException, IOException {
ensureOpen();
final HttpResponse response = this.responseParser.parse();
onResponseReceived(response);
if (response.getStatusLine().getStatusCode() >= HttpStatus.SC_OK) {
incrementResponseCount();
}
return response;
}
@Override
public void receiveResponseEntity(
final HttpResponse response) throws HttpException, IOException {
Args.notNull(response, "HTTP response");
ensureOpen();
final HttpEntity entity = prepareInput(response);
response.setEntity(entity);
}
RequestExecutor
HttpRequestExecutor,我们只看其名,就可以感受到它是HttpRuquest的执行器,究竟官方怎么定义的呢?
HttpRequestExecutor is a client side HTTP protocol handler based on the blocking (classic) I/O model. HttpRequestExecutor relies on HttpProcessor to generate mandatory protocol headers for all outgoing messages and apply common, cross-cutting message transformations to all incoming and outgoing messages. Application specific processing can be implemented outside HttpRequestExecutor once the request has been executed and a response has been received.
大致讲了这么几点
-
HttpRequestExecutor是一个基于经典阻塞 I/O模型的客户端HTTP协议处理对象。
-
它依赖于HttpProcessor,对所有传出的消息(也就是发送的请求)添加协议头(也就是我们所说的请求头);对于所有传出/传入的消息都将应用通用的、跨领域的消息转换。
通过对外暴露的函数
-
public void preProcess( HttpRequest , HttpProcessor , HttpContext)
对请求的预处理,实质是调用HttpProcessor的相关操作,给请求添加请求头。 -
public void postProcess( HttpRequest , HttpProcessor , HttpContext)
-
对响应后处理,实质是调用HttpProcessor的相关操作,给响应添加响应头。
-
public void execute(HttpRequest , HttpProcessor , HttpContext)
执行请求的发送
源码如下:
public HttpResponse execute(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
try {
HttpResponse response = doSendRequest(request, conn, context);
if (response == null) {
response = doReceiveResponse(request, conn, context);
}
return response;
} catch (final IOException ex) {
closeConnection(conn);
throw ex;
} catch (final HttpException ex) {
closeConnection(conn);
throw ex;
} catch (final RuntimeException ex) {
closeConnection(conn);
throw ex;
}
}
本质调用其内部的doSendRequest(request, conn, context)或doReceiveResponse(request, conn, context)方法; 查看doSendRequest或doReceiveResponse的源码,其调用DefaultBHttpClientConnection的send系列与receive系列方法,可见请求最终的发送以及响应最终的接收是DefaultBHttpClientConnection对象。
总结
通过对关键对象的梳理,我相信我们对发送请求过程中各对象的作用也有了初步地理解,这也便于我们今后开发时对各对象灵活的运用。