spring-web restful http client之:(二)SimpleBufferingClientHttpRequest

前一篇中我们说到同步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-1

2.发送请求:

在讲述代码之前我先了解下http协议(如图2-2: SP--空格, CRLF--换行):

图2-2

 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() 再将请求数据发送到服务端
图2-3

细心的读者可能会有这样的疑问: 为什么连接服务器用的是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的值.

 

注:

这种方式每次请求都会向服务端创建新的连接

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值