OKHttp Source Code Analysis (2) RequestBody

Keywords: OkHttp JSON network

1. Overview

In the previous blog, the surface source code of the OKHttp framework was analyzed using the get request as an example, see: OKHttp Source Code Analysis (1)

Most of the API s used in post requests are the same as those used in get requests. The biggest difference is the post method of the Request.Builder class, which sets the body of the post request and receives parameters of the RequestBody class and subclass objects.

The source code for the post method of the Request.Builder class is:

    public Builder post(RequestBody body) {
      return method("POST", body);
    }

The source code for the method is:

    public Builder method(String method, RequestBody body) {
      this.method = method;
      this.body = body;
      return this;
    }

The purpose of this method is to assign a body object to the body field of the Request object.

stay OKHttp Source Code Analysis (1) We know that both the OKHttpClient object and the Request object are passed to the RealCall class, that is, you can get the body object in the RealCall object, then you can call the writeTo method of the body to get the stream object and write data to the server.The code in the body's writeTo method works like the post upload parameter of httpURLconnection, and we can learn by comparison.

Where to call the body's writeTo method is not the focus of this article, which focuses on the analysis of the RequestBody class and its subclasses.
Specifically the following points:
1, Core methods in the RequestBody class
2, Create method in RequestBody class
3, FromBody class
4, MultipartBody class

Two, the RequestBody class and its core methods

The RequestBody class is the core class for uploading data, and its writeTo method obtains the stream object and then writes the request body data to the server.This is the core method of uploading data.

There are three core methods in the RequestBody class:

1public abstract MediaType contentType()//data type
2,public long contentLength()//Data Length
3,public abstract void writeTo(BufferedSink sink)//Write operation

As little partners who are familiar with the http protocol know, both data types and data lengths exist when http uploads data, so the first two methods are not introduced.The third method is the focus below.

The writeTo method in the RequestBody class is abstract and is implemented in subclasses, so the logic for writing specific data is also in subclasses.What you need to explain here is the BufferedSink class.

The BufferedSink class is a class in square's open source IO framework Okio that encapsulates OutputStream, which is essentially an output stream with a write method.The Okio framework and the BufferedSink class are also not the focus of this introduction. They are not explained here.Use BufferedSink as OutputStream.

3. create method of RequestBody class

We know that RequestBody is an abstract class that cannot be instantiated.The RequestBody class therefore provides a create method to create an instance object of RequestBody.

The create method of the RequestBody class has multiple overloads, but only two are important:

//1, create a RequestBody object to upload byte data.
create(final MediaType contentType, final byte[] content,final int offset, final int byteCount)
//2, Create a RequestBody object to upload File data.
create(final MediaType contentType, final File file)

1, Create RequestBody object to upload byte data

The surface meaning of this method is to upload byte data, but depending on the programmer's sixth sense, it is clear that it can upload String and file data.String and file data can be easily converted to byte arrays.Because File files are large and converting to Byte arrays takes up too much memory, a dedicated method for File data is provided.This method is often used for uploading String data.This method is called at the bottom of uploading Json data in the usage details of OKHttp.

The original code for the method is:

  public static RequestBody create(final MediaType contentType, final byte[] content,
      final int offset, final int byteCount) {
    return new RequestBody() {
      @Override public MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return byteCount;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.write(content, offset, byteCount);
      }
    };
  }

In fact, the implementation of the method is very simple, that is, to create a subclass object of RequestBody and override the three methods.The following focuses on the writeTo method.The implementation of the writeTo method has only one line of code:
sink.write(content, offset, byteCount);

This line of code means: Write the byte array, start with offset, and write the byteCount length.
The sink.write method is similar to the OutputStream write method, so we should understand what this line of code means.

Here we find that although OKHttp is a low-level network request framework, the low-level implementation is not cumbersome, which is similar to the use of httpURLconnection.

2, Create RequestBody object to upload File data

The original code for the method is as follows:

  public static RequestBody create(final MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("content == null");

    return new RequestBody() {
      @Override public MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return file.length();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
          source = Okio.source(file);
          sink.writeAll(source);
        } finally {
          Util.closeQuietly(source);
        }
      }
    };
  }

This method is also an instance object for creating the RequestBody class, and the writeTo method is also highlighted below.The core code for the writeTo method is:

source = Okio.source(file);//Get the input stream object from the file.
sink.writeAll(source);//Write out the input stream object.

WteAll is a method of the RealBufferSink class, which is also part of the Okio framework.The original code for the method is:

  @Override public long writeAll(Source source) throws IOException {
    long totalBytesRead = 0;
    for (long readCount; (readCount = source.read(buffer, Segment.SIZE)) != -1; ) {
      totalBytesRead += readCount;
      emitCompleteSegments();
    }
    return totalBytesRead;
  }

The source code for the emitCompleteSegments method is:

  @Override public BufferedSink emitCompleteSegments() throws IOException {
    if (closed) throw new IllegalStateException("closed");
    long byteCount = buffer.completeSegmentByteCount();
    if (byteCount > 0) sink.write(buffer, byteCount);
    return this;
  }

Is there a familiar feeling?Yes, this is the common IO stream operation.

Four, FormBody class

Although RequestBody provides a create method to upload String-type data.However, uploading key values requires splicing data, which is cumbersome, so Professional upload key values for data FormBody class is provided in the framework.

