OkHttp源码学习(二)拦截器

本文深入探讨了OkHttp的拦截器机制,详细介绍了RetryAndFollowUpInterceptor、ConnectInterceptor和CallServerInterceptor的工作原理。RetryAndFollowUpInterceptor负责重试和重定向,ConnectInterceptor创建连接和流对象,而CallServerInterceptor则处理请求与响应的交换。连接池的管理,包括获取和清理连接,确保高效使用。通过对这些拦截器的学习,可以更深入理解OkHttp的网络请求流程。
部署运行你感兴趣的模型镜像

前言

OkHttp同步或者异步都会通过getResponseWithInterceptorChain方法获取响应,此方法通过一系列的拦截器进行处理。这些拦截器分别有:RetryAndFollowUpInterceptor(重试或者重连)、BridgeInterceptor(request和response的转换)、 CacheInterceptor(缓存)、ConnectInterceptor(连接)、CallServerInterceptor(负责将请求数据写到输出流和从输入 流中获取响应数据

连接相关

  • ConnectInterceptor#intercept:
public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain)chain;
        Request request = realChain.request();
        //1.通过Chain对象获取StreamAllocation
        StreamAllocation streamAllocation = realChain.streamAllocation();
        boolean doExtensiveHealthChecks = !request.method().equals("GET");
        //2.通过StreamAllocation获取流对象(根据Http版本不同获取不同的HttpCodec)
        HttpCodec httpCodec = streamAllocation.newStream(this.client, chain, doExtensiveHealthChecks);
        //3.根据StreamAllocation获取连接
        RealConnection connection = streamAllocation.connection();
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
    }

ConnectInterceptor负责用StreamAllocation去创建连接和流对象,方便发起请求后面的使用。

  • StreamAllocation
//StreamAllocation中的属性

 public final Address address;//封装了URL地址

  private Route route;

  private final ConnectionPool connectionPool;//连接池

  private final Object callStackTrace;

  // State guarded by connectionPool.

  private final RouteSelector routeSelector;

  private int refusedStreamCount;

  private RealConnection connection;//连接对象

  private boolean released;

  private boolean canceled;

  private HttpCodec codec;//流对象

newStream方法:

 public HttpCodec newStream(OkHttpClient client, Chain chain, boolean doExtensiveHealthChecks) {
        ......//一些列出参数,例如超时时间

        try {
          //寻找合适的连接
          /*
            1.从连接池中复用连接
            2.选择一个合适的路径,并且在该路径上建立连接;然后将该连接加入到连接池
            3.新建的连接发起连接,然后从路线黑名单移除掉该连接(因为该连接刚建立,是可用的)
          */
            RealConnection resultConnection = this.findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
            //创建流对象
            HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
            ConnectionPool var11 = this.connectionPool;
            synchronized(this.connectionPool) {
                this.codec = resultCodec;
                return resultCodec;
            }
        } catch (IOException var14) {
            throw new RouteException(var14);
        }
    }

StreamAllocation通过寻找一个可用的连接,然后在连接上建立一个流,这个流根据不同的协议而不同(Http1Codec、Http2Codec等)。

  • 连接(RealConnection)

RealConnection的成员

private static final String NPE_THROW_WITH_NULL = "throw with null exception";
    private static final int MAX_TUNNEL_ATTEMPTS = 21;
    private final ConnectionPool connectionPool;//连接池
    private final Route route;
    private Socket rawSocket;//基于Socket
    private Socket socket;
    private Handshake handshake;
    private Protocol protocol;
    private Http2Connection http2Connection;
    private BufferedSource source;//输入流
    private BufferedSink sink;//输出流
    public boolean noNewStreams;//是否还可用标志
    public int successCount;
    public int allocationLimit = 1;//分配流的数量上限,默认为1,Http1.1版本
    public final List<Reference<StreamAllocation>> allocations = new ArrayList();//维护着一个连接上有多少个流。
    public long idleAtNanos = 9223372036854775807L;
  	...

connect方法:

 public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
        ......
		//连接Socket
		this.connectSocket(connectTimeout, readTimeout, call, eventListener);
		......
		//初始化协议
		this.establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
		......
    }

connectSocket方法:

 private void connectSocket(int connectTimeout, int readTimeout, Call call, EventListener eventListener) throws IOException {
        Proxy proxy = this.route.proxy();
        Address address = this.route.address();
   //根据不同代理类型创建对应的Socket
        this.rawSocket = proxy.type() != Type.DIRECT && proxy.type() != Type.HTTP ? new Socket(proxy) : address.socketFactory().createSocket();
        eventListener.connectStart(call, this.route.socketAddress(), proxy);
        this.rawSocket.setSoTimeout(readTimeout);

        try {
          //内部调用Socket的connect方法,建立Socket连接
            Platform.get().connectSocket(this.rawSocket, this.route.socketAddress(), connectTimeout);
        } catch (ConnectException var9) {
            ......
        }

        try {
          //通过Socket初始化输入输出流
            this.source = Okio.buffer(Okio.source(this.rawSocket));
            this.sink = Okio.buffer(Okio.sink(this.rawSocket));
        } catch (NullPointerException var10) {
            ......
        }

    }

此方法中根据代理和路由对象创建Socket对象,然后将Socket进行连接,连接后通过Socket初始化(封装)输入输出流。

  • 连接池(ConnectionPool)
    成员
public final class ConnectionPool {
    private static final Executor executor;
    private final int maxIdleConnections;//最大空闲数
    private final long keepAliveDurationNs;//最长空闲时间
    private final Runnable cleanupRunnable;//清除任务
    private final Deque<RealConnection> connections;//连接队列
    final RouteDatabase routeDatabase;//用于放置黑名单(不可用的路径)
    boolean cleanupRunning;//清理标志
 }

