博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OkHttp3源码详解(一) Request类
阅读量:6006 次
发布时间:2019-06-20

本文共 13949 字,大约阅读时间需要 46 分钟。

 

每一次网络请求都是一个Request,Request是对url,method,header,body的封装,也是对Http协议中请求行,请求头,实体内容的封装

1 public final class Request {2   private final HttpUrl url;3   private final String method;4   private final Headers headers;5   private final RequestBody body;6   private final Object tag;7 8   private volatile CacheControl cacheControl; // Lazily initialized.

1.HttpUrl

HttpUrl主要用来规范普通的url连接,并且解析url的组成部分

现通过下面的例子来示例httpUrl的使用

https://www.google.com/search?q=maplejaw
①使用parse解析url字符串:

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

②通过构造者模式来常见:

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用于配置请求头,对于请求头配置大家一定不陌生吧,比如Content-Type,User-Agent和Cache-Control等等。

创建Headers也有两种方式。如下:

(1)of()创建:传入的数组必须是偶数对,否则会抛出异常。

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

还可以使用它的重载方法of(Map<String,String> map)方法来创建

(2)构建者模式创建:

Headers mHeaders=new Headers.Builder()            .set("name1","value1")//set表示name1是唯一的,会覆盖掉已经存在的            .add("name2","value2")//add不会覆盖已经存在的头,可以存在多个            .build();

我们来看一下Header的内部,源码就不粘贴了很简单,Headers内部是通过一个数组来保存header private final String[] namesAndValues;大家可能会有这样的疑问,为什么不用Map而用数组呢?因为Map的Key是唯一的,而header要求不唯一

另外,数组方便取数据吗?很方便,我们来看着三个方法

最后通过toString方法转变成String,方便写入请求头,

1 @Override  2 public String toString() { 3     StringBuilder result = new StringBuilder(); 4     for (int i = 0, size = size(); i < size; i++) { 5       result.append(name(i)).append(": ").append(value(i)).append("\n"); 6     } 7     return result.toString(); 8 } 9 10 /** Returns the field at {@code position} or null if that is out of range. */11 public String name(int index) {12     int nameIndex = index * 2;13     if (nameIndex < 0 || nameIndex >= namesAndValues.length) {14       return null;15     }16     return namesAndValues[nameIndex];17 }18  19 /** Returns the value at {@code index} or null if that is out of range. */20 public String value(int index) {21     int valueIndex = index * 2 + 1;22     if (valueIndex < 0 || valueIndex >= namesAndValues.length) {23       return null;24     }25     return namesAndValues[valueIndex];26   }

 

3.RequestBody

 requestBody也就是请求实体内容,我们先来看一下如何来构建一个RequestBody

(1)Request.create()方法创建

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"); 4  5 //构建字符串请求体 6 RequestBody body1 = RequestBody.create(TEXT, string); 7 //构建字节请求体 8 RequestBody body2 = RequestBody.create(STREAM, byte); 9 //构建文件请求体10 RequestBody body3 = RequestBody.create(STREAM, file);11 //post上传json12 RequestBody body4 = RequestBody.create(JSON, json);//json为String类型的13 14 //将请求体设置给请求方法内15 Request request = new Request.Builder()16 .url(url)17 .post(xx)// xx表示body1,body2,body3,body4中的某一个18 .build();19
View Code

(2)构建表单请求体,提交键值对(OkHttp3没有FormEncodingBuilder这个类,替代它的是功能更加强大的FormBody:)

//构建表单RequestBodyRequestBody formBody=new FormBody.Builder()                 .add("name","maplejaw")                 .add("age","18")                  ...                      .build();

 (3)构建分块表单请求体:(OkHttp3取消了MultipartBuilder,取而代之的是MultipartBody.Builder()

                  既可以添加表单,又可以也可以添加文件等二进制数据)

1 public static final MediaType STREAM = MediaType.parse("application/octet-stream"); 2  //构建表单RequestBody 3 RequestBody multipartBody=new MultipartBody.Builder() 4                 .setType(MultipartBody.FORM)//指明为 multipart/form-data 类型 5                 .addFormDataPart("age","20") //添加表单数据 6                 .addFormDataPart("avatar","111.jpg",RequestBody.create(STREAM,file)) //添加文件,其中avatar为表单名,111.jpg为文件名。 7                 .addPart(..)//该方法用于添加RequestBody,Headers和添加自定义Part,一般来说以上已经够用 8                 .build(); 9  10

知道了RequestBody的创建,我们来看一下源码

RequestBody也就是请求实体内容,对于一个Get请求时没有实体内容的,Post提交才有,而且浏览器与服务器通信时基本上只有表单上传才会用到POST提交,所以RequestBody其实也就是封装了浏览器表单上传时对应的实体内容,对于实体内容是什么样还不清楚的可以去看一下我的一篇博客Android的Http协议的通信详解

OkHttp3中RequestBody有三种创建方式

 ①方式一:

public static RequestBody create(MediaType contentType, String content) {    Charset charset = Util.UTF_8;    if (contentType != null) {      charset = contentType.charset();//MediaType的为请求头中的ContentType创建方式:public static final MediaType TEXT =                                       //MediaType.parse("text/plain; charset=utf-8")      if (charset == null) {        charset = Util.UTF_8;//如果contentType中没有指定charset,默认使用UTF-8        contentType = MediaType.parse(contentType + "; charset=utf-8");      }    }    byte[] bytes = content.getBytes(charset);    return create(contentType, bytes);  }
View Code

②方式二:FormBody表单创建,我们来看一下

FormBody用于普通post表单上传键值对,我们先来看一下创建的方法,再看源码

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

FormBody源码

1 public final class FormBody extends RequestBody { 2   private static final MediaType CONTENT_TYPE = 3       MediaType.parse("application/x-www-form-urlencoded"); 4  5   private final List
encodedNames; 6 private final List
encodedValues; 7 8 private FormBody(List
encodedNames, List
encodedValues) { 9 this.encodedNames = Util.immutableList(encodedNames);10 this.encodedValues = Util.immutableList(encodedValues);11 }12 13 /** The number of key-value pairs in this form-encoded body. */14 public int size() {15 return encodedNames.size();16 }17 18 public String encodedName(int index) {19 return encodedNames.get(index);20 }21 22 public String name(int index) {23 return percentDecode(encodedName(index), true);24 }25 26 public String encodedValue(int index) {27 return encodedValues.get(index);28 }29 30 public String value(int index) {31 return percentDecode(encodedValue(index), true);32 }33 34 @Override public MediaType contentType() {35 return CONTENT_TYPE;36 }37 38 @Override public long contentLength() {39 return writeOrCountBytes(null, true);40 }41 42 @Override public void writeTo(BufferedSink sink) throws IOException {43 writeOrCountBytes(sink, false);44 }45 46 /**47 * Either writes this request to {@code sink} or measures its content length. We have one method48 * do double-duty to make sure the counting and content are consistent, particularly when it comes49 * to awkward operations like measuring the encoded length of header strings, or the50 * length-in-digits of an encoded integer.51 */52 private long writeOrCountBytes(BufferedSink sink, boolean countBytes) {53 long byteCount = 0L;54 55 Buffer buffer;56 if (countBytes) {57 buffer = new Buffer();58 } else {59 buffer = sink.buffer();60 }61 62 for (int i = 0, size = encodedNames.size(); i < size; i++) {63 if (i > 0) buffer.writeByte('&');64 buffer.writeUtf8(encodedNames.get(i));65 buffer.writeByte('=');66 buffer.writeUtf8(encodedValues.get(i));67 }68 69 if (countBytes) {70 byteCount = buffer.size();71 buffer.clear();72 }73 74 return byteCount;75 }76 77 public static final class Builder {78 private final List
names = new ArrayList<>();79 private final List
values = new ArrayList<>();80 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 }86 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 }92 93 public FormBody build() {94 return new FormBody(names, values);95 }96 }97 }
View Code

我们主要来看一下方法writeOrCountBytes,通过writeOrCountBytes来计算请求体大小和将请求体写入BufferedSink。

至于BufferSink和Buffer类,这两个类是Okio中的类,Buffer相当于一个缓存区,BufferedSink相当于OutputStream,它扩展了

OutputStream的功能,Okio的完整源码我后续也会写博客

③方式三:MultipartBody分块表单创建

MultipartBody, 既可以添加表单,又可以也可以添加文件等二进制数据,我们就看几个重要的方法

public static Part createFormData(String name, String filename, RequestBody body) {      if (name == null) {        throw new NullPointerException("name == null");      }      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);    }
View Code

