5.okhttp

简单使用

OkHttpClient client = new OkHttpClient.Builder()
                .build();

Request request = new Request.Builder()
                .url(ENDPOINT)
                .build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        System.out.println("onFailure");
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println("onResponse");
        ResponseBody body = response.body();
        System.out.println(body.source().readString(Charset.forName("utf-8")));
    }
});

源码分析

先是创建HttpClient,通过建造者模式来创建,我们来看build方法

OkHttpClient.Builder.build()
public OkHttpClient build() {
            return new OkHttpClient(this);
}

它调用了OkHttpClient的含有build参数的构造方法

OkHttpClient(Builder builder) {
    //调度者,用于调度线程
    this.dispatcher = builder.dispatcher;
    //代理服务器
    this.proxy = builder.proxy;
    //支持协议
    this.protocols = builder.protocols;
    //连接规格
    this.connectionSpecs = builder.connectionSpecs;
    //请求拦截器,在框架网路发生前调用和框架处理完响应后的时间段处理
    this.interceptors = Util.immutableList(builder.interceptors);
    //网络拦截器,在框架进行网络握手后和发送实际请求信息前这段时间处理
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    //事件监听者工厂
    this.eventListenerFactory = builder.eventListenerFactory;
    //代理选择器
    this.proxySelector = builder.proxySelector;
    //Cookie存储器
    this.cookieJar = builder.cookieJar;
    //缓存
    this.cache = builder.cache;
    //内部缓存
    this.internalCache = builder.internalCache;
    //客户端套接字工厂
    this.socketFactory = builder.socketFactory;
    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
        isTLS = isTLS || spec.isTls();
    }
    if (builder.sslSocketFactory != null || !isTLS) {
        //SSL套接字工厂
        this.sslSocketFactory = builder.sslSocketFactory;
        //证书链整理者
        this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
        X509TrustManager trustManager = Util.platformTrustManager();
        this.sslSocketFactory = newSslSocketFactory(trustManager);
        this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }
    if (sslSocketFactory != null) {
        Platform.get().configureSslSocketFactory(sslSocketFactory);
    }
    // 主机名验证器,给https使用,验证对方的host是不是要访问的host
    this.hostnameVerifier = builder.hostnameVerifier;
    //证书固定器,用于做自签名
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
            certificateChainCleaner);
    //代理授权认证器
    this.proxyAuthenticator = builder.proxyAuthenticator;
    //授权认证器
    this.authenticator = builder.authenticator;
    //连接池
    this.connectionPool = builder.connectionPool;
    //dns
    this.dns = builder.dns;
    //HTTP与https切换的重定向是否允许
    this.followSslRedirects = builder.followSslRedirects;
    //是否允许重定向
    this.followRedirects = builder.followRedirects;
    //是否请求连接失败重试
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    //连接超时
    this.connectTimeout = builder.connectTimeout;
    //下载缓存超时
    this.readTimeout = builder.readTimeout;
    //写入缓存超时
    this.writeTimeout = builder.writeTimeout;
    //针对WebSocket,定义发送心跳的间隔
    this.pingInterval = builder.pingInterval;
    if (interceptors.contains(null)) {
        throw new IllegalStateException("Null interceptor: " + interceptors);
    }
    if (networkInterceptors.contains(null)) {
        throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
    }
}

上面对OkHttp全局属性做了配置。

接下来是生成一个请求对象

Request.Builder.build()
public Request build() {
    if (url == null) throw new IllegalStateException("url == null");
    return new Request(this);
}

它也是调用了request用build做参数的构造法

Request(Builder builder) {
    //请求url
    this.url = builder.url;
    //请求方式
    this.method = builder.method;
    //请求头
    this.headers = builder.headers.build();
    //请求体
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
}

这里对http的这次请求信息进行设置

然后用client和这个request对象生成call,call对象是这次通信的抽象,可以进行同/异步请求,取消等

client.newCall(request)
@Override
public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

调用RealCall的静态方法newRealCall方法

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    //安全地将Call实例发布到EventListener。
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

接下来才是真正调用RealCall的构造方法,传入client,request,和forwebSocket参数

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}

