HTTP FAILED: java.net.ProtocolException: Too many follow-up requests: 21

leeeha·2024년 11월 17일

Trouble Shooting

목록 보기
2/5
post-thumbnail

LoveMarker 프로젝트를 개발하며 발생했던 이슈들을 기록합니다!

💥 어떤 문제가 발생했나요?

액세스 토큰 재발급 API를 호출했는데, 21번이나 중복으로 요청되는 리다이렉션 문제가 발생했다.

HTTP FAILED: java.net.ProtocolException: Too many follow-up requests: 21

🤷‍♀️ 왜 발생했나요?

https://stackoverflow.com/questions/35132330/retrofit-too-many-follow-up-requests-21

위의 스택오버플로우 글을 통해 원인을 찾을 수 있었다.

요청 헤더에 토큰을 추가할 때, header가 아니라 addHeader 메서드를 사용해서 발생한 문제였다.

addHeader, header 메서드의 차이점은 다음과 같다.

addHeader

  • 기존 헤더 값을 유지한 채, 새로운 헤더 값을 추가한다.
  • 헤더 이름이 동일하지만, 여러 값을 허용해야 하는 경우 사용한다.
return response.request.newBuilder()
    .addHeader("accessToken", newAccessToken) 
    .build()
  • 같은 이름의 기존 헤더 값을 모두 제거한 뒤, 새로운 값을 설정한다.
  • 동일한 이름의 헤더가 있으면 덮어씌운다.
  • 일반적으로 Authorization과 같은 헤더는 하나의 값만 가져야 하므로 header를 사용하는 것이 적합하다.
return response.request.newBuilder()
    .header("accessToken", newAccessToken) 
    .build()

따라서, addHeader 메서드로 인해 헤더의 값이 중복되면, 서버가 이를 잘못 해석하여 유효하지 않은 요청으로 간주하거나, 반복적인 리다이렉션을 유발할 수 있다.

🤔 어떻게 해결했나요?

package com.capstone.lovemarker.core.network.authenticator

class LoveMarkerAuthenticator @Inject constructor(
    private val userPreferencesDataSource: UserPreferencesDataSource,
    private val reissueTokenService: ReissueTokenService,
    @ApplicationContext private val context: Context,
) : Authenticator {
    override fun authenticate(route: Route?, response: Response): Request? {
        if (response.code == CODE_TOKEN_EXPIRED) {
            val newAccessToken = runCatching {
                runBlocking {
                    reissueTokenService.getNewAccessToken(
                        refreshToken = userPreferencesDataSource.userData.first().refreshToken
                    )
                }.data.accessToken
            }.onSuccess { token ->
                runBlocking {
                    userPreferencesDataSource.updateAccessToken(token)
                }
            }.onFailure { throwable ->
                Timber.e("FAIL REISSUE TOKEN: ${throwable.message}")
                runBlocking {
                    userPreferencesDataSource.clear()
                }
                ProcessPhoenix.triggerRebirth(context)
            }.getOrThrow()

			// addHeader 대신에 header 사용 
            return response.request.newBuilder()
                .header("accessToken", newAccessToken) 
                .build() 
        }

        return null
    }

    companion object {
        const val CODE_TOKEN_EXPIRED = 401
    }
}

🙏 오늘의 교훈

  • addHeader() vs header() 차이점을 명확하게 알고 사용하자!
profile
습관이 될 때까지 📝

0개의 댓글