okhttp-源码简析

 

1 Request req = new Request.Builder()
2     .cacheControl(new CacheControl.Builder().noCache().build())
3     .url("http://publicobject.com/helloworld.txt")
4     .build();//GET
5 //同步执行
6 client.newCall(req).execute();
7 //异步执行
8 client.newCall(req).enqueue(callback);

不管同步请求还是异步请求,都需要通过newCall创建一个Call实例

1 public Call newCall(Request request) {
2     //client、originalRequest、forWebSocket
3   return new RealCall(this, request, false /* for web socket */);
4 }

 

同步请求

 1 //RealCall的execute方法
 2 public Response execute() throws IOException {
 3   synchronized (this) {
 4       //RealCall只能执行一次
 5     if (executed) throw new IllegalStateException("Already Executed");
 6     executed = true;
 7   }
 8   captureCallStackTrace();
 9   try {
10       //调用runningSyncCalls.add(call),runningSyncCalls是一个queue,
11       //finally中的finished会调用runningSyncCalls.remove(call),
12       //runningSyncCalls的意义在于client可以统一管理同步call(异步call同样有一个queue),
13       //比如通过runningCalls()得到正在执行的http请求(包括同步、异步),还可以通过cancelAll()取消所有http请求(包括同步、异步)
14     client.dispatcher().executed(this);
15     //getResponseWithInterceptorChain是真正的执行逻辑,从方法名可以看出okhttp将执行逻辑分隔成了一个个拦截器,使用责任链来执行。
16     Response result = getResponseWithInterceptorChain();
17     if (result == null) throw new IOException("Canceled");
18     return result;
19   } finally {
20       //runningSyncCalls.remove(call)
21     client.dispatcher().finished(this);
22   }
23 }

 

 1 //getResponseWithInterceptorChain
 2 Response getResponseWithInterceptorChain() throws IOException {
 3   // Build a full stack of interceptors.
 4   List<Interceptor> interceptors = new ArrayList<>();
 5   //自定义的interceptor
 6   interceptors.addAll(client.interceptors());
 7   interceptors.add(retryAndFollowUpInterceptor);
 8   interceptors.add(new BridgeInterceptor(client.cookieJar()));
 9   interceptors.add(new CacheInterceptor(client.internalCache()));
10   interceptors.add(new ConnectInterceptor(client));
11   if (!forWebSocket) {
12     interceptors.addAll(client.networkInterceptors());
13   }
14   //拦截链的最后一个拦截器,通常来说最后一个拦截器就是真正的执行逻辑,比如tomcat的filters中最后一个filter其实就是Servlet
15   interceptors.add(new CallServerInterceptor(forWebSocket));
16 
17   Interceptor.Chain chain = new RealInterceptorChain(
18       interceptors, null, null, null, 0, originalRequest);
19   return chain.proceed(originalRequest);//执行拦截链
20 }
CallServerInterceptor的intercept方法
 1 //CallServerInterceptor的intercept,有删减
 2 public Response intercept(Chain chain) throws IOException {
 3   RealInterceptorChain realChain = (RealInterceptorChain) chain;
 4   HttpCodec httpCodec = realChain.httpStream();
 5   StreamAllocation streamAllocation = realChain.streamAllocation();
 6   RealConnection connection = (RealConnection) realChain.connection();
 7   Request request = realChain.request();
 8     //请求时间
 9   long sentRequestMillis = System.currentTimeMillis();
10   //内部会调用writeRequest(request.headers(), requestLine);,writeRquest会先写请求行,再遍历请求头并写出。
11   //这里的“写”操作,只是将字符串写入一个Buffer(后面会提到Buffer),socket还没有write。
12   httpCodec.writeRequestHeaders(request);
13      /** 什么是sink(okio.Sink接口)?sink翻译为水槽,可以理解为水的最终汇聚地,相对的有okio.Source,即水的发源地。
14         * Receives a stream of bytes. Use this interface to write data wherever it's
15         * needed: to the network, storage, or a buffer in memory. Sinks may be layered
16         * to transform received data, such as to compress, encrypt, throttle, or add
17         * protocol framing.
18         */
19   Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
20   //将sink包装一层缓冲区
21   BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
22   //将body字节序列全部写入bufferedRequestBody,注意是全部,这里就有可能出现OOM。
23   //此时body只是写入了buffer,还未写入socket。
24   request.body().writeTo(bufferedRequestBody);
25   //close时会将buffer中的数据写入sink(即requestBodyOut),这里仍旧没有写入socket,
26   //这里我不太理解,httpCodec.createRequestBody返回的sink其实已经是一个buffer了,为什么还要套一层?
27   bufferedRequestBody.close();
28   
29   //这里才真正写出socket,socketOutputStream.write
30     /** Flush the request to the underlying socket and signal no more bytes will be transmitted. */
31   httpCodec.finishRequest();
32   
33     //开始构造response
34     //读响应行、响应头
35   Response.Builder responseBuilder = httpCodec.readResponseHeaders(false);
36 
37   Response response = responseBuilder
38       .request(request)
39       .handshake(streamAllocation.connection().handshake())
40       .sentRequestAtMillis(sentRequestMillis)
41       .receivedResponseAtMillis(System.currentTimeMillis())
42       .build();
43 
44  /**
45       * httpCodec.openResponseBody会返回一个输入流,类似socketInputStream,还未从流中读取数据。
46       * 需要手动关闭
47      * Response.close()
48      * Response.body().close()
49      * Response.body().source().close()
50      * Response.body().charStream().close()
51      * Response.body().byteString().close()
52      * Response.body().bytes()
53      * Response.body().string()
54      */
55   response = response.newBuilder()
56       .body(httpCodec.openResponseBody(response))
57       .build();
58   return response;
59 }

 