client就是之前我们生成的Client对象,也是从这个对象调用方法链接过来的

request就是上面定义的这次请求的相关信息

forwebSocket是标记是否是长连接

这样我们就获得了call对象,拿到这个对象我们就可以进行同/异步请求了,我们先看异步请求

call.enqueue(callback)
@Override
public void enqueue(Callback responseCallback) {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //这里使用了调度者进行调用
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

最后一行使用了client的dispatcher的enqueue方法将新建的AsynCall对象调度

AsyncCall继承了NamedRunnable类,NamedRunnable类实现了Runnable方法,并且定义了当run方法执行时,执行其execute方法,在NamedRunnable里execute方法,是一个抽象方法,AsyncCall实现了其方法,并进行网络请求。怎么请求后面再说,我们先看dispatcher的enqueue方法

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        //如果正在运行的的调用数小于最大请求书且当前主机的调用数小于每个主机的最大请求数
        //加入到正在运行的调用者队列
        runningAsyncCalls.add(call);
        //executorService执行调用
        executorService().execute(call);
    } else {
        //超过了最大数
        //添加到准备运行的队列中
        readyAsyncCalls.add(call);
    }
}

dispatcher的enqueue方法里如果没有超过运行数量限制的话,将用线程池执行asynCall对象,执行时会运行AsyncCall的run方法,然后 是execute方法,跟上面对应上了。

而如果超过数量限制的话则加入到准备运行队列中。等待执行

准备队列执行的任务会在promoteCalls方法执行后进行检查,将readyAsyncCalls里的call提升到runningAsyncCalls队列执行

/**
 * 提升调用
 */
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.
    }
}

而promoteCalls的调用时机有dispatcher.setMaxRequests方法dispatcher.setMaxRequestsPerHost以及dispatcher.finished方法

而dispatcher.finished会在AsyncCall的execute方法执行的finally代码块里,也就是说每执行完一个AsyncCall,就会通知dispatcher自己结束执行了,然后就会去执行promoteCalls方法将一定数量的readyAsyncCalls队列的call提升执行

接下来看AsyncCall的execute方法

AsyncCall.execute()
protected void execute() {
    boolean signalledCallback = false;
    try {
        //用拦截器链获取到response
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
            signalledCallback = true;
            responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
            signalledCallback = true;
            responseCallback.onResponse(RealCall.this, response);
        }
    } catch (IOException e) {
        if (signalledCallback) {
            // Do not signal the callback twice!不要两次回调通知!
            Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
            eventListener.callFailed(RealCall.this, e);
            responseCallback.onFailure(RealCall.this, e);
        }
    } finally {
        client.dispatcher().finished(this);
    }
}

通过getResponseWithInterceptorChain方法获取到了这次请求的response对象

然后将处理结果通过callBack回调到应用层的代码 我们的一次请求即完成

getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //应用层拦截器
    interceptors.addAll(client.interceptors());
    //出错重试,重定向重新请求的拦截器
    interceptors.add(retryAndFollowUpInterceptor);
    //桥接拦截器,根据用户请求构建网络请求,它根据网络响应构建用户响应。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //缓存拦截器,在缓存有效时,直接从缓存取数据,不再进行网络请求
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //连接拦截器,做了Tcp连接和Tls连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        //网络请求拦截器针对完整原始数据进行处理
        interceptors.addAll(client.networkInterceptors());
    }
    //网络呼叫拦截器
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //生成真正的拦截器链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    //执行链
    return chain.proceed(originalRequest);
}

该方法将所有的拦截器、请求、超时时间的组合生成一个RealInterceptorChain链,然后所有的操作就奖给了这个拦截器链进行处理

然后是chain.proceed(originalRequest)方法

chain.proceed(originalRequest)
@Override
public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
}

然后是proceed(request, streamAllocation, httpCodec, connection)方法

