OkHttp – official address OkHttp – GitHub code address

An overview of the

OkHttp overall flow (covered in red)

Interceptor processes

Chain of Responsibility model

The Chain of Responsibility Pattern consists of command objects and a series of processing objects that implement the same interface and are connected to form a Chain of Responsibility. Each processing object determines which command objects it can process, and those it cannot process are passed on to the next processing object in the chain. The chain of responsibility pattern is an abstraction of this sequential event processing behavior, defining the method of event processing through the interface, and distributing/processing events sequentially.

The main solution is that the handler on the responsibility chain is responsible for processing the request. The customer only needs to send the request to the responsibility chain, and does not need to care about the processing details of the request and the delivery of the request, so the responsibility chain decouples the sender of the request and the handler of the request.

advantages

  1. Reduce coupling. It decouples the sender and receiver of the request.
  2. Simplifies the object. The object does not need to know the structure of the chain.
  3. Enhanced flexibility in assigning responsibilities to objects. Responsibilities can be dynamically added or removed by changing members in the chain or reordering them.
  4. It is convenient to add new request handling classes.

The characteristics of

  1. Each responsible person implements the same interface and handles an event object
  2. Pass sequentially between event object owners
  3. The result of an event is returned in reverse order
  4. Each responsible person in the chain of responsibility has the right to process the event and return the result without continuing to transmit the event

In OkHttp, the command object is the Request object, and the processing object is each Interceptor object. Each interceptor does some processing of the request and passes the rest of the work to the next interceptor. In addition, a handler in the chain of responsibility does not need to pass to the next handler if it has full authority to handle the command object. The CacheInterceptor in OkHttp also has carte Blanche capabilities. If the request result is already cached, the ConnectInterceptor does not need to connect to the server and send the request. Instead, it directly returns the cached response.

Source code analysis

Interceptor

Interceptors, which process object classes, are called sequentially by Chain and Interceptor, and the Response is returned in turn. Interceptors can generally be summarized in three steps

  1. Pre-request processing logic
  2. Proceed with chain.proceed, called sequentially
  3. Logic processing after obtaining the response result

The default interceptors are included in order

  1. RetryAndFollowUpInterceptor (retry mechanism)
  2. BridgeInterceptor (Http Connection Conversion Bridge)
  3. CacheInterceptor (Cache Policy)
  4. ConnectInterceptor
  5. CallServerInterceptor (Response read)
Interinterceptor {// Interceptor method, which accepts a chain of interceptors (Request and Call command objects, and a series of auxiliary objects), returns the result: Fun proceed(request: request): Response... }}Copy the code

RealInterceptorChain Chain of interceptors

As you know from the previous article, the RealCall command object executes the Method proceed on the RealInterceptorChain, which loops through the Chain object in the order in which the interceptors were added. Then execute the interceptor’s interception method, directing the last interceptor or the midstream interceptor directly back.

Class RealInterceptorChain(private val interceptors: List<Interceptor>, // Interceptor) Private val exchange: exchange? Private val index: Int, private val request: request, private val call: Call, // A request and return encapsulate private val connectTimeout: Int, private valreadTimeout: Int,
  private val writeTimeout: Int
) : Interceptor.Chain {
  ...

  override fun proceed(request: Request): Response {
    returnProceed (request, transmitter, exchange)} // Proceed (request: request, transmitter: exchange) Exchange?) : Response { ... calls++ ... * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * call, connectTimeout,readWriteTimeout) val interceptor = interceptors[index] Chain. proceed completes the reponse return in the interceptor method, passing the command object to the next interceptor for processing, or terminating at that interceptor if chain.proceed is not executed. val response = interceptor.intercept(next) ?: throw NullPointerException("interceptor $interceptor returned null")...return response
  }

}
Copy the code

RetryAndFollowUpInterceptor

Retry interceptor

RetryAndFollowUpInterceptor.intercept

  • Request connection preparation
  • Proceed in chain.proceed order to obtain response
  • If an exception occurs, determine whether a retry mechanism is required
  • According to the status code returned by the server, the redirection request is generated
  • Retry a maximum of 20 times until no further retry is required or the retry succeeds
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    var request = chain.request()
    val realChain = chain as RealInterceptorChain
    val transmitter = realChain.transmitter()
    var followUpCount = 0
    var priorResponse: Response? = null
    while (true) {/ / cycle / / transmitter to perform connection preparation transmitter. PrepareToConnect (request)if(transmitter. IsCanceled) {// Throw IOException("Canceled")
      }

      var response: Response
      var success = falseResponse response = realChain. Proceed (request, transmitter, null) Success =true} catch (e: RouteException) {// An exception will be thrown if the retry condition is not metif(! recover(e.lastConnectException, transmitter,false, request)) {
          throw e.firstConnectException
        }
        continue} catch (e: IOException) {val requestSendStarted = e! is ConnectionShutdownExceptionif(! recover(e, transmitter, requestSendStarted, request)) throw econtinue} finally {// exception thrown, success=falseTo release resourcesif(! success) { transmitter.exchangeDoneDueToException() } }if(priorResponse ! Response = response.newBuilder().priorResponse(priorResponse.newBuilder().body(null) .build()) .build() } val exchange = response.exchange val route = exchange? .connection()? .route() val followUp = followUpRequest(response, route)if (followUp == null) {
        if(exchange ! = null && exchange. IsDuplex) {transmitter. TimeoutEarlyExit () / / no retry, stop the timeout and exit}returnResponse} val followUpBody = followup. body // If you want to execute once, endif(followUpBody ! = null && followUpBody.isOneShot()) {returnresponse } response.body? .closeQuietly()if(transmitter.hasExchange()) { exchange? .detachWithviolence ()} // Retry up to 20 times thisif (++followUpCount > MAX_FOLLOW_UPS) {
        throw ProtocolException("Too many follow-up requests: $followUpCount"Request = followUp priorResponse = response}}Copy the code

