本篇探究HTTPClient客户端链接的最底层建立连接、发送请求、接收响应的原理,接下来的文章会从底层依次向上探究,本文尽可能少粘代码,只讲具体功能,如有错误请各位指正。
建立连接
在实现HttpClientConnectionOperator接口的DefaultHttpClientConnectionOperator类下,找到了客户端建立与服务器socket连接的代码,在调用该类之前已经已经保有了关于进行socket连接的一系列信息。
在DefaultHttpClientConnectionOperator类的connect方法中,完成了:
- 通过ConnectionSocketFactory,根据传入的socket参数(SocketConfig)来构建一个socket连接
把该链接托管给ManagedHttpClientConnection
其中细节:
一:Lookup接口
该接口实现的是通过传入一个参数,获得跟该参数有关的实例。
在该类中用于获取ConnectionSocketFactory实例
private Lookup<ConnectionSocketFactory> getSocketFactoryRegistry(final HttpContext context) {
Lookup<ConnectionSocketFactory> reg = (Lookup<ConnectionSocketFactory>) context.getAttribute(
SOCKET_FACTORY_REGISTRY);
if (reg == null) {
reg = this.socketFactoryRegistry;
}
return reg;
}
可以看出,该方法尝试通过在HttpContext中根据SOCKET_FACTORY_REGISTRY常量获取一个保存有很多ConnectionSocketFactory实例的对象,在connect方法中
final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);
final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());
根据HttpHost的Scheme(刚读到此处不知其含义,可能是该host的名字,如baidu/google,也有可能是协议名http/ftp/file)来获取ConnectionSocketFactory实例。 Lookup的查找功能的实现是依靠的final map,在实现该接口的Registry类中可以看出。但具体如何将实例保存在map中、如何保证该map的持久性需要进一步阅读了解。
二:HttpClientConnection的socket连接托管
HttpClientConnection是一个客户端的链接(client-side connection)
对于已经生成的socket连接,需要在BHttpConnectionBase及其继承中进行操作。
BHttpConnectionBase提供了客户端的连接和服务器的连接通用的方法。
1:确保socket的线程安全
该类的所有对socket的操作(存、取、关闭、验证是否有效、取出socket的流等),均通过Java本身的AtomicReference(原子性引用)来操作保证其线程安全(基本原理移步http://www.tuicool.com/articles/RRbiye)。
绑定有效的socket进行数据传输:
把已经建立好的socket对象放入其属性AtomicReference中保证原子性操作
验证socket的有效性的具体实现如下:
首先,把socket的timeout设为1
然后调用在该socket中获取的SessionInputBufferImpl(该类在httpcore中)的fillBuffer方法。该方法用于判断在流中,当前读取位置之后是否还有数据(具体实现:校准读取位置,获取本次读取数据长度和已保存数据长度,read前两个长度的差是否为-1?返回-1:更新读取长度)。
该方法(isStable())为public,可以随时在socket中调用来询问是否有效。
2:发送请求、获得响应
HttpMessage包含了客户端向服务器的请求和服务器到客户端的响应相关内容。prepareInput方法把接收到的HttpMessage对象中的输入流、头信息等封装成HttpEntity用于请求。
它的子类 DefaultBHttpClientConnection 在父类的基础上,又完成了发送请求(头部、实体)和接收响应(头、实体)的功能。
//经过一系列处理之后 由ClientExecChain整合所有数据 并通过HttpRequestExecutor来发送请求获得响应
发送请求
在BHttpConnectionBase的继承DefaultBHttpClientConnection的构造方法中,获取了socket的输入流,并根据根据该输入流生成一个HttpMessageWriter
this.requestWriter = (requestWriterFactory != null ? requestWriterFactory : DefaultHttpRequestWriterFactory.INSTANCE).create(getSessionOutputBuffer());
当sendRequestHeader时,调用了HttpMessageWriter的write方法,把已经处理好的request的信息输入到获取的socket的输入流中。至此请求已经发送完成。具体是否请求成功需要根据响应判断。
注:
Request = Request-Line
(( general-header
| request-header
| entity-header ) CRLF)
CRLF
[ message-body ]
HttpEntityEnclosingRequest
获取响应
1 获取头部
在DefaultBHttpClientConnection构造方法中,根据socket的输入流和相关参数MessageConstraints 生成了 HttpMessageParser。 该类用于Httpmessage(request、response)与socket的流内数据的转换
this.responseParser = (responseParserFactory != null ? responseParserFactory : DefaultHttpResponseParserFactory.INSTANCE).create(getSessionInputBuffer(), constraints);
在receiveResponseHeader时 调用AbstractMessageParser的parse方法。parse方法分两步完成响应头的封装
<1>.获取响应状态
在其子类DefaultHttpResponseParser的parseHead方法中:
final int i = sessionBuffer.readLine(this.lineBuf);
此处根据Response的构成发现,读的第一行(红色部分)是response的响应信息。
把响应信息写入linebuf中,对其进行格式处理,并把其作为response的响应状态信息封装response。
<2>.获取响应头信息
上一步完成之后,不断按行读取socket中的流,直到没有下一行或者是某行为空,把读取的信息解析成header封装到response中。
2 获取内容
根据status >= HttpStatus.SC_OK
&& status != HttpStatus.SC_NO_CONTENT
&& status != HttpStatus.SC_NOT_MODIFIED
&& status != HttpStatus.SC_RESET_CONTENT;
来判断响应是否有实体 若有的话
在连接中取出SessionInputBuffer,并根据头信息中内容长度来生成InputStream封装进Entity
判断实体长度是chunked(分段的)、identity(未给定大小)还是已经给定的大小。前两种暂时将Entity的ContentLength设为-1