/**
 * 负责生成下一个RealInterceptorChain,并取出当前的拦截器进行拦截
 *
 * @param request          请求
 * @param streamAllocation 流分配
 * @param httpCodec        http编解码器
 * @param connection       连接
 * @return 被后面拦截器处理好的response
 */
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    // If we already have a stream, confirm that the incoming request will use it.
    //如果我们已经有了一个流,请确认传入的请求将使用它
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
        throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                + " must retain the same host and port");
    }
    // If we already have a stream, confirm that this is the only call to chain.proceed().
    //如果我们已经有了一个流,请确认这是对chain.proceed()的唯一调用
    if (this.httpCodec != null && calls > 1) {
        throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                + " must call proceed() exactly once");
    }
    // Call the next interceptor in the chain.
    //构建下一个RealInterceptorChain,指明这个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);
    // Confirm that the next interceptor made its required call to chain.proceed().
    //确认下一个拦截器已对chain.proceed()进行了必要的调用
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
        throw new IllegalStateException("network interceptor " + interceptor
                + " must call proceed() exactly once");
    }
    // Confirm that the intercepted response isn't null.
    //确认截获的响应不为空
    if (response == null) {
        throw new NullPointerException("interceptor " + interceptor + " returned null");
    }
    if (response.body() == null) {
        throw new IllegalStateException(
                "interceptor " + interceptor + " returned a response with no body");
    }
    
    return response;
}

proceed方法会取出当前chain指明的拦截器,并且用剩下的拦截器的信息生成下一个RealInterceptorChain,并调用当前chain指明的拦截器的intercept方法获取到intercept(next)方法获取到response并返回回去,实际的工作是由intercept来完成的

接下来根据getResponseWithInterceptorChain方法里的interceptors添加顺序一个个来分析源码

interceptors源码分析

RetryAndFollowUpInterceptor

这个拦截器负责两部分功能,retry和followup。

  • retry即重试,当一个请求进来,它会首先扔给后面的环节处理,当它下游的环节都搞不定,请求失败并抛出异常,这时候由这一环负责决定是否再来一次,判断是否重试的条件主要在RetryAndFollowUpInterceptor类的recover方法
  • followup功能在RetryAndFollowUpInterceptor的 followup()方法中,主要是检查response的返回码,根据返回码采取相应措施,比如代理验证、重定向、请求超时等。
@Override
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    //创建流分配
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;
    int followUpCount = 0;
    //预先的Response
    Response priorResponse = null;
    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) {
            //路由异常,例如ip错了
            // The attempt to connect via a route failed. The request will not have been sent.
            //尝试通过路由连接失败。该请求将不会被发送
            if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
                throw e.getFirstConnectException();
            }
            releaseConnection = false;
            continue;
        } catch (IOException e) {
            // 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();
            }
        }
        // Attach the prior response if it exists. Such responses never have a body.
        //附加先前的responses(如果存在)。这样的responses从来没有body。
        if (priorResponse != null) {
            response = response.newBuilder()
                    .priorResponse(priorResponse.newBuilder()
                            .body(null)
                            .build())
                    .build();
        }
        //后续请求
        Request followUp;
        try {
            //尝试获取后续请求
            followUp = followUpRequest(response, streamAllocation.route());
        } catch (IOException e) {
            streamAllocation.release();
            throw e;
        }
        if (followUp == null) {
            if (!forWebSocket) {
                streamAllocation.release();
            }
            //没有后续请求,返回这次执行的响应
            return response;
        }
        closeQuietly(response.body());
        if (++followUpCount > MAX_FOLLOW_UPS) {
            //后续请求过多
            streamAllocation.release();
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }
        if (followUp.body() instanceof UnrepeatableRequestBody) {
            //无法重试流式HTTP正文
            streamAllocation.release();
            throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
        }
        if (!sameConnection(response, followUp.url())) {
            //如果原来的连接不能重用,流分配释放掉,并新建一个流分配
            streamAllocation.release();
            streamAllocation = new StreamAllocation(client.connectionPool(),
                    createAddress(followUp.url()), call, eventListener, callStackTrace);
            this.streamAllocation = streamAllocation;
        } else if (streamAllocation.codec() != null) {
            throw new IllegalStateException("Closing the body of " + response
                    + " didn't close its backing stream. Bad interceptor?");
        }
        //请后续请求赋值给请求,响应赋值给原来的响应
        //while循环会使用新的请求重新走一遍上面逻辑
        request = followUp;
        priorResponse = response;
    }
}

