okhttp 源码解析 - 网络协议的实现 - 请求流程: 请求的发送与响应的接收

http协议相关

一个http请求的过程

  • 输入url和参数
  • ->DNS解析(访问域名服务器53号端口,根据域名拿到ip,可能会拿到好几个ip)
  • ->根据ip和端口号连接socket(TCP三次握手已封装在socket api内部,对开发者透明)
  • -> socket连接成功后,往socket输出流中写入http报文

一个响应的接收过程

  • 服务器接受请求并处理后发出响应
  • 客户端从socket输入流中读取http报文

注意: 请求行/状态行和报文头部是字符,而请求体和响应体可以是字符,也可以是二进制流

http请求报文的格式

《okhttp 源码解析 - 网络协议的实现 - 请求流程: 请求的发送与响应的接收》 Paste_Image.png

注: 下图第一行名字标错了,是叫请求行.状态行是响应报文中的叫法.

《okhttp 源码解析 - 网络协议的实现 - 请求流程: 请求的发送与响应的接收》 Paste_Image.png

特殊一点的: 文件上传的http报文格式:

注:下图请求参数为:
键值对:
“uploadFile555″,”1474363536041.jpg”
“api_secret777″,”898767hjk”
文件:
“uploadFile”,”/storage/emulated/0/qxinli.apk”

《okhttp 源码解析 - 网络协议的实现 - 请求流程: 请求的发送与响应的接收》 upload

http响应报文的格式

《okhttp 源码解析 - 网络协议的实现 - 请求流程: 请求的发送与响应的接收》 Paste_Image.png

《okhttp 源码解析 - 网络协议的实现 - 请求流程: 请求的发送与响应的接收》 Paste_Image.png

常见的json response:

《okhttp 源码解析 - 网络协议的实现 - 请求流程: 请求的发送与响应的接收》 Paste_Image.png

okhttp中代码执行流程

直接看GetExample中的同步执行代码:核心为RealCall的execute()方法

OkHttpClient client = new OkHttpClient();

  String run(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    try (Response response = client.newCall(request).execute()) {
          //即call.execute(),Call接口的具体实现为RealCall
      return response.body().string();
    }
  }复制代码

RealCall的execute():

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);//只是将call对象添加到Deque<RealCall> runningSyncCalls 中,只是增加了个引用
      Response result = getResponseWithInterceptorChain();//真实执行的地方
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }复制代码

getResponseWithInterceptorChain():
涉及到拦截器机制,参见:okhttp源码解析-coding skills-拦截器机制

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//用户设置的拦截器
    interceptors.add(retryAndFollowUpInterceptor);//重定向和重试的拦截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));//
    interceptors.add(new CacheInterceptor(client.internalCache()));//缓存管理的拦截器
    interceptors.add(new ConnectInterceptor(client));//Dns查询和获取socket连接的地方
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());//用户设置的network拦截器
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));//真实读写socket的地方

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }复制代码

从文章开头,我们分析的请求流程:获取socket连接->往连接的流里读写数据 可以知道
请求流程的实现重点在两个拦截器:ConnectInterceptor和CallServerInterceptor

ConnectInterceptor
具体的DNS解析和一个网络连接的获取请参见 okhttp源码解析-网络框架业务处理-连接的建立与连接池

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);//httpCodec 封装了输入输出流
    RealConnection connection = streamAllocation.connection();//RealConnection 封装了"网络连接"对象

    return realChain.proceed(request, streamAllocation, httpCodec, connection);//将上面的对象封装到chain中
  }复制代码

CallServerInterceptor: 真正读写socket流的地方

注: 这里输入输出流用的不是java的stream,而是okio,其中Sink相当于OutputStream,用于写请求,Source相当于InputStream,用于读取响应

@Override public Response intercept(Chain chain) throws IOException {
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();//将输入输出流的封装对象从chain中拿出
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);//往流中写入请求头

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);//从流中读取响应头
      }


      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);//往输出流中写入请求体
        bufferedRequestBody.close();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))//从流中读取响应体
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }复制代码

