๐Ÿค”okhttp(์‹ฌํ™”) ๋‚ด๋ถ€ Intercepter github ์ฝ”๋“œ ๋ถ„์„ํ•ด๋ณด๊ธฐ

๋™ํ‚คยท2025๋…„ 4์›” 11์ผ

์•ˆ๋“œ๋กœ์ด๋“œ

๋ชฉ๋ก ๋ณด๊ธฐ
8/14

์ด์ „์— okhttp์˜ ๊ฐœ๋…๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธํ„ฐ์…‰ํ„ฐ์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ–ˆ๋‹ค.

๊ณต๋ถ€ํ•˜๋ฉฐ ์„œ๋ฒ„์—์„œ 401์„ ์‘๋‹ตํ•˜๋ฉด OkhttpClient ์˜ Authenticator ๊ฐ€ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜๊ณ  ์žฌ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

๋ถ„๋ช… G๋ฆฌ๋Š” ๊ธฐ์ˆ ์ธ๋ฐโ€ฆ

์–ด๋–ป๊ฒŒ ์ด๊ฒŒ ๊ฐ€๋Šฅํ•œ๊ฑฐ์ง€? ๊ถ๊ธˆํ•ด์„œ okhttp์˜ github์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜์—ˆ๋‹ค.

์šฐ์„  ์–ด๋–ค ์ธํ„ฐ์…‰ํ„ฐ๋“ค์ด ์žˆ๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ธํ„ฐ์…‰ํ„ฐ์˜ ์ข…๋ฅ˜

์–ด๋–ค ์ธํ„ฐ์…‰ํ„ฐ๋“ค์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ RealCall ํŒŒ์ผ์— ๋“ค์–ด๊ฐ”์Šต๋‹ˆ๋‹ค.

    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)

์˜คโ€ฆ ์šฐ์„  ์ธํ„ฐ์…‰ํ„ฐ๋“ค์„ mutableListOf ๋กœ ๊ด€๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ๊ตฌ๋งŒโ€ฆ

์ด์ œ ์–ด๋–ค ์ธํ„ฐ์…‰ํ„ฐ๋“ค์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

client.interceptors

ํด๋ผ์ด์–ธํŠธ(Android)์—์„œ ์„ค์ •ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธํ„ฐ์…‰ํ„ฐ ๋“ค์ด ํ•ด๋‹น๋ฉ๋‹ˆ๋‹ค.

RetryAndFollowUpInterceptor

 // * This interceptor recovers from failures and follows redirects as necessary. It may throw an
 // * [IOException] if the call was canceled.

์š”์ฒญ ์‹คํŒจ ๋ฐ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

์ด๋…€์„์ด ๋ฐ”๋กœ ์„œ๋ฒ„์—์„œ 401์„ ๋‚ด๋ ค์ฃผ๋ฉด Authenticator ๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ  ์žฌ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์•„์ฃผ ๊ณ ๋งˆ์šด ๋…€์„์ž…๋‹ˆ๋‹ค.

์ด ์นœ๊ตฌ๊ฐ€ ์–ด๋–ป๊ฒŒ 401์„ ๋ฐ›์œผ๋ฉด Authenticator ๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ  ์žฌ์š”์ฒญ์„ ๋ณด๋‚ด๋Š”์ง€๋Š” ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ๋‹ค๋ฃจ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.


BridgeInterceptor

/**
 * Bridges from application code to network code. First it builds a network request from a user
 * request. Then it proceeds to call the network. Finally it builds a user response from the network
 * response.
 */

์•ฑ ์ฝ”๋“œ์—์„œ ๋„คํŠธ์›Œํฌ ์ฝ”๋“œ๋กœ ๊ฐ€๋Š” ๋‹ค๋ฆฌ ์—ญํ•  ์„ ํ•ฉ๋‹ˆ๋‹ค.

ย ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด OkHttp ๋‚ด๋ถ€์—์„œ โ€œ์‚ฌ์šฉ์ž ์š”์ฒญ โ†” ์‹ค์ œ HTTP ์š”์ฒญโ€์„ ์—ฐ๊ฒฐํ•˜๋Š” ์ค‘๊ฐ„์ฒ˜๋ฆฌ๊ธฐ ์—ญํ™œ์„ ํ•ฉ๋‹ˆ๋‹ค.