followUpRequest方法用于判断是否要重新请求

/**
 * 主要针对http 状态码为3xx、4xx、5xx的出错信息进行处理,看下是否可以重新请求
 * Figures out the HTTP request to make in response to receiving {@code userResponse}. This will
 * either add authentication headers, follow redirects or handle a client request timeout. If a
 * follow-up is either unnecessary or not applicable, this returns null.
 * 找出response收到{@code userResponse}而发出的HTTP请求。这将添加身份验证标头,遵循重定向或处理客户端请求超时。如果后续跟踪是不必要的或不适用的,则返回null。
 *
 * @param userResponse 之前的响应
 */
private Request followUpRequest(Response userResponse, Route route) throws IOException {
    if (userResponse == null) {
        throw new IllegalStateException();
    }
    int responseCode = userResponse.code();
    final String method = userResponse.request().method();
    switch (responseCode) {
        case HTTP_PROXY_AUTH:
            //407,需要代理身份认证,请求要求代理的身份认证
            Proxy selectedProxy = route != null
                    ? route.proxy()
                    : client.proxy();
            if (selectedProxy.type() != Proxy.Type.HTTP) {
                throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
            }
            return client.proxyAuthenticator().authenticate(route, userResponse);
        case HTTP_UNAUTHORIZED:
            //401,认证失败,请求要求用户的身份认证
            return client.authenticator().authenticate(route, userResponse);
        case HTTP_PERM_REDIRECT:
        case HTTP_TEMP_REDIRECT:
            //308:永久转移,307:临时重定向
            // "If the 307 or 308 status code is received in response to a request other than GET
            // or HEAD, the user agent MUST NOT automatically redirect the request"
            //“如果响应除GET或HEAD以外的请求而接收到307或308状态代码,则用户代理务必不得自动重定向该请求”
            if (!method.equals("GET") && !method.equals("HEAD")) {
                return null;
            }
            // fall-through
        case HTTP_MULT_CHOICE:
        case HTTP_MOVED_PERM:
        case HTTP_MOVED_TEMP:
        case HTTP_SEE_OTHER:
            //300:多种选择,301:永久移动,302:临时移动,303:查看其他位置
            // Does the client allow redirects?
            if (!client.followRedirects()) return null;
            String location = userResponse.header("Location");
            if (location == null) return null;
            HttpUrl url = userResponse.request().url().resolve(location);
            // Don't follow redirects to unsupported protocols.
            //不能跟随重定向到不受支持的协议
            if (url == null) return null;
            // If configured, don't follow redirects between SSL and non-SSL.
            //如果已配置,请不要遵循SSL和非SSL之间的重定向。
            //如果协议不同(例如一个是HTTP一个是https)并且配置了不允许http和https只见重定向,则返回null
            boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
            if (!sameScheme && !client.followSslRedirects()) return null;
            // Most redirects don't include a request body.
            //大多数重定向不包含请求正文。
            Request.Builder requestBuilder = userResponse.request().newBuilder();
            if (HttpMethod.permitsRequestBody(method)) {
                final boolean maintainBody = HttpMethod.redirectsWithBody(method);
                if (HttpMethod.redirectsToGet(method)) {
                    requestBuilder.method("GET", null);
                } else {
                    RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
                    requestBuilder.method(method, requestBody);
                }
                if (!maintainBody) {
                    requestBuilder.removeHeader("Transfer-Encoding");
                    requestBuilder.removeHeader("Content-Length");
                    requestBuilder.removeHeader("Content-Type");
                }
            }
            // When redirecting across hosts, drop all authentication headers. This
            // is potentially annoying to the application layer since they have no
            // way to retain them.
            //跨主机重定向时,请删除所有身份验证标头。这可能会困扰应用程序层,因为它们无法保留它们。
            if (!sameConnection(userResponse, url)) {
                requestBuilder.removeHeader("Authorization");
            }
            return requestBuilder.url(url).build();
        case HTTP_CLIENT_TIMEOUT:
            //408:请求超时,服务器等待客户端发送的请求时间过长,超时
            // 408's are rare in practice, but some servers like HAProxy use this response code. The
            // spec says that we may repeat the request without modifications. Modern browsers also
            // repeat the request (even non-idempotent ones.)
            //408在实践中很少见,但某些服务器(例如HAProxy)使用此响应代码。规范说,我们可以重复请求而不做任何修改。现代浏览器还会重复请求(甚至是非幂等的请求)。
            if (!client.retryOnConnectionFailure()) {
                //应用层配置了不允许连接失败重试
                // The application layer has directed us not to retry the request.
                return null;
            }
            if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
                return null;
            }
            if (userResponse.priorResponse() != null
                    && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
                // We attempted to retry and got another timeout. Give up.
                //我们尝试重试并再次超时。放弃。
                return null;
            }
            if (retryAfter(userResponse, 0) > 0) {
                return null;
            }
            return userResponse.request();
        case HTTP_UNAVAILABLE:
            //503:暂停服务,由于超载或系统维护,服务器暂时的无法处理客户端的请求,延时的长度可包含在服务器的Retry-After头信息中
            if (userResponse.priorResponse() != null
                    && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
                // We attempted to retry and got another timeout. Give up.
                //我们尝试重试并再次超时。放弃
                return null;
            }
            if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
                // specifically received an instruction to retry without delay
                //特别收到立即重试的指令
                return userResponse.request();
            }
            return null;
        default:
            return null;
    }
}