我们来看这个方法,我们是addPart还是addFormDataPart最终都走到了这个方法,封装成一个Part对象,也就是实体内容中

的Content-Disposition跟文件二进制流或者键值对的值

MultipartBody和FormBody大体上相同,主要区别在于writeOrCountBytes方法,分块表单主要是将每个块的大小进行累加来求出请求体大小,如果其中有一个块没有指定大小,就会返回-1。所以分块表单中如果包含文件,默认是无法计算出大小的,除非你自己给文件的RequestBody指定contentLength。

private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {    long byteCount = 0L;     Buffer byteCountBuffer = null;    if (countBytes) {      //如果是计算大小的话,就new个      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;       //写 --xxxxxxxxxx 边界           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);        }      }       //写块的Content_Type      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.        //如果有个块没有这名大小,就返回-1.        byteCountBuffer.clear();        return -1L;      }       sink.write(CRLF);       //如果是计算大小就累加,否则写入BufferedSink      if (countBytes) {        byteCount += contentLength;      } else {        body.writeTo(sink);      }       sink.write(CRLF);    }  //写 --xxxxxxxxxx-- 结束边界    sink.write(DASHDASH);    sink.write(boundary);    sink.write(DASHDASH);    sink.write(CRLF);      if (countBytes) {      byteCount += byteCountBuffer.size();      byteCountBuffer.clear();    }     return byteCount;  }
View Code

4.CacheControl

( 1)  Cache-Control:

Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令有下几种:

  1. Public:所有内容都将被缓存(客户端和代理服务器都可缓存)。
  2. Private:内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
  3. no-cache:请求或者响应消息不能缓存
  4. no-store:不使用缓存,也不存储缓存
  5. max-age:缓存的内容将在指定时间(秒)后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
  6. 在 xxx 秒后,浏览器重新发送请求到服务器,指定时间(秒)内,客户端会直接返回cache而不会发起网络请求,若过期会自动发起网络请求
  7. min-fresh:指示客户端可以接收响应时间小于当前时间加上指定时间的响应。
  8. max-stale:指示客户端可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。

(2)CacheControl类

①常用的函数

final CacheControl.Builder builder = new CacheControl.Builder();            builder.noCache();//不使用缓存,全部走网络            builder.noStore();//不使用缓存,也不存储缓存            builder.onlyIfCached();//只使用缓存            builder.noTransform();//禁止转码            builder.maxAge(10, TimeUnit.MILLISECONDS);//指示客户机可以接收生存期不大于指定时间的响应。            builder.maxStale(10, TimeUnit.SECONDS);//指示客户机可以接收超出超时期间的响应消息            builder.minFresh(10, TimeUnit.SECONDS);//指示客户机可以接收响应时间小于当前时间加上指定时间的响应。            CacheControl cache = builder.build();//cacheControl
View Code

②CacheControl的两个常量:

public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();//不使用缓存  public static final CacheControl FORCE_CACHE = new Builder()      .onlyIfCached()      .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)      .build();//只使用缓存
View Code

③请求时如何使用:

final CacheControl.Builder builder = new CacheControl.Builder();            builder.maxAge(10, TimeUnit.MILLISECONDS);            CacheControl cache = builder.build();            final Request request = new Request.Builder().cacheControl(cache).url(requestUrl).build();            final Call call = mOkHttpClient.newCall(request);//            call.enqueue(new Callback() {                @Override                public void onFailure(Call call, IOException e) {                    failedCallBack("访问失败", callBack);                    Log.e(TAG, e.toString());                }                 @Override                public void onResponse(Call call, Response response) throws IOException {                    if (response.isSuccessful()) {                        String string = response.body().string();                        Log.e(TAG, "response ----->" + string);                        successCallBack((T) string, callBack);                    } else {                        failedCallBack("服务器错误", callBack);                    }                }            });            return call;        } catch (Exception e) {            Log.e(TAG, e.toString());        }
View Code

   以上如果Cache没有过期会直接返回cache而不会去发起网络请求,若过期自动发起网络请求,注意:如果您使用FORCE_CACHE和网络的响应需求,OkHttp则会返回一个504提示,告诉你不可满足请求响应,所以我们加一个判断在没有网络的情况下使用

//判断网络是否连接        boolean connected = NetworkUtil.isConnected(context);         if (!connected) {             request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();          }

 

转载于:https://www.cnblogs.com/ganchuanpu/p/6014456.html

你可能感兴趣的文章
tomcat中web.xml各配置项的意义
查看>>
Linux下ftp+ssl实现ftps
查看>>
我的友情链接
查看>>
Java基础 - 第一章 计算
查看>>
CentOS7添加用户账户,授权
查看>>
金蝶kis记账王凭证过账要不要要审核
查看>>
Nodejs学习笔记(二):《node.js开发指南》代码中需要注意的几点
查看>>
Nginx 配置 SSL 及 SSL 证书的创建过程
查看>>
Ztree异步加载自动展开节点
查看>>
应用程序内购买类型
查看>>
大型企业网络构建(二)——高级OSPF&虚链路应用
查看>>
文件系统概念深入介绍及压缩归档工具
查看>>
6个优秀的思维导图网站
查看>>
初识Docker
查看>>
Solaris 10基本命令操作
查看>>
我的友情链接
查看>>
MySQL查询in操作 查询结果按in集合顺序显示
查看>>
arm汇编基础(转)
查看>>
IO流-输入输出流:字节流、字符流、缓冲流、转换流
查看>>
sql字符串插入函数STUFF
查看>>