从上面看到几个方法:

httpCodec.writeRequestHeaders(request);//往流中写入请求头
httpCodec.createRequestBody(request, request.body().contentLength())
httpCodec.readResponseHeaders(true);//从流中读取响应头
httpCodec.openResponseBody(response)复制代码

httpCodec 接口的实现类有Http1Codec和Http2Codec,我们看Http1Codec:

writeRequestHeaders:


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


/** 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;
  }复制代码

读响应头:


@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.parse(source.readUtf8LineStrict());

      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;
      }

      state = STATE_OPEN_RESPONSE_BODY;
      return responseBuilder;
    } catch (EOFException e) {
      // Provide more context if the server ends the stream before sending a response.
      IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
      exception.initCause(e);
      throw exception;
    }
  }

  /** Reads headers or trailers. */
  public Headers readHeaders() throws IOException {
    Headers.Builder headers = new Headers.Builder();
    // parse the result headers until the first blank line
    for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
      Internal.instance.addLenient(headers, line);
    }
    return headers.build();
  }复制代码

写请求体:createRequestBody

@Override public Sink createRequestBody(Request request, long contentLength) {
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
      // Stream a request body of unknown length.
      return newChunkedSink();
    }

    if (contentLength != -1) {
      // Stream a request body of a known length.
      return newFixedLengthSink(contentLength);
    }

    throw new IllegalStateException(
        "Cannot stream a request body without chunked encoding or a known content length!");
  }

//读写: 在上方intercept中,传入
 Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();复制代码

读响应体:

@Override public ResponseBody openResponseBody(Response response) throws IOException {
    Source source = getTransferStream(response);
    return new RealResponseBody(response.headers(), Okio.buffer(source));
  }复制代码

请求体RequestBody和响应体ResponseBody:

内部封装了输入输出流,具体的读写封装在特定的子类中,

RequestBody的子类有

表单提交的FormBody,文件上传的MultipartBody

FormBody往流中写数据的代码:

key和value要预先url编码成encodedName,encodedValue,放入两个list,然后根据index依次写如输出流

private long writeOrCountBytes(BufferedSink sink, boolean countBytes) {
    long byteCount = 0L;

    Buffer buffer;
    if (countBytes) {
      buffer = new Buffer();
    } else {
      buffer = sink.buffer();
    }

    for (int i = 0, size = encodedNames.size(); i < size; i++) {
      if (i > 0) buffer.writeByte('&');
      buffer.writeUtf8(encodedNames.get(i));
      buffer.writeByte('=');
      buffer.writeUtf8(encodedValues.get(i));
    }

    if (countBytes) {
      byteCount = buffer.size();
      buffer.clear();
    }

    return byteCount;
  }复制代码

MultipartBody 写数据的代码:

注意body也有content-type,这个与header中的content-type不一样,前者是标识这一块body是什么类,而header中的是标识这个http请求是什么类型.
而且,上传body里的content-type有特点的格式:MediaType.parse(type + “; boundary=” + boundary.utf8())


MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {
    this.boundary = boundary;
    this.originalType = type;
    this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
    this.parts = Util.immutableList(parts);
  }

