- 分發(fā)器
- 線程池
- 攔截器
- 五大攔截器
分發(fā)器
Dispatcher類杭跪,這個(gè)類的作用用于分發(fā)提交的網(wǎng)絡(luò)任務(wù)通惫,高并發(fā)任務(wù)分發(fā)和線程池排隊(duì)茂翔;
Dispatcher的工作流程:
首先介紹Dispatcher類的一些核心成員
- ready隊(duì)列和running隊(duì)列:
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
- 最大并發(fā)數(shù)和單位域名最大并發(fā):默認(rèn)是64和5,可以自定義
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
dispatcher.enqueue()
synchronized void enqueue(AsyncCall call) {
// 比較
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
promoteCalls()
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
線程池
OKHttp內(nèi)部的線程池:
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
- 核心線程數(shù):0履腋;無(wú)核心線程數(shù)珊燎,不緩存線程
- 阻塞隊(duì)列:SynchronousQueue;SynchronousQueue的特點(diǎn)是沒(méi)有容量
當(dāng)提交了一些任務(wù)后遵湖,阻塞隊(duì)列不能緩存任務(wù)悔政,會(huì)直接新建線程去執(zhí)行,從而完成了無(wú)等待延旧,高并發(fā)的特點(diǎn)
攔截器
攔截器是OKHttp最核心的部分谋国,當(dāng)一個(gè)網(wǎng)絡(luò)任務(wù)由分發(fā)器提交給線程池后,會(huì)交給攔截器處理迁沫;
與攔截器相關(guān)的核心API:
Chain接口和Intercept接口
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
intercept接口是攔截器實(shí)現(xiàn)的接口芦瘾,自帶五個(gè)默認(rèn)的攔截器都實(shí)現(xiàn)了這個(gè)接口,chain接口是責(zé)任鏈模式的接口集畅,攔截器的調(diào)用是使用責(zé)任鏈模式(單一責(zé)任原則)調(diào)用的
chain接口的實(shí)現(xiàn)類:RealInterceptorChain
private final List<Interceptor> interceptors;
private final int index;
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
....
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
....
return response;
}
RealInterceptorChain(一下簡(jiǎn)稱realChain)是攔截器責(zé)任鏈模式的主要實(shí)現(xiàn)類近弟,他有兩個(gè)重要的成員變量 interceptors & index
集合用來(lái)存放所有需要執(zhí)行的攔截器,index作為下標(biāo)從0開(kāi)始一個(gè)個(gè)的遍歷集合挺智;
getResponseWithInterceptorChain():
// 獲取網(wǎng)絡(luò)請(qǐng)求的Response
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors. 添加攔截器到list中
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));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
// 通過(guò)list創(chuàng)建RealInterceptChain
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
// 調(diào)用RealChain的proceed方法
return chain.proceed(originalRequest);
}
我們從線程池的execute()開(kāi)始分析攔截器的工作流程祷愉,首先調(diào)用getResponseWithInterceptorChain()添加自定義的攔截器和五個(gè)OKHTTP自帶的攔截器,然后創(chuàng)建一個(gè)index為0的realChain逃贝,調(diào)用realChain(index=0)的proceed();
在proceed方法中獲取當(dāng)前index下標(biāo)所指的intercept,創(chuàng)建一個(gè)新的realChain(index+1=2)迫摔,執(zhí)行攔截器的intercept方法沐扳,并傳入新的realChain;
RetryAndFollowUpInterceptor.intercept(realChain(index = 1))
@Override public Response intercept(Chain chain) throws IOException {
...
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
}
...
}
在intercept中調(diào)用了責(zé)任鏈的proceed()句占,此時(shí)的index+1了沪摄,如此往返將責(zé)任鏈遍歷下去,執(zhí)行每一個(gè)攔截器纱烘;
五大攔截器
RetryAndFollowUpInterceptor:重試和重定向
- retry重試
- followUp重定向
當(dāng)請(qǐng)求發(fā)生異常時(shí)杨拐,RetryAndFollowUpInterceptor會(huì)根據(jù)異常的條件判斷是否需要重試;
RetryAndFollowUpInterceptor.intercept
@Override public Response intercept(Chain chain) throws IOException {
...
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) { // 如果是路由異常/io異常就通過(guò)recover()判斷是否需要重試
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) { // 如果是路由異常/io異常就通過(guò)recover()判斷是否需要重試
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
}
...
}
在while循環(huán)中catch RouteException & IOException 用recover()判斷這個(gè)異常是否需要重試擂啥,如果需要就continue哄陶,不需要?jiǎng)t拋出異常結(jié)束循環(huán);
RetryAndFollowUpInterceptor.recover()
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries. client配置是否允許重試()默認(rèn)允許
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again. // http2.0才可能不通過(guò)
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// This exception is fatal. 判斷是否是重大異常
if (!isRecoverable(e, requestSendStarted)) return false;
// No more routes to attempt. 當(dāng)前域名只有一個(gè)ip
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
重大異常的判斷
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// If there was a protocol problem, don't recover.
if (e instanceof ProtocolException) { // 協(xié)議異常 哺壶,不重試
return false;
}
// If there was an interruption don't recover, but if there was a timeout connecting to a route
// we should try the next route (if there is one).
if (e instanceof InterruptedIOException) {
return e instanceof SocketTimeoutException && !requestSendStarted; // 是Socket超時(shí)異常直接返回true重試屋吨,不是繼續(xù)往下判斷
}
// Look for known client-side or negotiation errors that are unlikely to be fixed by trying
// again with a different route.
if (e instanceof SSLHandshakeException) {
// If the problem was a CertificateException from the X509TrustManager,
// do not retry.
if (e.getCause() instanceof CertificateException) { // 證書(shū)異常蜒谤,不重試
return false;
}
}
if (e instanceof SSLPeerUnverifiedException) {
// e.g. a certificate pinning error.
return false;
}
// An example of one we might want to retry with a different route is a problem connecting to a
// proxy and would manifest as a standard IOException. Unless it is one we know we should not
// retry, we return true and try a new route.
return true;
}
總的來(lái)說(shuō),如果是服務(wù)端的問(wèn)題(證書(shū)異常至扰,協(xié)議異常)就不會(huì)重試鳍徽,因?yàn)樵僦卦囈彩且粯咏Y(jié)果,而如果是網(wǎng)絡(luò)波動(dòng)超時(shí)異常敢课,就會(huì)觸發(fā)攔截器重試(需要滿足其他的要求)阶祭;
當(dāng)重試環(huán)節(jié)結(jié)束后(即while循環(huán)跳出后),會(huì)進(jìn)入重定向階段followUp直秆,非重點(diǎn)濒募,了解就好
重定向:
客戶端向服務(wù)器發(fā)送一個(gè)請(qǐng)求,服務(wù)端返回一個(gè)url給客戶端切厘,客戶端訪問(wèn)這個(gè)新的url萨咳,叫做重定向,這個(gè)url放在響應(yīng)頭的Location中疫稿;
while (true) {
...
try {
// 循環(huán)執(zhí)行新的重定向
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
}
// 通過(guò)響應(yīng)碼判定是否有重定向
Request followUp = followUpRequest(response, streamAllocation.route());
// 如果沒(méi)有直接返回response
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 重定向次數(shù)限制
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 重定向
request = followUp;
priorResponse = response;
...
}
BridgeInterceptor:橋接
最簡(jiǎn)單的攔截器培他,主要的功能:
- 補(bǔ)充請(qǐng)求頭
- 設(shè)置和讀取Cookie
- 設(shè)置gzip壓縮和解壓(Accept-Encoding/Content-Encoding)
CacheInterceptor:緩存
緩存攔截器內(nèi)部有一個(gè)緩存策略類,他有兩個(gè)重要的成員變量:
/** The request to send on the network, or null if this call doesn't use the network. */
public final @Nullable Request networkRequest;
/** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;
networkRequest用來(lái)表示這個(gè)請(qǐng)求的網(wǎng)絡(luò)訪問(wèn)請(qǐng)求細(xì)節(jié)(Request)遗座,cacheResponse用來(lái)表示這個(gè)請(qǐng)求的緩存(如果沒(méi)有為null)舀凛;
1 cache == null && request == null
緩存為空,請(qǐng)求為空途蒋,fail猛遍,直接返回一個(gè)504空的response
2 cache == null && request != null
緩存為空,請(qǐng)求不空号坡,訪問(wèn)服務(wù)器
3 cache != null && request == null
緩存不為空懊烤,請(qǐng)求為空,使用緩存
4 cache != null && request != null
緩存不為空宽堆,請(qǐng)求不為空腌紧,訪問(wèn)服務(wù)器,對(duì)比緩存(響應(yīng)碼304)
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 通過(guò) CacheStrategy 獲取 cache 和 request
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 第一種情況畜隶,緩存為空壁肋,請(qǐng)求為空
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// request為空,直接使用緩存籽慢, (緩存為空則return null)
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// request不為空浸遗, 使用責(zé)任鏈發(fā)起請(qǐng)求,獲取networkResponse
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 第四種情況箱亿,request 不為空跛锌, 緩存也不為空, 對(duì)比緩存
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
緩存策略的實(shí)現(xiàn):
我們先看一下緩存攔截器的緩存策略是如何使用;
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
使用了內(nèi)部類的get方法獲取CacheStrategy 對(duì)象届惋;
在緩存策略的內(nèi)部有一個(gè)Factory內(nèi)部類:在構(gòu)造Factory時(shí)Factory的成員變量用來(lái)存儲(chǔ)response的一些基本信息備用Date Expires Last-Modified ETag Age
public static class Factory {
final long nowMillis;
final Request request;
final Response cacheResponse;
/** The server's time when the cached response was served, if known. */
private Date servedDate;
private String servedDateString;
/** The last modified date of the cached response, if known. */
private Date lastModified;
private String lastModifiedString;
/**
* The expiration date of the cached response, if known. If both this field and the max age are
* set, the max age is preferred.
*/
private Date expires;
/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP request was
* first initiated.
*/
private long sentRequestMillis;
/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP response was
* first received.
*/
private long receivedResponseMillis;
/** Etag of the cached response. */
private String etag;
/** Age of the cached response. */
private int ageSeconds = -1;
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
}
get():
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
getCandidate():
private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
getCandidate()是獲取CacheStrategy 類的核心代碼:
ConnectInterceptor 連接
連接攔截器的作用是建立Socket連接和Socket連接池的復(fù)用;
相關(guān)類 | 作用 |
---|---|
StreamAllocation | 協(xié)調(diào)請(qǐng)求察净、連接與數(shù)據(jù)流三者之間的關(guān)系驾茴,它負(fù)責(zé)為一次請(qǐng)求尋找連接,然后獲得流來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)通信氢卡,簡(jiǎn)單來(lái)說(shuō)就是維護(hù)連接锈至,在重定向攔截器創(chuàng)建,連接攔截器使用 |
RealConnection | 封裝了Socket與一個(gè)Socket連接池译秦,建立與服務(wù)器的Socket連接 |
ConnectionPool | Socket連接池峡捡,復(fù)用Socket連接,關(guān)閉一定時(shí)間內(nèi)空閑的連接 |
Socket連接的建立:
- 無(wú)代理:直接向Socket指定目標(biāo)服務(wù)器
- Socket代理:向Socket傳入Proxy對(duì)象筑悴,Proxy指定代理服務(wù)器们拙,Socket連接目標(biāo)服務(wù)器
- HTTP代理:Socket連接代理服務(wù)器,請(qǐng)求報(bào)文請(qǐng)求行的第二個(gè)參數(shù)加上
目標(biāo)服務(wù)器域名前置
- HTTPS代理:Socket連接代理服務(wù)器阁吝,請(qǐng)求報(bào)文需要先請(qǐng)求
CONNECT
砚婆,服務(wù)器返回connect成功,再包裝一層SSLSocket與服務(wù)器通信
連接攔截器類并沒(méi)有多少代碼突勇,大部分的邏輯都被封裝在了RealConnection和StreamAllocation中装盯,從connect()開(kāi)始,如果是HTTPS請(qǐng)求甲馋,會(huì)創(chuàng)建隧道代理埂奈,隧道代理的功能代理服務(wù)器可以發(fā)出身份質(zhì)疑,關(guān)閉連接定躏,保證HTTPS的安全性
CallServerInterceptor 請(qǐng)求服務(wù)
將request拼成請(qǐng)求報(bào)文账磺,將服務(wù)器的響應(yīng)報(bào)文轉(zhuǎn)化成response
一般出現(xiàn)于上傳大容量請(qǐng)求體或者需要驗(yàn)證。代表了先詢問(wèn)服務(wù)器是否原因接收發(fā)送請(qǐng)求體數(shù)據(jù)痊远,
OkHttp的做法:
如果服務(wù)器允許則返回100垮抗,客戶端繼續(xù)發(fā)送請(qǐng)求體;
如果服務(wù)器不允許則直接返回給用戶碧聪。
同時(shí)服務(wù)器也可能會(huì)忽略此請(qǐng)求頭冒版,一直無(wú)法讀取應(yīng)答,此時(shí)拋出超時(shí)異常矾削。