recover用于判断异常请求是否可以恢复

/**
 * Report and attempt to recover from a failure to communicate with a server. Returns true if
 * {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
 * be recovered if the body is buffered or if the failure occurred before the request has been
 * sent.
 * 报告并尝试从与服务器通信的故障中恢复。如果{@code e}是可恢复的,则返回true;如果失败是永久的,则返回false。仅在缓冲正文或在发送请求之前发生故障时,才能恢复带有正文的请求。
 */
private boolean recover(IOException e, StreamAllocation streamAllocation,
                        boolean requestSendStarted, Request userRequest) {
    streamAllocation.streamFailed(e);
    // The application layer has forbidden retries.
    //客户端配置连接失败不允许失败重试,不再恢复
    if (!client.retryOnConnectionFailure()) return false;
    // We can't send the request body again.
    //我们无法再次发送请求正文
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
    // This exception is fatal.
    //此异常是致命的
    if (!isRecoverable(e, requestSendStarted)) return false;
    // No more routes to attempt.
    //没有其他路由地址了,恢复失败
    if (!streamAllocation.hasMoreRoutes()) return false;
    // For failure recovery, use the same route selector with a new connection.
    return true;
}

接下来是BridgeInterceptor

BridgeInterceptor

桥接拦截器

  • 主要功能是把便于用户识别和设置的请求信息转换成网络标准信息,网络返回结果后,再把网络返回的信息转变成用户便于识别的应用信息。
