OkHttp3 source code details Request class

Keywords: Operation & Maintenance network JSON Google less



Every network request is a request. Request is the encapsulation of URL, method, header and body. It is also the encapsulation of request line, request header and entity content in Http protocol

 public final class Request {
   private final HttpUrl url;
   private final String method;
   private final Headers headers;
   private final RequestBody body;
   private final Object tag;
 
   private volatile CacheControl cacheControl; // Lazily initialized.

1.HttpUrl

HttpUrl is mainly used to regulate common url connection and parse the components of url

Let's use the following example to illustrate the use of httpUrl
https://www.google.com/search?q=maplejaw
① parse url string with parse:

1.  HttpUrl url =  HttpUrl.parse("https://www.google.com/search?q=maplejaw");

(2) common through the constructor mode:

1.  HttpUrl url =  new  HttpUrl.Builder()
2.  .scheme("https")
3.  .host("www.google.com")
4.  .addPathSegment("search")
5.  .addQueryParameter("q",  "maplejaw")
6.  .build();

2.Headers

Headers are used to configure request headers. You must be familiar with the configuration of request headers, such as' content type ',' user agent 'and' cache control '. ` ` ` `

`There are also two ways to create Headers. As follows:
(1)of() creation: the array passed in must be an even pair, otherwise an exception will be thrown. `

1.  Headers.of("name1","value1","name2","value2",.....);

You can also use its overloaded method of (map map map) to create

(2) builder mode creation:``

1.  Headers mHeaders=new  Headers.Builder()
2.  .set("name1","value1")//set means name1 is unique, which will overwrite the existing
3.  .add("name2","value2")//add does not overwrite the existing header. There can be multiple headers
4.  .build();

Let's take a look at the internal part of the header. It's very simple not to paste the source code. The internal part of the Headers is to save the header private final String[] namesAndValues through an array. You may have such a question, why not use the array instead of the Map? Because the Map Key is unique, and the header requirements are not unique

In addition, is the array easy to access data? It's convenient. Let's look at three ways

Finally, the toString method is transformed into String to facilitate writing the request header,

1.  @Override
2.  public  String toString()  {
3.  StringBuilder result =  new  StringBuilder();
4.  , size = size(); i < size; i++)  {
5.  result.append(name(i)).append(": ").append(value(i)).append("\n");
6.  }
7.  return result.toString();
8.  }

10.  /** Returns the field at {@code position} or null if that is out of range. */
11.  public  String name(int index)  {
12.  ;
13.  || nameIndex >= namesAndValues.length)  {
14.  return  null;
15.  }
16.  return namesAndValues[nameIndex];
17.  }

19.  /** Returns the value at {@code index} or null if that is out of range. */
20.  public  String value(int index)  {
21.  +  ;
22.  || valueIndex >= namesAndValues.length)  {
23.  return  null;
24.  }
25.  return namesAndValues[valueIndex];
26.  }

3.RequestBody

Requestbody is the content of the request entity. Let's see how to build a requestbody

(1) create the request. Create() method

1.  public  static  final  MediaType TEXT =  MediaType.parse("text/plain; charset=utf-8");
2.  public  static  final  MediaType STREAM =  MediaType.parse("application/octet-stream");
3.  public  static  final  MediaType JSON =  MediaType.parse("application/json; charset=utf-8");

5.  //Build string request body
6.  RequestBody body1 =  RequestBody.create(TEXT,  string);
7.  //Build byte request body
8.  RequestBody body2 =  RequestBody.create(STREAM,  byte);
9.  //Build file request body
10.  RequestBody body3 =  RequestBody.create(STREAM, file);
11.  //post upload json
12.  RequestBody body4 =  RequestBody.create(JSON, json);//json is of type String

14.  //Set the request body to the request method
15.  Request request =  new  Request.Builder()
16.  .url(url)
17.  .post(xx)// xx represents one of body1, body2, body3, body4
18.  .build();

(2) build the form request body and submit the key value pair (OkHttp3 does not have the FormEncodingBuilder class, instead of the more powerful FormBody:)