异步请求

 1 //RealCall的enqueue方法
 2 public void enqueue(Callback responseCallback) {
 3   synchronized (this) {
 4     if (executed) throw new IllegalStateException("Already Executed");
 5     executed = true;
 6   }
 7   captureCallStackTrace();
 8   //入队
 9   client.dispatcher().enqueue(new AsyncCall(responseCallback));
10 }

dispatcher()会返回与该okHttpClient关联的Dispatcher实例,dispatcher的enqueue方法

 1 //Dispatcher的enqueue方法
 2 synchronized void enqueue(AsyncCall call) {
 3   if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
 4       //放入异步队列,与之前提到的同步队列意义相同
 5     runningAsyncCalls.add(call);
 6     //异步执行,通常的做法是放入executorService维护的任务队列中,然后由线程池竞争执行队列中的任务。
 7     //executorService的默认构造逻辑:executorService = new java.util.concurrent.ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 
 8     //new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
 9     //参数对应:corePoolSize,maximumPoolSize,keepAliveTime,timeUnit,workQueue,threadFactory。
10     //也可以自定义executorService:
11     //OkHttpClient okHttpClient = new OkHttpClient.Builder().dispatcher(new Dispatcher(Executors.newSingleThreadExecutor())).build();
12 
13     executorService().execute(call);
14   } else {
15     readyAsyncCalls.add(call);
16   }
17 }

 

AsynCall在executorService.execute中的执行逻辑是AsynCall的execute逻辑

 1 //AsyncCall的执行逻辑
 2 protected void execute() {
 3   boolean signalledCallback = false;
 4   try {
 5       //getResponseWithInterceptorChain与同步调用一样
 6     Response response = getResponseWithInterceptorChain();
 7     if (retryAndFollowUpInterceptor.isCanceled()) {
 8       signalledCallback = true;
 9       //onFailure回调
10       responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
11     } else {
12       signalledCallback = true;
13       //onResponse回调
14       responseCallback.onResponse(RealCall.this, response);
15     }
16   } catch (IOException e) {
17     if (signalledCallback) {
18       // Do not signal the callback twice!
19       Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
20     } else {
21       responseCallback.onFailure(RealCall.this, e);
22     }
23   } finally {
24       //从异步队列中删除该call
25     client.dispatcher().finished(this);
26   }
27 }

 