private static final byte[] COLONSPACE = {':', ' '};
  private static final byte[] CRLF = {'\r', '\n'};
  private static final byte[] DASHDASH = {'-', '-'};


 private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
    long byteCount = 0L;

    Buffer byteCountBuffer = null;
    if (countBytes) {
      sink = byteCountBuffer = new Buffer();
    }

    for (int p = 0, partCount = parts.size(); p < partCount; p++) {
      Part part = parts.get(p);
      Headers headers = part.headers;
      RequestBody body = part.body;

      sink.write(DASHDASH);
      sink.write(boundary);
      sink.write(CRLF);

      if (headers != null) {
        for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
          sink.writeUtf8(headers.name(h))
              .write(COLONSPACE)
              .writeUtf8(headers.value(h))
              .write(CRLF);
        }
      }

      MediaType contentType = body.contentType();
      if (contentType != null) {
        sink.writeUtf8("Content-Type: ")
            .writeUtf8(contentType.toString())
            .write(CRLF);
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        sink.writeUtf8("Content-Length: ")
            .writeDecimalLong(contentLength)
            .write(CRLF);
      } else if (countBytes) {
        // We can't measure the body's size without the sizes of its components.
        byteCountBuffer.clear();
        return -1L;
      }

      sink.write(CRLF);

      if (countBytes) {
        byteCount += contentLength;
      } else {
        body.writeTo(sink);
      }

      sink.write(CRLF);
    }

    sink.write(DASHDASH);
    sink.write(boundary);
    sink.write(DASHDASH);
    sink.write(CRLF);

    if (countBytes) {
      byteCount += byteCountBuffer.size();
      byteCountBuffer.clear();
    }

    return byteCount;
  }复制代码

ResponseBody的子类有:

RealResponseBody和CacheResponseBody.前者代码少,主要实现在ResponseBody中:

提供有几个常用方法:

  • bytes():返回byte数组
  • string(): 返回字符串
  • byteStream(): 返回java的inputStream,可用于文件下载
public final byte[] bytes() throws IOException {
    long contentLength = contentLength();
    if (contentLength > Integer.MAX_VALUE) {
      throw new IOException("Cannot buffer entire body for content length: " + contentLength);
    }

    BufferedSource source = source();
    byte[] bytes;
    try {
      bytes = source.readByteArray();
    } finally {
      Util.closeQuietly(source);
    }
    if (contentLength != -1 && contentLength != bytes.length) {
      throw new IOException("Content-Length ("
          + contentLength
          + ") and stream length ("
          + bytes.length
          + ") disagree");
    }
    return bytes;
  }


public final String string() throws IOException {
    BufferedSource source = source();
    try {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    } finally {
      Util.closeQuietly(source);
    }
  }

public final InputStream byteStream() {
    return source().inputStream();
  }复制代码

Response: 用户拿到的最终返回对象

就是响应解析后变成内存对象,http报文各部分分别封装好了.并提供相应的一些方法

public final class Response implements Closeable {
  final Request request;
  final Protocol protocol;
  final int code;
  final String message;
  final Handshake handshake;
  final Headers headers;
  final ResponseBody body;
  final Response networkResponse;
  final Response cacheResponse;
  final Response priorResponse;
  final long sentRequestAtMillis;
  final long receivedResponseAtMillis;

  private volatile CacheControl cacheControl; // Lazily initialized.
  ...
  public boolean isSuccessful() {
    return code >= 200 && code < 300;
  }
  public int code() {
    return code;
  }

  public ResponseBody body() {
    return body;
  }

  public List<String> headers(String name) {
    return headers.values(name);
  }

  public String header(String name) {
    return header(name, null);
  }

  public String header(String name, String defaultValue) {
    String result = headers.get(name);
    return result != null ? result : defaultValue;
  }

  public Headers headers() {
    return headers;
  }
  ...复制代码

总结:

  • 从上面的源码分析可知,okhttp的这一部分,本质上就是对http协议的一个实现,了解http协议,是读懂源码的一个前提.
  • 直接使用okhttp时,涉及到两个类: Request和Response
  • okhttp提供了两种请求体的实现:表单提交时的FormBody,文件上传时的MultiPartBody
  • okhttp提供了响应体的几个接收形式:
    以字符串形式接收:string()方法
    以字节数组的形式接收: bytes()方法
    以流的形式接收: byteStream()方法

参考:
一个http请求的详细过程
HTTP协议详解(真的很经典)

封装到极致的网络库:

HttpUtilForAndroid

    原文作者:HTTP
    原文地址: https://juejin.im/post/58c6033a8ac24707200b3986
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