1.  //Build form RequestBody
2.  RequestBody formBody=new  FormBody.Builder()
3.  .add("name","maplejaw")
4.  .add(")
5.  ...
6.  .build();

(3) build request body of partitioned form: (OkHttp3 cancels MultipartBuilder and replaces MultipartBody.Builder()

              You can add forms, binary data such as files, etc.)
1.  public  static  final  MediaType STREAM =  MediaType.parse("application/octet-stream");
2.  //Build form RequestBody
3.  RequestBody multipartBody=new  MultipartBody.Builder()
4.  .setType(MultipartBody.FORM)//Indicates multipart / form data type
5.  .addFormDataPart(") //Add form data
6.  .addFormDataPart("avatar","111.jpg",RequestBody.create(STREAM,file)) //Add a file, where avatar is the form name and 111.jpg is the file name.
7.  .addPart(..)//This method is used to add RequestBody,Headers and custom parts. Generally speaking, the above is enough
8.  .build();

Knowing the creation of RequestBody, let's take a look at the source code
RequestBody is to request entity content. If there is no entity content in a Get request, post submission is available. In addition, only post submission is available when the form is uploaded when the browser communicates with the server. Therefore, RequestBody encapsulates the entity content corresponding to the browser form when it is uploaded. It is unclear what the entity content is Communication details of Http protocol of Android

There are three ways to create RequestBody in OkHttp3

① mode 1:

1.  public  static  RequestBody create(MediaType contentType,  String content)  {
2.  Charset charset =  Util.UTF_8;
3.  if  (contentType !=  null)  {
4.  charset = contentType.charset();//Creation method of MediaType for ContentType in request header: public static final MediaType TEXT=
5.  //MediaType.parse("text/plain; charset=utf-8")
6.  if  (charset ==  null)  {
7.  charset =  Util.UTF_8;<span style="font-family:Microsoft YaHei;">//If charset is not specified in contentType, UTF-8 is used by default</span>
8.  contentType =  MediaType.parse(contentType +  "; charset=utf-8");
9.  }
10.  }
11.  byte[] bytes = content.getBytes(charset);
12.  return create(contentType, bytes);
13.  }

② method 2: FormBody form creation, let's take a look

FormBody is used for common post form upload key value pairs. Let's first look at the creation method, and then look at the source code

1.  RequestBody formBody=new  FormBody.Builder()
2.  .add("name","maplejaw")
3.  .add(")
4.  ...
5.  .build();

FormBody source code

1.  public  final  class  FormBody  extends  RequestBody  {
2.  private  static  final  MediaType CONTENT_TYPE =
3.  MediaType.parse("application/x-www-form-urlencoded");

5.  private  final  List<String> encodedNames;
6.  private  final  List<String> encodedValues;

8.  private  FormBody(List<String> encodedNames,  List<String> encodedValues)  {
9.  this.encodedNames =  Util.immutableList(encodedNames);
10.  this.encodedValues =  Util.immutableList(encodedValues);
11.  }

13.  /** The number of key-value pairs in this form-encoded body. */
14.  public  int size()  {
15.  return encodedNames.size();
16.  }

18.  public  String encodedName(int index)  {
19.  return encodedNames.get(index);
20.  }

22.  public  String name(int index)  {
23.  return percentDecode(encodedName(index),  true);
24.  }

26.  public  String encodedValue(int index)  {
27.  return encodedValues.get(index);
28.  }

30.  public  String value(int index)  {
31.  return percentDecode(encodedValue(index),  true);
32.  }

34.  @Override  public  MediaType contentType()  {
35.  return CONTENT_TYPE;
36.  }

38.  @Override  public  long contentLength()  {
39.  return writeOrCountBytes(null,  true);
40.  }

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

46.  /**
47.  * Either writes this request to {@code sink} or measures its content length. We have one method
48.  * do double-duty to make sure the counting and content are consistent, particularly when it comes
49.  * to awkward operations like measuring the encoded length of header strings, or the
50.  * length-in-digits of an encoded integer.
51.  */
52.  private  long writeOrCountBytes(BufferedSink sink,  boolean countBytes)  {
53.  long byteCount =  0L;

55.  Buffer buffer;
56.  if  (countBytes)  {
57.  buffer =  new  Buffer();
58.  }  else  {
59.  buffer = sink.buffer();
60.  }

62.  , size = encodedNames.size(); i < size; i++)  {
63.  ) buffer.writeByte('&');
64.  buffer.writeUtf8(encodedNames.get(i));
65.  buffer.writeByte('=');
66.  buffer.writeUtf8(encodedValues.get(i));
67.  }

69.  if  (countBytes)  {
70.  byteCount = buffer.size();
71.  buffer.clear();
72.  }

74.  return byteCount;
75.  }

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

81.  public  Builder add(String name,  String value)  {
82.  names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET,  false,  false,  true,  true));
83.  values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET,  false,  false,  true,  true));
84.  return  this;
85.  }

