이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.
작성 시점: 2018-05-28
Retrofit 의 기반이 되는 OKHttp 에는 Interceptor 란 기능이 있다.
일반적으로 Retrofit 를 사용할 때 Retrofit.create 로 생성한 서비스 객체를 이용해서 요청을 하는데, 그 통신 과정을 잡아서 일정한 행동을 처리하게 하는 것이다.
흔히 로그를 표시할 때 사용하는 Okhttp-logging-interceptor 도 이런 방식을 이용한다.
이 Interceptor 는 두 가지가 있는데, 첫번째는 이번에 사용할 Application Interceptor 이고, 나머지는 Network Interceptor 이다.
Network Interceptor 는 단순히 원격 소스에 대한 Interceptor 를 제공하는 반면 Application Interceptor 는 원격 소스 말고도 Cache 에 대한 Interceptor 도 제공한다.
Interceptor 를 구현하기 위해서는 Interceptor 라는 클래스를 상속할 필요가 있다.
해당 클래스에는 intercept 라는 메서드를 가지고 있는데, Interceptor.Chain 를 주고서 Response 를 얻는 식이다. 즉, 해당 Interceptor 에서 기존의 Request 를 기반으로 조작해서 새로운 Request 를 제작하는 형태라고 설명할 수 있다.
Interceptor 를 상속하는 클래스를 만들고, 기존의 Request 를 가지고 새 Request 를 만들어서 다시 Response 로 반환하는 코드는 다음과 같다.
class AddParamsInterceptor() : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val originalHttpUrl = original.url()
val request = original.newBuilder()
.url(originalHttpUrl.newBuilder().build())
.build()
return chain.proceed(request)
}
}
이번 글에서는 이 Interceptor 를 사용해서 Retrofit 를 거치는 모든 통신에 공통으로 들어가는 파라미터를 넣고자 한다. 즉, GET 에 들어갈 Query Parameter 와 POST 에 들어갈 Field Parameter 를 넣으면, Interceptor 를 통해 기존 파라미터에 파라미터를 추가하는 것이다.
GET 에 들어갈 Query Parameter 의 경우 HttpUrl, 즉 chain.request 의 반환형에서는 addQueryParameter 라는 다소 직관적인 인터페이스를 제공하지만, POST 의 경우에는 RequestBody, 즉 request.body() 의 반환형은 직관적인 인터페이스를 제공하지 않는다. 따라서 이 RequestBody 를 상속하는 또 다른 클래스를 만들어서 그 클래스가 기존 body 위에 새로운 내용을 삽입하도록 해야한다.
우선 Query Parameter 를 추가해보자.
val original = chain.request()
val originalHttpUrl = original.url()
val urlBuilder = originalHttpUrl.newBuilder()
for ((key, value) in mGETMap.entries) {
urlBuilder.addQueryParameter(key, value)
}
그 다음, Field Parameter 를 추가해보자.
if (original.body() != null) {
val requestBody = original.body()!!
val paramList = mPOSTMap.entries.map { "&" + it.key + "=" + it.value }
val paramLength = paramList.map { it.length }.sum()
val newRequestBody = object : RequestBody() {
override fun contentLength(): Long = requestBody.contentLength() + paramLength
override fun contentType(): MediaType? = requestBody.contentType()
@Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
requestBody.writeTo(sink)
for (param in paramList) {
sink.writeString(param, Charset.forName("UTF-8"))
}
}
}
requestBuilder.post(newRequestBody)
}
Query Parameter 와는 다르게 직접 query string 를 만들고, contentLength 등도 직접 다 추가해야 한다.
파라미터를 추가하는 Interceptor 를 쉽게 사용할 수 있게 Builder 등 코드를 더 추가한 것이 다음과 같다.
class AddParamsInterceptor(getMap: HashMap<String, String>, postMap: HashMap<String, String>) : Interceptor {
private var mGETMap = getMap
private var mPOSTMap = postMap
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val originalHttpUrl = original.url()
val urlBuilder = originalHttpUrl.newBuilder()
val requestBuilder = original.newBuilder()
for ((key, value) in mGETMap.entries) {
urlBuilder.addQueryParameter(key, value)
}
val httpUrl = urlBuilder.build()
if (original.body() != null) {
val requestBody = original.body()!!
val paramList = mPOSTMap.entries.map { "&" + it.key + "=" + it.value }
val paramLength = paramList.map { it.length }.sum()
val newRequestBody = object : RequestBody() {
override fun contentLength(): Long = requestBody.contentLength() + paramLength
override fun contentType(): MediaType? = requestBody.contentType()
@Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
requestBody.writeTo(sink)
for (param in paramList) {
sink.writeString(param, Charset.forName("UTF-8"))
}
}
}
requestBuilder.post(newRequestBody)
}
requestBuilder.url(httpUrl)
val request = requestBuilder.build()
return chain.proceed(request)
}
class Builder {
private val mGETMap = hashMapOf<String, String>()
private val mPOSTMap = hashMapOf<String, String>()
/**
* add (key, value) into additional common parameter with @Query annotation
*/
fun addQueryParameter(key: String, value: String) = this.apply { mGETMap[key] = value }
/**
* add (key, value) into additional common parameter with @Field annotation
*/
fun addFieldParameter(key: String, value: String) = this.apply { mPOSTMap[key] = value }
/**
* add (key, value) into additional common parameter with @Query, @Field annotation
*/
fun addParameter(key: String, value: String) = this.apply { addQueryParameter(key, value).addFieldParameter(key, value) }
/**
* Building AddParamsInterceptor
*/
fun build() = AddParamsInterceptor(mGETMap, mPOSTMap)
}
}
실제 사용하는 방법은 다음과 같다.
@Provides
public OkHttpClient provideClient(Interceptor interceptor) {
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.readTimeout(Config.getConfig().getTimeout(), TimeUnit.MILLISECONDS);
builder.connectTimeout(Config.getConfig().getConnectTimeout(), TimeUnit.MILLISECONDS);
builder.addInterceptor(interceptor);
AddParamsInterceptor addParamsInterceptor = new AddParamsInterceptor.Builder()
.addParameter("key", "abc")
.build();
builder.addInterceptor(addParamsInterceptor);
return builder.build();
}