前一篇中我们说到同步RestTemplate使用的默认的requestFactory为SimpleClientHttpRequestFactory, 当我们执行请求时候requestFactory.createRequest()获取到默认的ClientHttpRequest就是本篇要讲的SimpleBufferingClientHttpRequest.这个类本身比较简单.
protected构造函数接受2个参数HttpURLConnection(用于和服务器通信) 和 布尔型outputStreaming(标记是否有数据需要写)
SimpleBufferingClientHttpRequest(HttpURLConnection connection, boolean outputStreaming) {
this.connection = connection;
this.outputStreaming = outputStreaming;
}
RestTemplate在执行request.execute()时会调用SimpleBufferingClientHttpRequest的executeInternal方法. 关键的处理点都是使用构造函数传进来的HttpURLConnection进行的
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
addHeaders(this.connection, headers);
// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
if (HttpMethod.DELETE == getMethod() && bufferedOutput.length == 0) {
this.connection.setDoOutput(false);
}
if (this.connection.getDoOutput() && this.outputStreaming) {
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
//1.链接服务器
this.connection.connect();
//2.发送请求
if (this.connection.getDoOutput()) {
//2-A 发送带数据的请求
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
}
else {
//2-B 发送不带数据的请求
// Immediately trigger the request in a no-output scenario as well
this.connection.getResponseCode();
}
return new SimpleClientHttpResponse(this.connection);
}
1. 连接服务器:
通过图2-1的调用栈我们可看到HttpURLConnection先创建一个HttpClient对象(保存在其http属性上), 在创建httpClient时执行连接服务器请求.
在NetworkClient.doConnect(serverHostname, port)中我们会发现亲切的网络scoket网络编程的代码 new Socket() 和 socket.connect(new InetSocketAddress(serverHostname, port), connectTimeout).
2.发送请求:
在讲述代码之前我先了解下http协议(如图2-2: SP--空格, CRLF--换行):
http请求报文包括:请求行、请求头部、空行和请求数据四个部分组成; 响应报文包括:响应行、响应头、空行、响应数据
请求方式为POST,PUT,PATCH,DELETE是可以有请求数据的, 所以doOutput=true; 其他为false.
2-A:doOutput=true时:
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream())这个方法,
2-A.1: 先通connection.getOutputStream()向服务端发送请请求行、请求头部、空行3部分数据,调用栈如图2-3所示;
2-A.2: 然后FileCopyUtils.copy() 再将请求数据发送到服务端
细心的读者可能会有这样的疑问: 为什么连接服务器用的是socket, 但是到发送请求去不见socket的踪影? 这是个好问题:
在构造函数创建socket时会为其创建一个属性SocketImpl impl = new SocksSocketImpl(), 实际上连接服务器是有impl来完成的;
而在发送时用的SocketOutputStream对象原本来自于SocksSocketImpl的socketOutputStream属性(继承之AbstractPlainSocketImpl),经过层层包装(printStream(BufferedOutputStream(SocketOutputStream)))成为httpClient的serverOutput属性(或者说httpClient持有的serverOutput最终指向的就是SocksSocketImpl的socketOutputStream);
SocketOutputStream::
private native void socketWrite0(FileDescriptor fd, byte[] b, int off, int len)
SocketOutputStream在写操作时通过持有的文件描述符和之前SocksSocketImpl创建的服务器连接相关联.
2-B:doOutput=false时:
connection.getResponseCode()操作会先去发送请求, 会先调用到HttpUrlConnection#writeRequest方法调用栈和2-A.1一样发送请求行、请求头部、空行3部分数据;
然后再获取SocksSocketImpl的socketInputStream-(记为ss)读取服务端返回的报文. 同时建立到socketInputStream关系:
httpClient#serverInputStream属性:赋值为BufferedOutputStream(socketInputStream)--(记为bs) 指向ss
HttpURLConnection#inputStream属性:赋值为HttpInputStream(BufferedOutputStream)--指向bs最终指向ss
解析读取到响应报文的返回状态码,保存在HttpURLConnection#responseCode属性上
2-A逻辑中没有看到获取返回信息的逻辑, 因为在RestTemplate#handleResponse时候会调用connection.getResponseCode()逻辑如上; 但是之前如果获取过(如2-B已获取过)就直接返回HttpURLConnection#responseCode的值.
注:
这种方式每次请求都会向服务端创建新的连接