The basic usage of the FormBody class is as follows:

 FormBody.Builder formBody = new FormBody.Builder();//Create form requester
 formBody.add("username","zhangsan");//Pass key-value pair parameter
 formBody.add("password","000000");//Pass key-value pair parameter
 RequestBody body= formBody.build();

This is obviously the Builder design pattern.

1, Analyse the Builder class inside FormBody

Builder is the internal class of FormBody, the source code is:

public static final class Builder {
    private final List<String> names = new ArrayList<>();
    private final List<String> values = new ArrayList<>();

    public Builder add(String name, String value) {
      names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true));
      values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true));
      return this;
    }
    public FormBody build() {
      return new FormBody(names, values);
    }
  }

The logic of this class is very simple, with the following emphasis:
1. First create two list collections, one for key s and one for value s;
2. The add method is used to place key-value pairs in two sets.
3. The build method passes the key and value collections to the FormBody object.

2, the writeTo method of the FormBody class

Let's focus on the source code for the writeTo method of the FormBody class:

  @Override public void writeTo(BufferedSink sink) throws IOException {
    writeOrCountBytes(sink, false);
  }

The core source code for the writeOrCountBytes method is:

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

It's still a familiar feeling to see if this is still the case when post ing requests to pass key-value pair parameters using HttpURLconnection.

Five, MultipartBody class

You can tell from the class name that this class is a multiple body by uploading key-value pairs of data and File data at the same time.For example, when sending circles of friends in a WeChat, you need to upload both text and pictures, so you need to use this multiple body.

The basic use of the MultipartBody class is as follows:

MultipartBody multipartBody =new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("groupId",""+groupId)//Add Key Value Pair Parameter
        .addFormDataPart("title","title")
        .addFormDataPart("file",file.getName(),RequestBody.create(MediaType.parse("file/*"), file))//Add Files
        .build();

This is obviously the builder Design pattern.

1, analyze the MultipartBody internal class builder

MultipartBody implements the simultaneous upload of key-value pairs of data and File data in a similar way to httpURLconnection, which mimics the data format used when submitting Form form data on the Web.

Look at the addFormDataPart method below, which has two important overloads:

//1, add key-value pairs to the data
addFormDataPart(String name, String value)
//2, Add File Data
addFormDataPart(String name, String filename, RequestBody body)

First look at the source code for the Add Key Value to Data addFormDataPart method:

    public Builder addFormDataPart(String name, String value) {
      return addPart(Part.createFormData(name, value));
    }

Look again at the addPart method:

    public Builder addPart(Part part) {
      if (part == null) throw new NullPointerException("part == null");
      parts.add(part);
      return this;
    }

The nature of this method assigns a Part object to the Builder's parts field.Let's look at the creation of a part object.

As you can see from the addFormDataPart method, the code for creating the part object is:
Part.createFormData(name, value).
The source code for the method is:

    public static Part createFormData(String name, String value) {
      return createFormData(name, null, RequestBody.create(null, value));
    }

The source code for the createFormData method is:

    public static Part createFormData(String name, String filename, RequestBody body) {
      StringBuilder disposition = new StringBuilder("form-data; name=");
      appendQuotedString(disposition, name);

      if (filename != null) {
        disposition.append("; filename=");
        appendQuotedString(disposition, filename);
      }

      return create(Headers.of("Content-Disposition", disposition.toString()), body);
    }

The initial encapsulation begins in the form data format submitted on the web.Encapsulate name and fileName into the Header object.

The source code for the create method of the Part class is:

    public static Part create(Headers headers, RequestBody body) {
      return new Part(headers, body);
    }

Pass the header and body objects to the part object, and place the part object in the Builder's field parts collection.

First look at the source code for the Add File Data addFormDataPart method:

   public Builder addFormDataPart(String name, String filename, RequestBody body) {
      return addPart(Part.createFormData(name, filename, body));
    }

First get the File object to the body object.The following methods, add Part and createFormData of the art class, have been explained.

The process for adding File data is as follows:
1. First get the body object from the File object.
2, encapsulate the name and fileName into the Header object.
3, pass the header and body objects to the part object, and then put the part object into the Builder's field parts collection.

Finally, look at the build method for the Builder class:

    public MultipartBody build() {
      return new MultipartBody(boundary, type, parts);
    }

Create a MultipartBody object and pass three parameters to it.
boundary and type are used to encapsulate data formats, and parts encapsulate header and body data.

2. Analyzing the WriteTo method of the MultipartBody class

The source code for the method is:

  @Override public void writeTo(BufferedSink sink) throws IOException {
    writeOrCountBytes(sink, false);
  }

The source code for the writeOrCountBytes method is as follows:

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

    Buffer byteCountBuffer = null;

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

      sink.write(DASHDASH);//Write data format characters
      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);//Write Header Data
        }
      }

      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) {
        byteCountBuffer.clear();
        return -1L;
      }

      sink.write(CRLF);

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

      sink.write(CRLF);
    }

    sink.write(DASHDASH);//Write data format characters
    sink.write(boundary);
    sink.write(DASHDASH);
    sink.write(CRLF);

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

    return byteCount;
  }

The WriteTo method of the MultipartBody class is slightly more complex, but this part of the code is the key to uploading data and deserves our study.

Posted by Pjack125 on Thu, 04 Jul 2019 13:32:24 -0700