87.  public  Builder addEncoded(String name,  String value)  {
88.  names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET,  true,  false,  true,  true));
89.  values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET,  true,  false,  true,  true));
90.  return  this;
91.  }

93.  public  FormBody build()  {
94.  return  new  FormBody(names, values);
95.  }
96.  }
97.  }

Let's look at the method 'writeOrCountBytes'. Use writeOrCountBytes to calculate the request body size and write the request body to BufferedSink.

As for the BufferSink and buffer classes, these two classes are the ones in Okio. Buffer is equivalent to a cache area and BufferedSink is equivalent to OutputStream. It extends

The function of OutputStream and the complete source code of Okio will be blogged later

③ mode 3: MultipartBody block form creation

`MultipartBody can add forms, files and other binary data. Let's look at several important methods`

1.  public  static  Part createFormData(String name,  String filename,  RequestBody body)  {
2.  if  (name ==  null)  {
3.  throw  new  NullPointerException("name == null");
4.  }
5.  StringBuilder disposition =  new  StringBuilder("form-data; name=");
6.  appendQuotedString(disposition, name);

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

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

Let's take a look at this method. Whether we are addPart or addFormDataPart finally comes to this method, which is encapsulated into a Part object, that is, the entity content

The value of the content disposition of and the binary stream or key value pair of the file

MultipartBody and FormBody are basically the same. The main difference lies in the 'writeOrCountBytes method. The block form is mainly to sum up the size of each block to find the size of the request body. If one of the blocks has no specified size, it will return - 1. So if there are files in the block form, the size cannot be calculated by default, unless you specify contentLength for the RequestBody of the file. `

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

4.  Buffer byteCountBuffer =  null;
5.  if  (countBytes)  {
6.  //If it's a calculation of size, it's new
7.  sink = byteCountBuffer =  new  Buffer();
8.  }
9.  //Loop block
10.  , partCount = parts.size(); p < partCount; p++)  {
11.  Part part = parts.get(p);
12.  //Get the header of each block
13.  Headers headers = part.headers;
14.  //Get the request body of each block
15.  RequestBody body = part.body;

17.  //Write -- xxxxxxxxxx boundary
18.  sink.write(DASHDASH);
19.  sink.write(boundary);
20.  sink.write(CRLF);

22.  //Block header
23.  if  (headers !=  null)  {
24.  , headerCount = headers.size(); h < headerCount; h++)  {
25.  sink.writeUtf8(headers.name(h))
26.  .write(COLONSPACE)
27.  .writeUtf8(headers.value(h))
28.  .write(CRLF);
29.  }
30.  }

32.  //Content type of write block
33.  MediaType contentType = body.contentType();
34.  if  (contentType !=  null)  {
35.  sink.writeUtf8("Content-Type: ")
36.  .writeUtf8(contentType.toString())
37.  .write(CRLF);
38.  }

40.  //Write block size
41.  long contentLength = body.contentLength();
42.  )  {
43.  sink.writeUtf8("Content-Length: ")
44.  .writeDecimalLong(contentLength)
45.  .write(CRLF);
46.  }  else  if  (countBytes)  {
47.  // We can't measure the body's size without the sizes of its components.
48.  //If a block does not have this size, return - 1
49.  byteCountBuffer.clear();
50.  return  -1L;
51.  }

53.  sink.write(CRLF);

55.  //If it is the calculated size, it will be accumulated, otherwise, it will be written to BufferedSink
56.  if  (countBytes)  {
57.  byteCount += contentLength;
58.  }  else  {
59.  body.writeTo(sink);
60.  }

62.  sink.write(CRLF);
63.  }