get方法:

@Nullable
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert Thread.holdsLock(this);

    Iterator var4 = this.connections.iterator();

    RealConnection connection;
    do {
        if (!var4.hasNext()) {
            return null;
        }
        connection = (RealConnection)var4.next();
      //根据Address和Route来匹配寻找的Connection
    } while(!connection.isEligible(address, route));
	//将StreamAllocation存放到Connection的allocations中
    streamAllocation.acquire(connection, true);
    return connection;
}

通过访问资源地址、端口、协议、代理等进行匹配获取连接。然后将StreamAllocation添加进流列表中。

put方法:

void put(RealConnection connection) {
    assert Thread.holdsLock(this);

    if (!this.cleanupRunning) {
      //如果没有清理任务,则出发清理任务
        this.cleanupRunning = true;
        executor.execute(this.cleanupRunnable);
    }
	//将连接添加到集合中去
    this.connections.add(connection);
}

如果没有正在执行清理任务,则开始执行清理任务,然后将连接添加进连接列表中。
清理任务:

cleanupRunnable = new Runnable() {
    public void run() {
      //持续清理,需等待清理方法返回的时间
        while(true) {
            long waitNanos = ConnectionPool.this.cleanup(System.nanoTime());
            if (waitNanos == -1L) {
                return;
            }

            if (waitNanos > 0L) {
                long waitMillis = waitNanos / 1000000L;
                waitNanos -= waitMillis * 1000000L;
                ConnectionPool var5 = ConnectionPool.this;
                synchronized(ConnectionPool.this) {
                    try {
                        ConnectionPool.this.wait(waitMillis, (int)waitNanos);
                    } catch (InterruptedException var8) {
                        ;
                    }
                }
            }
        }
    }
};

cleanup方法会统计空闲个数,如果空闲个数超过限制的空闲个数,或者某连接空闲时间超过最大的限制,则会清理闲置最长时间的连接。

CallServerInterceptor

发起请求于获取响应的拦截器。

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());
  //1.写入请求头
    httpCodec.writeRequestHeaders(request);
    ...
    Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
          
          //将请求头写到输出流去
            httpCodec.flushRequest();
            realChain.eventListener().responseHeadersStart(realChain.call());
          //表示有请求体,所以里边会返回null
            responseBuilder = httpCodec.readResponseHeaders(true);
        }

        if (responseBuilder == null) {
            realChain.eventListener().requestBodyStart(realChain.call());
            long contentLength = request.body().contentLength();
            CallServerInterceptor.CountingSink requestBodyOut = new CallServerInterceptor.CountingSink(httpCodec.createRequestBody(request, contentLength));
            BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
          //2.将请求体写到输出流中
            request.body().writeTo(bufferedRequestBody);
            bufferedRequestBody.close();
            realChain.eventListener().requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
        } else if (!connection.isMultiplexed()) {
            streamAllocation.noNewStreams();
        }
    }
	//3.将请求写入到Socket的输出流
    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) {
      //4.获取响应头
        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 (this.forWebSocket && code == 101) {
     
        response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();
    } else {
      //5.从输入流中获取响应体
        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() > 0L) {
        throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    } else {
        return response;
    }
}
  1. 写入请求头,然后根据请求头是否有Expect字段,如果有,则表示询问服务端是否接受请求体。
  2. 构建请求体,然后将请求体写入到输出流中。
  3. 将请求写入到Socket的输出流中。
  4. 获取响应头。
  5. 获取响应体,判断某些特殊的响应码。

RetryAndFollowUpInterceptor

  public Response intercept(Chain chain) throws IOException {
       ......

        while(!this.canceled) {
            boolean releaseConnection = true;

            Response response;
            try {
            //1. 开始进行请求
                response = realChain.proceed(request, streamAllocation, (HttpCodec)null, (RealConnection)null);
                releaseConnection = false;
            } catch (RouteException var19) {
            //1.1 重试
                if (!this.recover(var19.getLastConnectException(), streamAllocation, false, request)) {
                    throw var19.getFirstConnectException();
                }

                releaseConnection = false;
                continue;
            } catch (IOException var20) {
                boolean requestSendStarted = !(var20 instanceof ConnectionShutdownException);
                //1.2 重试
                if (!this.recover(var20, streamAllocation, requestSendStarted, request)) {
                    throw var20;
                }

                releaseConnection = false;
                continue;
            } finally {
                if (releaseConnection) {
                    streamAllocation.streamFailed((IOException)null);
                    streamAllocation.release();
                }

            }

            ......

            Request followUp;
            try {
            //2. 判断是否重定向
                followUp = this.followUpRequest(response, streamAllocation.route());
            } catch (IOException var18) {
                streamAllocation.release();
                throw var18;
            }

            if (followUp == null) {
                if (!this.forWebSocket) {
                    streamAllocation.release();
                }

                return response;
            }

            ......

            if (!this.sameConnection(response, followUp.url())) {
            //3. 需要重定向,则释放原来的StreamAllcation,并根据新的路径重新创建一个新的
                streamAllocation.release();
                streamAllocation = new StreamAllocation(this.client.connectionPool(), this.createAddress(followUp.url()), call, eventListener, this.callStackTrace);
                this.streamAllocation = streamAllocation;
            } 
            ......
    }
  1. 重试,如果没有取消,则执行网络请求,请求过程如果抛出异常,会调用recover进行重试;如果网络请求正确,则会进行下面的重定向。
  2. 重定向,根据Response判断是否需要重定向,如果需要,则判断主机路径端口等是否相同,如果不同则释放先前的StreamAllocation然后重新创建一个。

以上便是OkHttp之中的其三个拦截器学习。

您可能感兴趣的与本文相关的镜像

PyTorch 2.8

PyTorch 2.8

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值