@Override
public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    //请求重新构造化
    Request.Builder requestBuilder = userRequest.newBuilder();
    RequestBody body = userRequest.body();
    if (body != null) {
        MediaType contentType = body.contentType();
        if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString());
        }
        long contentLength = body.contentLength();
        //contentLength有值则加入"Content-Length"的Header,移除"Transfer-Encoding"
        //"Transfer-Encoding:chunked"用于当响应发起时,内容长度还没能确定的情况下,和 Content-Length 不同时使用。
        if (contentLength != -1) {
            requestBuilder.header("Content-Length", Long.toString(contentLength));
            requestBuilder.removeHeader("Transfer-Encoding");
        } else {
            requestBuilder.header("Transfer-Encoding", "chunked");
            requestBuilder.removeHeader("Content-Length");
        }
    }
    if (userRequest.header("Host") == null) {
        requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    if (userRequest.header("Connection") == null) {
        //默认保持长连接
        requestBuilder.header("Connection", "Keep-Alive");
    }
    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing the transfer stream.
    // 如果我们添加“ Accept-Encoding:gzip”标头字段,我们还将负责解压缩传输流。
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
        transparentGzip = true;
        requestBuilder.header("Accept-Encoding", "gzip");
    }
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
        requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    if (userRequest.header("User-Agent") == null) {
        requestBuilder.header("User-Agent", Version.userAgent());
    }
    //当前拦截器处理请求完毕交个下一个拦截器处理
    // 而此次处理会找到下一个拦截器,并用生成下一个Chain,这个chain构建会指名此次处理使用那个拦截器,并调用下一个拦截器扥intercept方法,依次循环
    Response networkResponse = chain.proceed(requestBuilder.build());
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);
    if (transparentGzip
            && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
            && HttpHeaders.hasBody(networkResponse)) {
        GzipSource responseBody = new GzipSource(networkResponse.body().source());
        Headers strippedHeaders = networkResponse.headers().newBuilder()
                .removeAll("Content-Encoding")
                .removeAll("Content-Length")
                .build();
        responseBuilder.headers(strippedHeaders);
        String contentType = networkResponse.header("Content-Type");
        responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }
    return responseBuilder.build();
}

接下来是CacheInterceptor

CacheInterceptor

服务来自缓存的请求并将响应写入缓存

@Override
public Response intercept(Chain chain) throws IOException {
    //缓存候选
    Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    long now = System.currentTimeMillis();
    //根据时间、请求信息、缓存的响应生成一个缓存策略
    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.
    }
    // 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();
    }
    // If we don't need the network, we're done.
    //如果我们不需要网络,那就完成了。
    if (networkRequest == null) {
        return cacheResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .build();
    }
    Response networkResponse = null;
    try {
        networkResponse = chain.proceed(networkRequest);
    } finally {
        // If we're crashing on I/O or otherwise, don't leak the cache body.
        //如果我们在 I/O或其他方面崩溃,请不要泄漏高速缓存主体。
        if (networkResponse == null && cacheCandidate != null) {
            closeQuietly(cacheCandidate.body());
        }
    }
    // 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()).
            //在合并标头之后但在剥离Content-Encoding标头之前(由initContentStream()执行),更新缓存。
            cache.trackConditionalCacheHit();
            cache.update(cacheResponse, response);
            return response;
        } else {
            closeQuietly(cacheResponse.body());
        }
    }
    //networkResponse不为空cacheResponse为空,返回这次网络请求的response
    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);
            //生成RealResponseBody
            return cacheWritingResponse(cacheRequest, response);
        }
        if (HttpMethod.invalidatesCache(networkRequest.method())) {
            try {
                cache.remove(networkRequest);
            } catch (IOException ignored) {
                // The cache cannot be written.
                //无法写入缓存
            }
        }
    }
    return response;
}

我们来看CacheStrategy.get()

/**
 * Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
 * 使用缓存的响应{@code response}返回满足{@code request}的策略。
 */
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;
/**
 * Returns a strategy to use assuming the request can use the network.
 * 假设请求可以使用网络,则返回要使用的策略。
 */
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 {
        // No condition! Make a regular request.没有条件!提出定期要求
        return new CacheStrategy(request, null);
    }
    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);
}

接下来是ConnectInterceptor

ConnectInterceptor

打开与目标服务器的连接,然后进入下一个拦截器。

@Override
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    //我们需要网络来满足此要求。可能用于验证条件GET
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //http编码解码器
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

重要内容都在streamAllocation.newStream方法里