RetryAndFollowUpInterceptor.followUpRequest

The Location field is taken from the Header and the redirect request is constructed

private fun followUpRequest(userResponse: Response, route: Route?) : Request? { val responseCode = userResponse.code val method = userResponse.request.method when (responseCode) { HTTP_PROXY_AUTH ->  { // 407 val selectedProxy = route!! .proxyif(selectedProxy.type() ! = Proxy.Type.HTTP) { throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy"} // Proxy authenticationreturnClient. ProxyAuthenticator. Authenticate (route, userResponse)} / / authentication HTTP_UNAUTHORIZED - >returnClient. The authenticator. Authenticate (route, userResponse) / / to Get, Head request execution redirection request HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT -> {// 307, 308if(method ! ="GET"&& method ! ="HEAD") {
          return null
        }
        returnBuildRedirectRequest (userResponse, method)} // Execute build redirection request HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {returnBuildRedirectRequest (userResponse, method)} HTTP_CLIENT_TIMEOUT -> {// 408 need to send the same request again... // Determine if it is necessary to resendreturn userResponse.request
      }

      HTTP_UNAVAILABLE -> { // 503
        return null
      }
      else -> returnnull } } private fun buildRedirectRequest(userResponse: Response, method: String): Request? {// Setting does not require a redirectionif(! client.followRedirects)returnVal location = userResponse.header("Location") ?: return null
    // Don't follow redirects to unsupported protocols. val url = userResponse.request.url.resolve(location) ? : return null / / if not allowed between the SSL and the Non - SSL redirection, it returns null val sameScheme = url. The scheme = = userResponse. The request url. The scheme if (! sameScheme && ! client.followSslRedirects) return null // Most redirects don't include a request body.
    val requestBuilder = userResponse.request.newBuilder()
    if (HttpMethod.permitsRequestBody(method)) {
      val maintainBody = HttpMethod.redirectsWithBody(method)
      if (HttpMethod.redirectsToGet(method)) {
        requestBuilder.method("GET", null)
      } else {
        val requestBody = if (maintainBody) userResponse.request.body else 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(! userResponse.request.url.canReuseConnectionFor(url)) { requestBuilder.removeHeader("Authorization"} // Build a new requestreturn requestBuilder.url(url).build()
  }
Copy the code

BridgeInterceptor

Bridges from application code to network code.

A bridge that connects the application layer to the network layer

  • It mainly deals with request headers, such as Gzip, cookie, and other Header fields
  • The response is gzip decompressed
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val userRequest = chain.request() val requestBuilder = userRequest.newBuilder() // Set the property in userRequest to val body = userRequest.body in requestif(body ! = null) { val contentType = body.contentType()if(contentType ! = null) { requestBuilder.header("Content-Type", contentType.toString())
      }

      val contentLength = body.contentLength()
      if(contentLength ! = -1L) { requestBuilder.header("Content-Length", contentLength.toString())
        requestBuilder.removeHeader("Transfer-Encoding")}else {
        requestBuilder.header("Transfer-Encoding"."chunked")
        requestBuilder.removeHeader("Content-Length")}}if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", userRequest.url.toHostHeader())
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection"."Keep-Alive")} // If accept-encoding is not set, gzip var transparentGzip = is automatically setfalse
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true
      requestBuilder.header("Accept-Encoding"."gzip"Val cookies = cookiejar.loadForRequest (userRequest.url)if (cookies.isNotEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies))
    }
    // 设置 User-Agent
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent"Val networkResponse = chain.proceed(requestBuilder.build()); And cache cookie cookieJar.receiveHeaders(userRequest.url, NetworkResponse. Headers) val responseBuilder. = networkResponse newBuilder (.) the request (userRequest) / / set the Gzip, If the response result supports GZIP, gZIP resolution is performedif (transparentGzip &&
        "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
        networkResponse.promisesBody()) {
        
      val responseBody = networkResponse.body
      if(responseBody ! = null) { val gzipSource = GzipSource(responseBody.source()) val strippedHeaders = networkResponse.headers.newBuilder() .removeAll("Content-Encoding")
            .removeAll("Content-Length")
            .build()
        responseBuilder.headers(strippedHeaders)
        val contentType = networkResponse.header("Content-Type")
        responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
      }
    }

    return responseBuilder.build()
  }

  /** Returns a 'Cookie' HTTP request header with all cookies, like `a=b; c=d`. */
  private fun cookieHeader(cookies: List<Cookie>): String = buildString {
    cookies.forEachIndexed { index, cookie ->
      if (index > 0) append("; ")
      append(cookie.name).append('=').append(cookie.value)
    }
  }
}
Copy the code

OkHttp 4 source code (3) – cache mechanism analysis