65.  //Write -- XXXXXXXX -- end boundary
66.  sink.write(DASHDASH);
67.  sink.write(boundary);
68.  sink.write(DASHDASH);
69.  sink.write(CRLF);

71.  if  (countBytes)  {
72.  byteCount += byteCountBuffer.size();
73.  byteCountBuffer.clear();
74.  }

76.  return byteCount;
77.  }

4.CacheControl

( 1) Cache-Control:

Cache control specifies the caching mechanism that requests and responses follow. Setting cache control in a request or response message does not modify the cache processing in another message processing. There are several cache instructions at request:

  1. Public: all content will be cached (both client and proxy can be cached).
  2. Private: content is only cached in private cache (only client can cache, proxy server can't cache)
  3. No cache: request or response messages cannot be cached
  4. No store: do not use or store cache
  5. Max age: the cached content will be invalid after the specified time (seconds). This option is only available in HTTP 1.1. If it is used with last modified, the priority is higher
  6. After xxx seconds, the browser resends the request to the server. Within the specified time (seconds), the client will directly return to the cache without initiating a network request. If it expires, it will automatically initiate a network request
  7. Min fresh: indicates that the client can receive a response whose response time is less than the current time plus the specified time.
  8. Max stale: indicates that the client can receive response messages beyond the timeout period. If you specify a value for the max stale message, the client can receive a response message that exceeds the value specified for the timeout period.

(2) CacheControl class

① common functions

1.  final  CacheControl.Builder builder =  new  CacheControl.Builder();
2.  builder.noCache();//Do not use cache, all go online
3.  builder.noStore();//Do not use or store cache
4.  builder.onlyIfCached();//Cache only
5.  builder.noTransform();//No transcoding
6.  builder.maxAge(,  TimeUnit.MILLISECONDS);//Indicates that the client can receive responses with a lifetime of no more than the specified time.
7.  builder.maxStale(,  TimeUnit.SECONDS);//Indicates that the client can receive response messages that exceed the timeout period
8.  builder.minFresh(,  TimeUnit.SECONDS);//Indicates that the client can receive a response with a response time less than the current time plus the specified time.
9.  CacheControl cache = builder.build();//cacheControl

② two constants of CacheControl:

1.  public  static  final  CacheControl FORCE_NETWORK =  new  Builder().noCache().build();//Do not use cache
2.  public  static  final  CacheControl FORCE_CACHE =  new  Builder()
3.  .onlyIfCached()
4.  .maxStale(Integer.MAX_VALUE,  TimeUnit.SECONDS)
5.  .build();//Cache only

③ how to use when requesting:

1.  final  CacheControl.Builder builder =  new  CacheControl.Builder();
2.  builder.maxAge(,  TimeUnit.MILLISECONDS);
3.  CacheControl cache = builder.build();
4.  final  Request request =  new  Request.Builder().cacheControl(cache).url(requestUrl).build();
5.  final  Call call = mOkHttpClient.newCall(request);//
6.  call.enqueue(new  Callback()  {
7.  @Override
8.  public  void onFailure(Call call,  IOException e)  {
9.  failedCallBack("Access failed", callBack);
10.  Log.e(TAG, e.toString());
11.  }

13.  @Override
14.  public  void onResponse(Call call,  Response response)  throws  IOException  {
15.  if  (response.isSuccessful())  {
16.  String  string  = response.body().string();
17.  Log.e(TAG,  "response ----->"  +  string);
18.  successCallBack((T)  string, callBack);
19.  }  else  {
20.  failedCallBack("Server error", callBack);
21.  }
22.  }
23.  });
24.  return call;
25.  }  catch  (Exception e)  {
26.  Log.e(TAG, e.toString());
27.  }

If the cache does not expire, it will directly return to the cache instead of initiating a network request. If the cache expires, it will automatically initiate a network request. Note: if you use FORCE_CACHE and the network's response requirements, OkHttp will return a 504 prompt, which tells you that the request response cannot be met. Therefore, we add a judgment to use it without a network

1.  //Determine whether the network is connected
2.  boolean connected =  NetworkUtil.isConnected(context);
3.  if  (!connected)  {
4.  request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
5.  }

Original link https://www.bbsmax.com/A/MAzArw9OJ9/

Posted by gazever on Mon, 18 Nov 2019 01:39:59 -0800