๊ณต๋ถํ๋ฉฐ ์๋ฒ์์ 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 ๋ก ๊ด๋ฆฌ๋ฅผ ํ๊ณ ์๊ตฌ๋งโฆ
์ด์ ์ด๋ค ์ธํฐ์ ํฐ๋ค์ด ์๋์ง ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค.
ํด๋ผ์ด์ธํธ(Android)์์ ์ค์ ํ
์ ํ๋ฆฌ์ผ์ด์ ์ธํฐ์ ํฐ๋ค์ด ํด๋น๋ฉ๋๋ค.
// * This interceptor recovers from failures and follows redirects as necessary. It may throw an
// * [IOException] if the call was canceled.
์์ฒญ ์คํจ ๋ฐ ๋ฆฌ๋ค์ด๋ ํธ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํฉ๋๋ค.
์ด๋
์์ด ๋ฐ๋ก ์๋ฒ์์ 401์ ๋ด๋ ค์ฃผ๋ฉด Authenticator ๋ฅผ ์คํ์ํค๊ณ ์ฌ์์ฒญ์ ํ ์ ์๊ฒ ํด์ฃผ๋ ์์ฃผ ๊ณ ๋ง์ด ๋
์์
๋๋ค.

์ด ์น๊ตฌ๊ฐ ์ด๋ป๊ฒ 401์ ๋ฐ์ผ๋ฉด Authenticator ๋ฅผ ์คํ์ํค๊ณ ์ฌ์์ฒญ์ ๋ณด๋ด๋์ง๋ ๋ค์ ํฌ์คํ
์์ ๋ค๋ฃจ๋๋ก ํ๊ฒ ์ต๋๋ค.
/**
* 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) ํ์คํ
์์ฒญ ๋ฐ๋ ์ฒ๋ฆฌ
์๋ต(Response) ๋ณํ
/** 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 ํค๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ต์ ์บ์ํฉ๋๋ค. ์๋ฒ๊ฐ ํด๋น ํค๋๋ฅผ ์ ๊ณตํ์ง ์๋ ๊ฒฝ์ฐ, ํด๋ผ์ด์ธํธ ์ธก์์ ์ธํฐ์ ํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์บ์ ๋์์ ์ ์ดํ ์ ์์ต๋๋ค.
์ฆ, ๋คํธ์ํฌ์์ ๋ฐ์ ์๋ต์ด ์บ์ ๊ฐ๋ฅํ๋ฉด ์ด๋ฅผ ์ ์ฅํ์ฌ ์ดํ ๋์ผํ ์์ฒญ ์ ์ฌ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
* 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)ย ์ ์ค์ ํฉ๋๋ค
์ฆ, ์ค์ ๋ก ์๋ฒ์ ์ฐ๊ฒฐ์ ์ํํ๋ ์น๊ตฌ์ ๋๋ค.
์ด ์น๊ตฌ๊ฐ ์์ผ๋ฉด ์๋ฒ์ ์ฐ๊ฒฐ์ด ์๋ฉ๋๋ค.
ํด๋ผ์ด์ธํธ์์ ์ถ๊ฐํ๋ ๋คํธ์ํฌ ์์ค์ ์ธํฐ์ ํฐ(์น ์์ผ์ด ์๋๊ฒฝ์ฐ)
/* 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 ๋น๋ ํ ๋ฐํ
์์์ 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
}
}
์ฐ์ ๊ฐ ๋ณ์, ํจ์๋ค์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
์ด์ proceed ํจ์๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ณด๊ฒ ์ต๋๋ค.
์ฒด์ธ ์ธ๋ฑ์ค ๊ฒ์ฆ
check(index < interceptors.size)
ํธ์ถ ์นด์ดํธ ์ฆ๊ฐ
calls++
๋คํธ์ํฌ ์ธํฐ์ ํฐ์ ๋ํ ์ถ๊ฐ ๊ฒ์ฆ (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"
}
}
๋ค์ ์ฒด์ธ ์ค๋น
val next = copy(index = index + 1, request = request)
๊ฐ ์ธํฐ์
ํฐ๊ฐ ํธ์ถ๋ ๋๋ง๋ค ์๋ณธ ์ฒด์ธ์ ์ํ(์๋ฅผ ๋ค์ด, ํ์ฌ ์ธ๋ฑ์ค, ์์ฒญ ์ ๋ณด, ํ์์์ ๊ฐ ๋ฑ)๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋ฉด์ ์๋ก์ด ๋ณ๊ฒฝ๋ ๊ฐ์ ์ ์ฉํด์ผ ํฉ๋๋ค.
์ด๋ฅผ ์ํด copy()๋ฅผ ํตํด ์๋ก์ด ์ฒด์ธ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด, ์ด์ ์ฒด์ธ์ ์ํ์ ์ํฅ์ ์ฃผ์ง ์๊ณ ์์ ํ๊ฒ ์ํ๋ฅผ ์
๋ฐ์ดํธํ ์ ์์ต๋๋ค. ์ฆ, ํ ์ธํฐ์
ํฐ์์ ๋ฐ์ํ ๋ณ๊ฒฝ์ด ๋ค๋ฅธ ์ธํฐ์
ํฐ์ ๋ถ์ ์ ์ธ ์ํฅ์ ์ฃผ์ง ์๊ฒ ๋ฉ๋๋ค.
์ธํฐ์ ํฐ ์ ํ
val interceptor = interceptors[index]
์ธํฐ์ ํฐ(index +1) ์คํ
@Suppress("USELESS_ELVIS")
val response =
interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null",
)
๋คํธ์ํฌ ์ธํฐ์ ํฐ์ ๋ํ ํ์ฒ๋ฆฌ ๊ฒ์ฆ (exchange๊ฐ ์์ ๊ฒฝ์ฐ)
if (exchange != null) {
check(index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
}
์๋ต ๋ฐํ
return response
์์ธํ ๋ค๋ฃจ์ง ์์์ง๋ง RealInterceptorChain ํ์ผ์์
withConnectTimeout, withReadTimeout, withWriteTimeout ๋ฉ์๋๋ฅผ ํตํด ํ์์์์ ์กฐ์ ํ ์ ์์ต๋๋ค.
๋ํ ๋คํธ์ํฌ ์ธํฐ์ ํฐ์์๋ ์ด Timeout์ ๋ณ๊ฒฝํ ์ ์๋๋ก ์ ํํฉ๋๋ค.
๊ณต๋ถ๋ฅผ ํ๋ฉด์ ์ ์ด๋ฆ์ Chain ์ด๋ผ๊ณ ๋ค์ด๋ฐ์ ํ ์ง ์๊ฒ๋์์ต๋๋ค.
์๋ชป๋ ์ ๋ณด๊ฐ ์๋ค๋ฉด ์๋ ค์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค!!๐
๋คํธ์ํน์ด ์ด๋ฃจ์ด์ง๋ ๊ณผ์ ์ ๋์ํํ ๊ทธ๋ฆผ์ผ๋ก ๋ง๋ฌด๋ฆฌ ํ๊ฒ ์ต๋๋ค. ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค!
์์ฒญ

์๋ต