public HttpCodec newStream(
        OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();
    try {
        //找到可用连接
        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
                writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        //创建http编解码器
        HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
        synchronized (connectionPool) {
            codec = resultCodec;
            return resultCodec;
        }
    } catch (IOException e) {
        throw new RouteException(e);
    }

findHealthyConnection方法

/**
 * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
 * until a healthy connection is found.
 * 查找连接,如果连接状况良好,则将其返回。如果不健康,请重复此过程,直到找到健康的连接为止。
 */
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
                                             int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
                                             boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
        //获取一个连接
        RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
                pingIntervalMillis, connectionRetryEnabled);
        // If this is a brand new connection, we can skip the extensive health checks.
        //如果这是一个全新的连接,我们可以跳过广泛的运行状况检查。
        synchronized (connectionPool) {
            if (candidate.successCount == 0) {
                return candidate;
            }
        }
        // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
        // isn't, take it out of the pool and start again.
        //进行(可能很慢)检查以确认池中连接仍然良好。如果不是,请将其从池中取出并重新开始。
        //检查这个连接是不是不健康的,不健康的则禁止在承载此分配的连接上创建新的流并关闭
        if (!candidate.isHealthy(doExtensiveHealthChecks)) {
            noNewStreams();
            continue;
        }
        return candidate;
    }
}

findConnection方法

/**
 * Returns a connection to host a new stream. This prefers the existing connection if it exists,
 * then the pool, finally building a new connection.
 * 返回用于托管新流的连接。如果存在现有连接,则首选现有连接,然后是从池中选择,最后建立一个新连接。
 */
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
        if (released) throw new IllegalStateException("released");
        if (codec != null) throw new IllegalStateException("codec != null");
        if (canceled) throw new IOException("Canceled");
        // Attempt to use an already-allocated connection. We need to be careful here because our
        // already-allocated connection may have been restricted from creating new streams.
        //尝试使用已分配的连接。我们在这里需要小心,因为我们已经分配的连接可能受到限制,无法创建新的流。
        releasedConnection = this.connection;
        toClose = releaseIfNoNewStreams();
        if (this.connection != null) {
            // We had an already-allocated connection and it's good.
            //我们已经分配了一个连接并且他是好的
            result = this.connection;
            releasedConnection = null;
        }
        if (!reportedAcquired) {
            // If the connection was never reported acquired, don't report it as released!
            //如果从未报告该连接已获得,请不要将其报告为已释放!
            releasedConnection = null;
        }
        if (result == null) {
            //根据已知的address在connectionPool里面找,如果有连接,则返回
            // Attempt to get a connection from the pool.
            //尝试从池中获取连接。
            Internal.instance.get(connectionPool, address, this, null);
            if (connection != null) {
                foundPooledConnection = true;
                result = connection;
            } else {
                selectedRoute = route;
            }
        }
    }
    closeQuietly(toClose);
    if (releasedConnection != null) {
        eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
        // If we found an already-allocated or pooled connection, we're done.
        //如果我们发现一个已经分配或连接的连接,就完成了。
        return result;
    }
    //更换路由,更换线路,在connectionPool里面再次查找,如果有则返回。
    // If we need a route selection, make one. This is a blocking operation.
    //如果需要路线选择,请选择一个。这是一个阻塞操作。
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
        newRouteSelection = true;
        routeSelection = routeSelector.next();
    }
    synchronized (connectionPool) {
        if (canceled) throw new IOException("Canceled");
        if (newRouteSelection) {
            // Now that we have a set of IP addresses, make another attempt at getting a connection from the pool. This could match due to connection coalescin
            // 现在我们有了一组IP地址,请再次尝试从池中获取连接。由于连接合并,这可能匹配。
            List<Route> routes = routeSelection.getAll();
            for (int i = 0, size = routes.size(); i < size; i++) {
                Route route = routes.get(i);
                //尝试从池中获取连接。
                Internal.instance.get(connectionPool, address, this, route);
                if (connection != null) {
                    foundPooledConnection = true;
                    result = connection;
                    this.route = route;
                    break;
                }
            }
        }
        if (!foundPooledConnection) {
            if (selectedRoute == null) {
                selectedRoute = routeSelection.next();
            }
            //*如果以上条件都不满足则直接new一个RealConnection出来
            // Create a connection and assign it to this allocation immediately. This makes it possible
            // for an asynchronous cancel() to interrupt the handshake we're about to do.
            //创建一个连接,并将其立即分配给该分配。这使得异步cancel()可以中断我们将要进行的握手。
            route = selectedRoute;
            refusedStreamCount = 0;
            result = new RealConnection(connectionPool, selectedRoute);
            //*新建的RealConnection通过acquire关联到connection.allocations上
            acquire(result, false);
        }
    }
    // If we found a pooled connection on the 2nd time around, we're done.
    //如果我们在第二次发现池化连接,就完成了。
    if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
        return result;
    }
    // Do TCP + TLS handshakes. This is a blocking operation.
    //执行TCP + TLS握手。这是一个阻塞操作。
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());
    做去重判断,如果有重复的socket则关闭
    Socket socket = null;
    synchronized (connectionPool) {
        reportedAcquired = true;
        // Pool the connection.
        //汇集连接。
        Internal.instance.put(connectionPool, result);
        // If another multiplexed connection to the same address was created concurrently, then
        // release this connection and acquire that one.
        //如果同时创建了另一个到同一地址的多路复用连接,则释放该连接并获取该连接
        if (result.isMultiplexed()) {
            socket = Internal.instance.deduplicate(connectionPool, address, this);
            result = connection;
        }
    }
    closeQuietly(socket);
    eventListener.connectionAcquired(call, result);
    return result;
}