Buffer

okhttp并没有使用jdk的ByteBuffer,而是自己对byte[]进行了池化,这也使得okhttp没有机会使用DirectByteBuffer了,同时由于池化的byte[]被限定为64kb,okhttp也不适合大数据量应用。

 1 //Buffer即是sink,又是source
 2 public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
 3     //双向链表头结点
 4     Segment head;
 5     //......
 6     
 7     public void write(Buffer source, long byteCount) {
 8         // Move bytes from the head of the source buffer to the tail of this buffer
 9         // while balancing two conflicting goals: don't waste CPU and don't waste
10         // memory.
11         //
12         // Don't waste CPU (ie. don't copy data around).
13         //
14         // Copying large amounts of data is expensive. Instead, we prefer to
15         // reassign entire segments from one buffer to the other.
16         //内存间的复制操作是需要cpu参与的,为了避免复制,就将source中的Segment链表接到本Buffer的Segment链表尾部.
17         //
18         // Don't waste memory.
19         //
20         // As an invariant, adjacent pairs of segments in a buffer should be at
21         // least 50% full, except for the head segment and the tail segment.
22         //当Segment的利用率低于50%时,依旧需要使用System.arrayCopy将next的数据复制到pre中,也就是进行compact.
23   }
24   
25   public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
26       //如果调用String.getBytes必然会在内部new byte[],这里的writeUtf8是通过String的getBytes编码string的吗?
27       //writeUtf8并没有使用getBytes,而是自己实现了utf8的编码规则,并将字节编码序列存放在Buffer持有的data字节数组中。
28   }
29 }

 

 1 final class Segment {
 2   /** The size of all segments in bytes. */
 3   static final int SIZE = 8192;
 4 
 5   /** Segments will be shared when doing so avoids {@code arraycopy()} of this many bytes. */
 6   static final int SHARE_MINIMUM = 1024;
 7     //字节数组,真正存放数据的地方
 8   final byte[] data;
 9 
10   /** The next byte of application data byte to read in this segment. */
11   int pos;
12 
13   /** The first byte of available data ready to be written to. */
14   int limit;
15 
16   /** True if other segments or byte strings use the same byte array. */
17   boolean shared;
18 
19   /** True if this segment owns the byte array and can append to it, extending {@code limit}. */
20   boolean owner;
21 
22   /** Next segment in a linked or circularly-linked list. */
23   //segment可以组成一个单链表(SegmentPool中)或循环链表(Buffer中)
24   Segment next;
25 
26   /** Previous segment in a circularly-linked list. */
27   //双向链表的前指针
28   Segment prev;
29 
30   Segment() {
31     this.data = new byte[SIZE];
32     this.owner = true;
33     this.shared = false;
34   }
35 
36   Segment(Segment shareFrom) {
37     this(shareFrom.data, shareFrom.pos, shareFrom.limit);
38     shareFrom.shared = true;
39   }
40 
41   Segment(byte[] data, int pos, int limit) {
42     this.data = data;
43     this.pos = pos;
44     this.limit = limit;
45     this.owner = false;
46     this.shared = true;
47   }
48   //......
49 }

 

 1 /**
 2  * A collection of unused segments, necessary to avoid GC churn and zero-fill.
 3  * This pool is a thread-safe static singleton.
 4  */
 5 //SegmentPool是未使用的Segment的集合,即池化的byte[]
 6 final class SegmentPool {
 7   /** The maximum number of bytes to pool. */
 8   // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
 9   //client最大持有64kb的缓冲池,超出的部分可以申请,但回收时不会被放回池中,看来okio.SegmentPool只适合小数据量应用
10   static final long MAX_SIZE = 64 * 1024; // 64 KiB.
11 
12   /** Singly-linked list of segments. */
13   //单链表
14   static @Nullable Segment next;
15   
16   /** Total bytes in this pool. */
17   static long byteCount;
18 
19   private SegmentPool() {
20   }
21     //从池中获取Segment
22   static Segment take() {
23     synchronized (SegmentPool.class) {
24       if (next != null) {
25         Segment result = next;
26         next = result.next;
27         result.next = null;
28         byteCount -= Segment.SIZE;
29         return result;
30       }
31     }
32     return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
33   }
34     //回收Segment置池中
35   static void recycle(Segment segment) {
36     if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
37     if (segment.shared) return; // This segment cannot be recycled.
38     synchronized (SegmentPool.class) {
39       if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
40       byteCount += Segment.SIZE;
41       segment.next = next;
42       segment.pos = segment.limit = 0;
43       next = segment;
44     }
45   }
46 }

 

