글로 들어가기 앞서
이 글은 Square에서 제공하는 Interceptor 아티클을 번역한 것이며, 개인적인 주관에 의해 해석되고 살이 조금 덧붙여질 수 있습니다.
Interceptor란?
Interceptor는 네트워크 호출을 모니터링하거나, 고쳐쓰거나, 재시도할 수 있는 강력한 매커니즘입니다.
예를 들어 공통적으로 보내줘야할 헤더가 있을 경우 Interceptor를 통해 모든 네트워크 호출에 포함시킬 수 있습니다. (링크)
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
// 서버로 보내는 Request
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
// 서버로부터 받은 Response
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
chain.proceed가 interceptor 매커니즘에서 가장 핵심적인 부분입니다.
간단해 보이는 메소드이지만, 실상은 모든 HTTP 작업이 이뤄지며, request를 만족하는 response를 생성해냅니다.
만약 chain.proceed 메소드가 2번이상 불리게 될 경우 response body들은 닫히게 됩니다.
Interceptor들은 연결될 수 있습니다.
Compressing하는 인터셉터와 Checksumming하는 인터셉터가 동시에 존재할 경우를 가정해봅시다.
언제 compress되고 checksum할지 결정해야 합니다.
OkHttps는 순서대로 Interceptor들을 관리해줍니다.
Application Interceptors
Application Interceptor 또는 Network Interceptor들로 등록될 수 있습니다.
LoggingInterceptor를통해 그 차이를 예시로서 보여드리겠습니다.
OkHttpClient.Builder에 addInterceptor()를 통해 Application Interceptor를 등록합니다.
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
http://www.publicobject.com/helloworld.txt는 https로 redirect되고, Okhttp는 자동으로 redirect를 따라갑니다.
Interceptor는 한번만 불리고, redirected된 response를 가지는 chain.proceed()로 부터 response를 반환합니다.
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
response.request().url과 request.url()이 다르기 때문에, redirected된 것을 확인할 수 있습니다.
Network Interceptors
Network Interceptor를 등록하는 것도 비슷합니다. addInterceptor() 대신 addNetworkInterceptor를 사용합니다.
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
code를 실행시키면, intercpetor는 두번 실행됩니다. http://www.publicobject.com/helloworld.txt에 초기 요청을 위해 한 번, https://publicobject.com/helloworld.txt로 redirect하기 위해 또 다시 한번 실행됩니다.
INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
네트워크 요청은 response compression에 대한 지원을 알리기 위해 OkHttp에 의해 추가된 Accept-Encoding: gzip 헤더와 같은 데이터를 더 가지고 있습니다. Network Interceptor의 Chain에는 null이 아닌 연결(Connection)이 있으며, 이 연결을 사용하여 웹 서버에 연결할 때 사용된 IP 주소와 TLS 설정을 확인할 수 있습니다.
Application과 Network 중 어떤 것을 선택해야할까?
애플리케이션 인터셉터 (Application Interceptors)
- 리다이렉트나 재시도와 같은 중간 응답에 대해 신경 쓸 필요가 없습니다.
- HTTP 응답이 캐시에서 제공되더라도 항상 한 번 호출됩니다.
- 애플리케이션의 원래 의도를 관찰하며, If-None-Match와 같은 OkHttp에서 추가된 헤더에는 관여하지 않습니다.
- Chain.proceed() 메서드를 호출하지 않고 바로 반환할 수 있습니다.
- Chain.proceed() 메서드를 재시도하고 여러 번 호출할 수 있습니다.
- withConnectTimeout, withReadTimeout, withWriteTimeout을 사용하여 호출 시간 초과를 조정할 수 있습니다.
네트워크 인터셉터 (Network Interceptors)
- 리다이렉트나 재시도와 같은 중간 응답에 대해 작동할 수 있습니다.
- 네트워크를 우회하여 캐시된 응답에 대해서는 호출되지 않습니다.
- 데이터가 네트워크를 통해 전송되는 그대로의 데이터를 관찰합니다.
- 요청을 전달하는 Connection에 접근할 수 있습니다.
Rewriting Requests
인터셉터는 요청 헤더를 추가, 제거, 또는 교체할 수 있습니다. 또한, body가 있는 요청의 경우 body를 변환할 수도 있습니다. 예를 들어, Application Interceptor를 사용하여 요청 본문 압축을 추가할 수 있는데, 이는 연결하려는 웹 서버가 해당 기능을 지원하는 것으로 알려진 경우에 유용합니다.
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override public MediaType contentType() {
return body.contentType();
}
@Override public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
@Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
Rewriting Responses
대칭적으로, 인터셉터는 응답 헤더를 다시 작성하거나 응답 본문을 변환할 수도 있습니다. 하지만 이는 일반적으로 요청 헤더를 다시 작성하는 것보다 더 위험할 수 있는데, 그 이유는 웹 서버의 기대에 어긋날 수 있기 때문입니다!
만약 어려운 상황에 처해 있고 그로 인한 결과를 처리할 준비가 되어 있다면, 응답 헤더를 다시 작성하는 것은 문제를 해결하는 강력한 방법이 될 수 있습니다. 예를 들어, 서버의 잘못 구성된 Cache-Control 응답 헤더를 수정하여 더 나은 응답 캐싱을 가능하게 할 수 있습니다.
/** Dangerous interceptor that rewrites the server's cache-control header. */
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=60")
.build();
}
};
일반적으로 이러한 접근 방식은 웹 서버에서의 대응되는 수정과 함께 사용될 때 가장 효과적입니다!
'Android' 카테고리의 다른 글
OkHttp Authenticator (0) | 2024.09.05 |
---|---|
[Android Compose] enableEdgeToEdge()로 인한 시스템 영역 패딩 설정 방법 (WindowInsets) (0) | 2024.06.30 |
[Android] Compose onGloballyPositioned - bounds 속성들 (1) | 2024.06.30 |
[Android] Material3 Color 속성 정리 (1) | 2024.06.29 |
[Android] Compose 키보드 영역에 따른 패딩 적용 방법 (imePadding) (0) | 2024.06.27 |
댓글