¿Qué te permiten hacer realmente los interceptores de OkHttp, y cómo es una cadena de interceptores real?
Los interceptores son el único punto donde puedes ver y reescribir cada petición y respuesta que maneja OkHttp — headers de auth, logging, reintentos y cacheo viven todos ahí.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Qué te permiten hacer realmente los interceptores de OkHttp, y cómo es una cadena de interceptores real?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#El interceptor es solo un eslabón en una cadena de responsabilidad
Cada petición de OkHttp viaja a través de una cadena de objetos Interceptor antes de llegar a la red, y cada respuesta viaja de vuelta por esa misma cadena en sentido inverso. Cada interceptor implementa un único método: intercept(chain: Interceptor.Chain): Response. Dentro de él puedes inspeccionar o reescribir el Request saliente, llamar a chain.proceed(request) para pasarle el control al siguiente eslabón, y luego inspeccionar o reescribir el Response que regresa antes de devolverlo tú mismo. Esa única llamada a proceed es todo el mecanismo — todo lo que OkHttp te permite hacer (headers de auth, logging, reintentos, gzip, cacheo) está construido encima de ella.
class AuthInterceptor(private val tokenStore: TokenStore) : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val original = chain.request()val authenticated = original.newBuilder().header("Authorization", "Bearer ${tokenStore.accessToken}").build()return chain.proceed(authenticated)}}
#Interceptores de aplicación vs. interceptores de red
OkHttp te da dos puntos de enganche, y la diferencia es una pregunta real de entrevista, no un dato curioso. addInterceptor() registra un interceptor de aplicación: corre exactamente una vez por llamada visible para el usuario, nunca ve las redirecciones ni los reintentos que ocurren por debajo, y no verá una respuesta que OkHttp sirvió directamente desde su caché en disco sin tocar la red. addNetworkInterceptor() registra un interceptor de red: se sienta justo al lado del socket, corre una vez por cada petición de red real (así que sí ve cada redirección y reintento como un paso separado), ve los bytes comprimidos en el cable, y se omite por completo cuando una respuesta cacheada satisface la llamada sin un round trip de red.
val client = OkHttpClient.Builder()// Interceptores de aplicación: ven la llamada lógica, no el cable..addInterceptor(AuthInterceptor(tokenStore)).addInterceptor(HttpLoggingInterceptor().apply {level = HttpLoggingInterceptor.Level.BASIC})// Interceptor de red: ve la petición/respuesta real en el cable,// incluidas las que la caché de arriba nunca llega a tocar..addNetworkInterceptor(StethoInterceptor()).cache(Cache(File(context.cacheDir, "http_cache"), 20L * 1024 * 1024)).build()
Si quieres agregar un header a cada intento de red real (por ejemplo, para una CDN que inspecciona el tráfico crudo del cable), usa un interceptor de red. Si quieres agregar un header Authorization a cada llamada lógica que hace la app sin importar el cacheo, un interceptor de aplicación es más simple y además correcto.
#Una cadena real: auth, refresh y logging juntos
La cadena que aparece en casi toda app en producción combina un interceptor que adjunta el token actual, un Authenticator (un gancho de OkHttp relacionado pero distinto, que se llama específicamente cuando una respuesta regresa con 401) que refresca el token y reintenta, y un interceptor de logging colocado al final para que registre lo que realmente viajó por el cable, tokens incluidos solo en builds de debug.
class TokenAuthenticator(private val tokenStore: TokenStore) : Authenticator {private val lock = Any()override fun authenticate(route: Route?, response: Response): Request? {// Se rinde después de un reintento para que un token permanentemente// inválido no entre en un ciclo infinito.if (responseCount(response) >= 2) return nullsynchronized(lock) {// Otro hilo pudo haber refrescado ya mientras esperábamos el lock —// revisamos de nuevo antes de disparar una segunda llamada de red// para el mismo problema.val current = tokenStore.accessTokenval failedToken = response.request.header("Authorization")?.removePrefix("Bearer ")if (current != failedToken) {return response.request.newBuilder().header("Authorization", "Bearer $current").build()}val refreshed = tokenStore.refreshBlocking() ?: return nullreturn response.request.newBuilder().header("Authorization", "Bearer $refreshed").build()}}private fun responseCount(response: Response): Int {var result = 1var prior = response.priorResponsewhile (prior != null) { result++; prior = prior.priorResponse }return result}}val client = OkHttpClient.Builder().addInterceptor(AuthInterceptor(tokenStore)).authenticator(TokenAuthenticator(tokenStore)).addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }).build()
El bloque synchronized importa más de lo que parece: sin él, diez peticiones que reciben un 401 en el mismo instante disparan cada una su propia llamada de refresh, saturando tu endpoint de auth y compitiendo entre sí por cuál token gana. El lock, más la revisión de si otro hilo ya refrescó mientras esperabas, convierte diez llamadas de refresh en una sola.
#El orden es una pila, no una lista
Los interceptores agregados con addInterceptor corren en el orden en que los agregas en el camino de ida, y en orden inverso en el camino de vuelta — el primero que agregas es el envoltorio más externo, el último que agregas es el más cercano a la llamada real. Si tu interceptor de logging necesita registrar los headers finales que OkHttp envía (incluidos los que un interceptor anterior agregó), tiene que agregarse después de ese interceptor, no antes.
🧠 Check your understanding
0/1 · 0/1 answered1. Agregas un interceptor con addNetworkInterceptor() para registrar cada header realmente enviado en el cable. Una petición se sirve por completo desde la caché en disco de OkHttp sin ningún acceso a la red. ¿Corre tu interceptor de red?