Cache

http缓存,首先server需要发送带缓存首部的响应(比如Cache-Control:max-age=3600),然后client需要开启缓存功能(浏览器自动会开启,chrome在debug时可以disable cache,okhttp需要在创建okHttpClient时设置cache)。

1 int cacheSize = 10 * 1024 * 1024; // 10 MiB
2 //最终会得到一个okhttp3.internal.cache.DiskLruCache
3 Cache cache = new Cache(cacheDirectory, cacheSize);
4 OkHttpClient client = new OkHttpClient.Builder()
5     .cache(cache)
6     .build();

client缓存响应时会记录一个缓存时间(不是Date,是本地时间),响应头的max-age表明在max-age时间内,该response都是新鲜的,即不需要去server验证;响应头的no-cache表示client可以对response缓存,但client请求时必须先向server验证该缓存的新鲜度。

请求头的max-age表示client只接收max-age之内的缓存,max-stale表示client能接受最大的缓存过期时间,no-cache也是要先向server验证新鲜度,if-only-cached表示只从缓存获取数据。

 1 Request request = new Request.Builder()
 2     //Cache-Control:no-cache
 3     //Forces caches to submit the request to the origin server for validation before releasing a cached copy.
 4     //准确的讲应该叫donot-serve-from-cache-without-revalidation
 5     .cacheControl(new CacheControl.Builder().noCache().build())
 6     .url("http://publicobject.com/helloworld.txt")
 7     .build();
 8 
 9 Request request = new Request.Builder()
10     //Cache-Control:max-age=<seconds>
11     //Specifies the maximum amount of time a resource will be considered fresh. 
12     //Contrary to Expires, this directive is relative to the time of the request.
13     .cacheControl(new CacheControl.Builder()
14         .maxAge(0, TimeUnit.SECONDS)
15         .build())
16     .url("http://publicobject.com/helloworld.txt")
17     .build();
18 
19 Request request = new Request.Builder()
20     //Cache-Control:only-if-cached
21     //Indicates to not retrieve new data. The client only wishes to obtain a cached response, 
22     //and should not contact the origin-server to see if a newer copy exists.
23     .cacheControl(new CacheControl.Builder()
24         .onlyIfCached()
25         .build())
26     .url("http://publicobject.com/helloworld.txt")
27     .build();
28 Response forceCacheResponse = client.newCall(request).execute();
29 if (forceCacheResponse.code() != 504) {
30     // The resource was cached! Show it.
31 } else {
32     // The resource was not cached.
33 }
34 
35 Request request = new Request.Builder()
36     //Cache-Control:max-stale[=<seconds>]
37     //Indicates that the client is willing to accept a response that has exceeded its expiration time. 
38     //Optionally, you can assign a value in seconds, indicating the time the response must not be expired by.
39     //okhttp中max-stale必须赋值?赋0表示max-stale无效?
40     .cacheControl(new CacheControl.Builder()
41         .maxStale(365, TimeUnit.DAYS)
42         .build())
43     .url("http://publicobject.com/helloworld.txt")
44     .build();

 

如下图,是否过期由响应头max-age决定,如果响应头没有ETag、Last-Modified则直接发送原始请求。

 

参考:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control

  https://segmentfault.com/a/1190000006741200

posted @ 2017-08-23 16:24  holoyong  阅读(445)  评论(0编辑  收藏  举报