์š”์ฒญ(Request) ํ‘œ์ค€ํ™”

  • User-Agent, Accept-Encoding, Content-Type, Content-Length ๋“ฑ์„ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€
  • Gzip ์••์ถ• ์š”์ฒญ ์—ฌ๋ถ€๋„ ์—ฌ๊ธฐ์„œ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

์š”์ฒญ ๋ฐ”๋”” ์ฒ˜๋ฆฌ

  • POST, PUT ๋“ฑ์˜ ์š”์ฒญ์— Body๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, Content-Length ํ˜น์€ Transfer-Encoding: chunked๋ฅผ ์ž๋™ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

์‘๋‹ต(Response) ๋ณ€ํ™˜

  • ๋„คํŠธ์›Œํฌ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์‘๋‹ต(Response)์„ ์‚ฌ์šฉ์ž ์‘๋‹ต ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

CacheInterceptor

/** Serves requests from the cache and writes responses to the cache. */

HTTP ๋ฐ HTTPS ์‘๋‹ต์„ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ €์žฅํ•˜์—ฌ ์ดํ›„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด OkHttpClient ๋ฅผ ์„ค์ •ํ•  ๋•Œ Cache ๋ฅผ ์„ค์ •ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

val cacheSize = 50L * 1024L * 1024L // 50 MiB
val cache = Cache(File(application.cacheDir, "http_cache"), cacheSize)

val client = OkHttpClient.Builder()
    .cache(cache)
    .build()

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด OkHttp๋Š” ์„œ๋ฒ„์˜ Cache-Control ํ—ค๋”๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‘๋‹ต์„ ์บ์‹œํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„๊ฐ€ ํ•ด๋‹น ํ—ค๋”๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹œ ๋™์ž‘์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฆ‰, ๋„คํŠธ์›Œํฌ์—์„œ ๋ฐ›์€ ์‘๋‹ต์ด ์บ์‹œ ๊ฐ€๋Šฅํ•˜๋ฉด ์ด๋ฅผ ์ €์žฅํ•˜์—ฌ ์ดํ›„ ๋™์ผํ•œ ์š”์ฒญ ์‹œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.


ConnectInterceptor

 * Opens a connection to the target server and proceeds to the next interceptor. The network might
 * be used for the returned response, or to validate a cached response with a conditional GET.

์„œ๋ฒ„์™€ ์‹ค์ œ ์†Œ์ผ“ ์—ฐ๊ฒฐ(TCP/HTTPS)ย ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค

์ฆ‰, ์‹ค์ œ๋กœ ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์นœ๊ตฌ์ž…๋‹ˆ๋‹ค.

์ด ์นœ๊ตฌ๊ฐ€ ์—†์œผ๋ฉด ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐ์ด ์•ˆ๋ฉ๋‹ˆ๋‹ค.


client.networkInterceptors(๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ)

ํด๋ผ์ด์–ธํŠธ์—์„œ ์ถ”๊ฐ€ํ•˜๋Š” ๋„คํŠธ์›Œํฌ ์ˆ˜์ค€์˜ ์ธํ„ฐ์…‰ํ„ฐ(์›น ์†Œ์ผ“์ด ์•„๋‹Œ๊ฒฝ์šฐ)


CallServerInterceptor

