OkHttp3.14 源码剖析系列(七)——请求的发起及响应的读取

本文详细剖析了OkHttp3.14中请求的发起及响应的读取过程,涵盖HTTP/1.x和HTTP/2的处理。通过分析writeRequestHeaders、createRequestBody、readRequestHeaders、openResponseBodySource等关键方法,揭示了HTTP/2的流量控制机制,展示了OkHttp在HTTP/2连接中的数据读取和线程调度策略。
摘要由CSDN通过智能技术生成

个人博客:https://blog.N0tExpectErr0r.cn

小专栏:https://xiaozhuanlan.com/N0tExpectErr0r

系列索引

本系列文章基于 OkHttp3.14

OkHttp3.14 源码剖析系列(一)——请求的发起及拦截器机制概述

OkHttp3.14 源码剖析系列(二)——拦截器大体流程分析

OkHttp3.14 源码剖析系列(三)——缓存机制分析

OkHttp3.14 源码剖析系列(四)——连接的建立概述

OkHttp3.14 源码剖析系列(五)——路由选择机制

OkHttp3.14 源码剖析系列(六)——连接复用机制及连接的建立

OkHttp3.14 源码剖析系列(七)——请求的发起及响应的读取

前言

终于来到了我们 OkHttp 的最后一个部分——请求的发起。让我们回顾一下 CallServerInterceptor 的大体流程:

  1. 调用 exchange.writeRequestHeaders 写入请求头
  2. 调用 exchange.createRequestBody 获取 Sink
  3. 调用 ResponseBody.writeTo 写入请求体
  4. 调用 exchange.readResponseHeaders 读入响应头
  5. 调用 exchange.openResponseBody 方法读取响应体

而我们知道,Exchange 最后实际上转调到了 ExchangeCodec 中的对应方法,而 ExchangeCodec 有两个实现——Http1ExchangeCodecHttp2ExchangeCodec

它们的创建过程在创建连接的过程中的 RealConnection.newCodec 方法中实现:

ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
   
    if (http2Connection != null) {
   
        return new Http2ExchangeCodec(client, this, chain, http2Connection);
    } else {
   
        socket.setSoTimeout(chain.readTimeoutMillis());
        source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
        sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
        return new Http1ExchangeCodec(client, this, source, sink);
    }
}

实际上是根据 Http2Connection 是否为 null 进行判断。

下面我们分别对 HTTP1 中及 HTTP2 中的处理进行分析:

HTTP/1.x

writeRequestHeaders

@Override
public void writeRequestHeaders(Request request) throws IOException {
   
    String requestLine = RequestLine.get(
            request, realConnection.route().proxy().type());
    writeRequest(request.headers(), requestLine);
}

这里首先调用了 RequestLine.get 方法获取到了 requestLine 这个 String,之后通过调用 writeRequest 方法将其写入。

我们首先看到 RequestLine.get 方法:

/**
 * Returns the request status line, like "GET / HTTP/1.1". This is exposed to the application by
 * {@link HttpURLConnection#getHeaderFields}, so it needs to be set even if the transport is
 * HTTP/2.
 */
public static String get(Request request, Proxy.Type proxyType) {
   
    StringBuilder result = new StringBuilder();
    result.append(request.method());
    result.append(' ');
    if (includeAuthorityInRequestLine(request, proxyType)) {
   
        result.append(request.url());
    } else {
   
        result.append(requestPath(request.url()));
    }
    result.append(" HTTP/1.1");
    return result.toString();
}

这里实际上就是在构建 HTTP 协议中的第一行,包括请求的 method、url、HTTP版本等信息。

我们接着看到 writeRequest 方法:

/**
 * Returns bytes of a request header for sending on an HTTP transport.
 */
public void writeRequest(Headers headers, String requestLine) throws IOException {
   
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
   
        sink.writeUtf8(headers.name(i))
                .writeUtf8(": ")
                .writeUtf8(headers.value(i))
                .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
}

这里首先写入了 statusLine,之后将 Header 以 key:value 的形式写入,最后写入了一个空行,到这里我们的请求头就成功写入了(具体可以看到 HTTP/1.x 的请求格式)。

createRequestBody

@Override
public Sink createRequestBody(Request request, long contentLength) throws IOException {
   
    if (request.body() != null && request.body().isDuplex()) {
   
        throw new ProtocolException("Duplex connections are not supported for HTTP/1");
    }
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
   
        // Stream a request body of unknown length.
        return newChunkedSink();
    }
    if (contentLength != -1L) {
   
        // Stream a request body of a known length.
        return newKnownLengthSink();
    }
    throw new IllegalStateException(
            "Cannot stream a request body without chunked encoding or a known content length!");
}

这里首先对 Transfer-Encoding:chunked 的情况进行了处理,返回了 newChunkedSink 方法的结果,之若 contentLength 是确定的,则返回 newKnownLengthSink 方法的结果。

让我们分别看到这两个方法。

newChunkedSink
private Sink newChunkedSink() {
   
    if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
    state = STATE_WRITING_REQUEST_BODY;
    return new ChunkedSink();
}

其实这里就是构建并返回了一个继承于 SinkChunkedSink 对象,我们可以看看它的 write 方法:

@Override
public void write(Buffer source, long byteCount) throws IOException {
   
    if (closed) throw new IllegalStateException("closed");
    if (byteCount == 0) return;
    sink.writeHexadecimalUnsignedLong(byteCount);
    sink.writeUtf8("\r\n");
    sink.write(source, byteCount);
    sink.writeUtf8("\r\n");
}

首先写入了十六进制的数据大小,之后写入了数据。

newKnownLengthSink
private Sink newKnownLengthSink() {
   
    if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
    state = STATE_WRITING_REQUEST_BODY;
    return new KnownLengthSink();
}

这里也是构建并返回了一个继承于 SinkKnownLengthSink 对象,我们可以看到其 write 方法:

@Override
public void write(Buffer source, long byteCount) throws IOException {
   
    if (closed) throw new IllegalStateException("closed");
    checkOffsetAndCount(source.size(), 0, byteCount);
    sink.write(source, byteCount);
}

可以看到,其实就是对数据进行写入,没有非常特别的地方。

readRequestHeaders

@Override
public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
   
    if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
   
        throw new IllegalStateException("state: " + state);
    }
    try {
   
    	// 读取statusLine
        StatusLine statusLine = StatusLine.parse(readHeaderLine());
        // 构建 Response(包含status信息及Header)
        Response.Builder responseBuilder = new Response.Builder()
                .protocol(statusLine.protocol)
                .code(statusLine.code)
                .message(statusLine.message)
                .headers(readHeaders());
        if (expectContinue && statusLine.code == HTTP_CONTINUE) {
   
            return null;
        } else if (statusLine.code == HTTP_CONTINUE) {
   
            state = STATE_READ_RESPONSE_HEADERS;
            return responseBuilder;
        }
        state = STATE_OPEN_RESPONSE_BODY;
        return responseBuilder;
    } catch (EOFException e) {
   
        // Provide more context if the server ends the stream before sending a response.
        String address = "unknown";
        if (realConnection != null) {
   
            address = realConnection.route().
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值