最后是CallServerInterceptor

CallServerInterceptor

这是链中的最后一个拦截器。它对服务器进行网络呼叫

  • 实现链接建立后读写请求和响应内容的功能
@Override
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();
    long sentRequestMillis = System.currentTimeMillis();
    realChain.eventListener().requestHeadersStart(realChain.call());
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
        // Continue" response before transmitting the request body. If we don't get that, return
        // what we did get (such as a 4xx response) without ever transmitting the request body.
        //如果请求上有一个“期望:100-继续”标头,则在发送请求正文之前,等待“ HTTP / 1.1 100继续”响应。
        // 如果没有得到,请返回我们得到的结果(例如4xx响应),而无需传输请求主体。
            if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
            httpCodec.flushRequest();
            realChain.eventListener().responseHeadersStart(realChain.call());
            //读取响应头
            responseBuilder = httpCodec.readResponseHeaders(true);
        }
        if (responseBuilder == null) {
            // Write the request body if the "Expect: 100-continue" expectation was met.
            //如果满足“Expect: 100-continue”的期望,请写请求体。
            realChain.eventListener().requestBodyStart(realChain.call());
            long contentLength = request.body().contentLength();
            CountingSink requestBodyOut =
                    new CountingSink(httpCodec.createRequestBody(request, contentLength));
            BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
            request.body().writeTo(bufferedRequestBody);
            bufferedRequestBody.close();
            realChain.eventListener()
                    .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
        } else if (!connection.isMultiplexed()) {
            // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
            // from being reused. Otherwise we're still obligated to transmit the request body to
            // leave the connection in a consistent state.
            //如果未达到“Expect: 100-continue”的期望,请防止HTTP / 1连接被重用。否则,我们仍然有义务传输请求主体,以使连接保持一致状态
            streamAllocation.noNewStreams();
        }
    }
    httpCodec.finishRequest();
    if (responseBuilder == null) {
        realChain.eventListener().responseHeadersStart(realChain.call());
        //读取响应头
        responseBuilder = httpCodec.readResponseHeaders(false);
    }
    Response response = responseBuilder
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    int code = response.code();
    if (code == 100) {
        // server sent a 100-continue even though we did not request one.
        // try again to read the actual response
        //即使我们没有请求,服务器仍发送了100-continu。再试一次以读取实际响应
        responseBuilder = httpCodec.readResponseHeaders(false);
        response = responseBuilder
                .request(request)
                .handshake(streamAllocation.connection().handshake())
                .sentRequestAtMillis(sentRequestMillis)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();
        code = response.code();
    }
    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);
    if (forWebSocket && code == 101) {
        // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
        //连接正在升级,但是我们需要确保拦截器看到非空的响应主体
        response = response.newBuilder()
                .body(Util.EMPTY_RESPONSE)
                .build();
    } else {
        response = response.newBuilder()
                .body(httpCodec.openResponseBody(response))
                .build();
    }
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
            || "close".equalsIgnoreCase(response.header("Connection"))) {
        streamAllocation.noNewStreams();
    }
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
        throw new ProtocolException(
                "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    return response;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值