/* This is the last interceptor in the chain. It makes a network call to the server. /

์ตœ์ข…์ ์œผ๋กœ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ›๋Š” ์—ญํ™œ์„ ํ•ฉ๋‹ˆ๋‹ค.

ย CallServerInterceptor๋ฅผ ๊ฑฐ์ณย ์ง„์งœ HTTP ํ†ต์‹ ์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

1.	writeRequestHeaders: ์š”์ฒญ ํ—ค๋” ์ „์†ก
2.	writeTo: ์š”์ฒญ ๋ฐ”๋”” ์ „์†ก (POST, PUT ๋“ฑ)
3.	finishRequest: ์š”์ฒญ ์ข…๋ฃŒ
4.	readResponseHeaders: ์‘๋‹ต ํ—ค๋” ์ˆ˜์‹ 
5.	openResponseBody: ์‘๋‹ต ๋ฐ”๋”” ์ˆ˜์‹ 
6.	์‘๋‹ต ๊ฐ์ฒด Response ๋นŒ๋“œ ํ›„ ๋ฐ˜ํ™˜

chain.proceed(request)

์œ„์—์„œ Intercepter ๋“ค์˜ ์ข…๋ฅ˜์™€ ์—ญํ™œ์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ์–ด๋–ป๊ฒŒ ์ด๋Ÿฐ ํ˜•ํƒœ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋˜๋Š”๊ฑธ๊นŒ์š”?

์ด๋Ÿฌํ•œ ์—ญํ™œ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” RealInterceptorChain ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

class RealInterceptorChain(
  internal val call: RealCall,
  private val interceptors: List<Interceptor>,
  private val index: Int,
  internal val exchange: Exchange?,
  internal val request: Request,
  internal val connectTimeoutMillis: Int,
  internal val readTimeoutMillis: Int,
  internal val writeTimeoutMillis: Int,
) : Interceptor.Chain {

}

//RealCall.kt
val chain = RealInterceptorChain(...)
	  val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)
 try {
      val response = chain.proceed(originalRequest)
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }

์šฐ์„  Interceptor.Chain interface๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

RealCall ํŒŒ์ผ์—์„œ๋Š” chain์„ RealInterceptorChain(...) ์œผ๋กœ ์ƒ์„ฑํ•ด์„œ chain.proceed ์„ ์‹คํ–‰ํ•œ response๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์–ด๋–ป๊ฒŒ RealInterceptorChain ์ด ๋™์ž‘ํ•˜๋Š”์ง€ ํ•œ๋ฒˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

RealInterceptorChain ์€ Interceptor.Chain ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—

override fun proceed(request: Request): Response ๋ฅผ ๊ผญ override ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ RealCall ํŒŒ์ผ์—์„œ .proceed๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ตฌํ˜„๋ถ€๊ฐ€ ์ด๊ณณ์ด๊ธฐ๋„ ํ•˜๊ตฌ์š”.

์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

private var calls: Int = 0

class RealInterceptorChain(
  internal val call: RealCall,
  private val interceptors: List<Interceptor>,
  private val index: Int,
  internal val exchange: Exchange?,
  internal val request: Request,
  internal val connectTimeoutMillis: Int,
  internal val readTimeoutMillis: Int,
  internal val writeTimeoutMillis: Int,
) : Interceptor.Chain {
  private var calls: Int = 0

  internal fun copy(
    index: Int = this.index,
    exchange: Exchange? = this.exchange,
    request: Request = this.request,
    connectTimeoutMillis: Int = this.connectTimeoutMillis,
    readTimeoutMillis: Int = this.readTimeoutMillis,
    writeTimeoutMillis: Int = this.writeTimeoutMillis,
  ) = RealInterceptorChain(
    call,
    interceptors,
    index,
    exchange,
    request,
    connectTimeoutMillis,
    readTimeoutMillis,
    writeTimeoutMillis,
  )
@Throws(IOException::class)
  override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++

    if (exchange != null) {
      check(exchange.finder.routePlanner.sameHostAndPort(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }

    // Call the next interceptor in the chain.
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    val response =
      interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null",
      )

    if (exchange != null) {
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }

    return response
  }
 }

์šฐ์„  ๊ฐ ๋ณ€์ˆ˜, ํ•จ์ˆ˜๋“ค์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • exchange: Exchange?
    • ๋„คํŠธ์›Œํฌ ๊ตํ™˜(์‹ค์ œ ์†Œ์ผ“์ด๋‚˜ HTTP ์ŠคํŠธ๋ฆผ ๋“ฑ)์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ ๋‹จ๊ณ„์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
      ๋งŒ์•ฝ ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ ์™ธ์˜ ๋‹จ๊ณ„๋ผ๋ฉด null์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • calls
    • ์ „์ฒด HTTP ์š”์ฒญ/์‘๋‹ต ์‚ฌ์ดํด์„ ๋Œ€ํ‘œํ•˜๋Š” RealCall ์ธ์Šคํ„ด์Šค๋กœ, ํ˜ธ์ถœ๊ณผ ๊ด€๋ จ๋œ ์ „๋ฐ˜์  ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค
  • index: Int
    • ํ˜„์žฌ ์ฒด์ธ์—์„œ ์‹คํ–‰ํ•  ์ธํ„ฐ์…‰ํ„ฐ์˜ ์ธ๋ฑ์Šค์ž…๋‹ˆ๋‹ค.
      ์ด ๊ฐ’์ด ์ฆ๊ฐ€ํ•˜๋ฉด์„œ ์ธํ„ฐ์…‰ํ„ฐ๋“ค์ด ์ˆœ์ฐจ์ ์œผ๋กœ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  • request: Request
    • ํ˜„์žฌ ์ฒ˜๋ฆฌ ์ค‘์ธ HTTP ์š”์ฒญ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

์ด์ œ proceed ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ์ฒด์ธ ์ธ๋ฑ์Šค ๊ฒ€์ฆ

    check(index < interceptors.size)
    • ํ˜„์žฌ index๊ฐ€ ์ „์ฒด ์ธํ„ฐ์…‰ํ„ฐ ๋ฆฌ์ŠคํŠธ์˜ ํฌ๊ธฐ๋ณด๋‹ค ์ž‘์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
    • ๋งŒ์•ฝ index๊ฐ€ ์ธํ„ฐ์…‰ํ„ฐ์˜ ๊ฐœ์ˆ˜๋ฅผ ์ดˆ๊ณผํ–ˆ๋‹ค๋ฉด ๋…ผ๋ฆฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค
  2. ํ˜ธ์ถœ ์นด์šดํŠธ ์ฆ๊ฐ€

    calls++
    • ํ˜„์žฌ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ proceed()๋ฅผ ํ˜ธ์ถœํ•œ ํšŸ์ˆ˜๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
    • ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ ํ•œ ๋ฒˆ๋งŒ proceed()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ด๋ฅผ ๊ฒ€์‚ฌํ•  ๊ธฐ๋ฐ˜์ด ๋ฉ๋‹ˆ๋‹ค.
  3. ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๊ฒ€์ฆ (exchange๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ)

    if (exchange != null) {
      check(exchange.finder.routePlanner.sameHostAndPort(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }
    • ํ˜„์žฌ ์š”์ฒญ(request)์ด ์ด์ „์— ์‚ฌ์šฉํ•œ ๋ผ์šฐํŠธ ํ”Œ๋ž˜๋„ˆ์˜ ํ˜ธ์ŠคํŠธ์™€ ํฌํŠธ์™€ ๋™์ผํ•œ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    • ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์š”์ฒญ์˜ ๋ชฉ์ ์ง€๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
    • ํ˜„์žฌ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ย proceed()๋ฅผย ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ–ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  4. ๋‹ค์Œ ์ฒด์ธ ์ค€๋น„

    val next = copy(index = index + 1, request = request)
    • ํ˜„์žฌ ์ฒด์ธ ์ƒํƒœ๋ฅผ ๋ณต์‚ฌํ•˜๋ฉด์„œ ์ธ๋ฑ์Šค๋ฅผ 1 ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
    • ์ƒˆ ๊ฐ์ฒด(next)๋Š” ๋‹ค์Œ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ์ฒ˜๋ฆฌํ•  ์ฒด์ธ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค
    • ์—ฌ๊ธฐ์„œ copy๋ฅผ ํ•˜๋Š” ์ด์œ ๋Š” ย ๋ถˆ๋ณ€์„ฑ(immutability) ๋ฐ ์ƒํƒœ ๋ณดํ˜ธ๋ฅผ ์œ„ํ•ด์„œ ์ž…๋‹ˆ๋‹ค.

    ๊ฐ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ์›๋ณธ ์ฒด์ธ์˜ ์ƒํƒœ(์˜ˆ๋ฅผ ๋“ค์–ด, ํ˜„์žฌ ์ธ๋ฑ์Šค, ์š”์ฒญ ์ •๋ณด, ํƒ€์ž„์•„์›ƒ ๊ฐ’ ๋“ฑ)๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    ์ด๋ฅผ ์œ„ํ•ด copy()๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด ์ฒด์ธ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์ด์ „ ์ฒด์ธ์˜ ์ƒํƒœ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ํ•œ ์ธํ„ฐ์…‰ํ„ฐ์—์„œ ๋ฐœ์ƒํ•œ ๋ณ€๊ฒฝ์ด ๋‹ค๋ฅธ ์ธํ„ฐ์…‰ํ„ฐ์— ๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  5. ์ธํ„ฐ์…‰ํ„ฐ ์„ ํƒ

    val interceptor = interceptors[index]
    • ํ˜„์žฌ ์ธ๋ฑ์Šค์— ํ•ด๋‹นํ•˜๋Š” ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
  6. ์ธํ„ฐ์…‰ํ„ฐ(index +1) ์‹คํ–‰

    @Suppress("USELESS_ELVIS")
    val response =
      interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null",
      )
    • ์„ ํƒ๋œ ์ธํ„ฐ์…‰ํ„ฐ์˜ intercept() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด์„œ, ์ƒˆ ์ฒด์ธ(next)์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
    • ์ธํ„ฐ์…‰ํ„ฐ์˜ ๊ฒฐ๊ณผ๋กœ Response ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.
  7. ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ์— ๋Œ€ํ•œ ํ›„์ฒ˜๋ฆฌ ๊ฒ€์ฆ (exchange๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ)

    if (exchange != null) {
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }
    • ๋งŒ์•ฝ ํ˜„์žฌ๊ฐ€ ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ ๊ด€๋ จ ์ฒด์ธ์ด๋ผ๋ฉด, ๋‹ค์Œ ์ฒด์ธ(next)์—์„œ proceed()๊ฐ€ ์ •ํ™•ํžˆ ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๋‹ค์‹œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  8. ์‘๋‹ต ๋ฐ˜ํ™˜

    return response
    • ์ตœ์ข…์ ์œผ๋กœ ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ด๋Š” ์ฒด์ธ์„ ๊ฑฐ์ณ ์ตœ์ข…์ ์œผ๋กœ ์„œ๋ฒ„์—์„œ ๋ฐ›์€ ์‘๋‹ต์ผ ์ˆ˜๋„ ์žˆ๊ณ , ์ค‘๊ฐ„ ์ธํ„ฐ์…‰ํ„ฐ์—์„œ ๋ณ€ํ™˜๋œ ์‘๋‹ต์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ž์„ธํžˆ ๋‹ค๋ฃจ์ง„ ์•Š์•˜์ง€๋งŒ RealInterceptorChain ํŒŒ์ผ์—์„œ

withConnectTimeout, withReadTimeout, withWriteTimeout ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํƒ€์ž„์•„์›ƒ์„ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋„คํŠธ์›Œํฌ ์ธํ„ฐ์…‰ํ„ฐ์—์„œ๋Š” ์ด Timeout์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋„๋ก ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.

๊ณต๋ถ€๋ฅผ ํ•˜๋ฉด์„œ ์™œ ์ด๋ฆ„์„ Chain ์ด๋ผ๊ณ  ๋„ค์ด๋ฐ์„ ํ•œ ์ง€ ์•Œ๊ฒŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ž˜๋ชป๋œ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ์•Œ๋ ค์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!!๐Ÿ™

๋„คํŠธ์›Œํ‚น์ด ์ด๋ฃจ์–ด์ง€๋Š” ๊ณผ์ •์„ ๋„์‹ํ™”ํ•œ ๊ทธ๋ฆผ์œผ๋กœ ๋งˆ๋ฌด๋ฆฌ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!


์š”์ฒญ

์‘๋‹ต

profile
์˜ค๋Š˜ ํ•˜๋ฃจ๋„ ํ™”์ดํŒ…

0๊ฐœ์˜ ๋Œ“๊ธ€