
토큰이 X 기간동안 파기된다면, 유저가 알아차리지 못하게 갱신해서 샤용해야한다.
이러기위해선, Requset시에 status code가 401일 경우에 토큰 갱신을 판단해야한다.
갱신 후 원래 요청하려던 Request를 다시 요청해야한다.
sol) Alamofire의 RequestInterceptor를 이용해서 해결 할 수 있다.

request -> (by URLRequestConvertible) -> URLRequestRequestInterceptor : URLReqeust가 만들어지면, Session과 연관된RequestInterceptor에 전달됨.
RequestInterceptor는 URLReqeust를 수정할 수 있다.(header니 인증정보,etc.. 가 필요하다면)
Session: 그런 다음. Session이 URLRequest를 서버에 보낸다. 만약, error가 발생한다면(server error or network error), error는 RequestInterceptor에 리턴됨.
RequestInterceptor의 retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) 가 retry 여부를 RetryResult를 completion인자로 보냄에 따라 retry여부 결정된다.
Session : RetryResult가 .retry 또는 retryWithDelay라면 Session은 자동으로 (토큰 or etc)수정된 URLRequest로 request를 재시도한다.
RetryResult가 .doNotRetry라면, 재시도않고 실패한다.
class RequestInterceptor: RequestInterceptor {
let retryLimit = 3
let retryDelay: TimeInterval = 2
var isRetrying = false
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
if ConnectorHelper.isEndpointAPIA(urlRequest.url?.absoluteString) {
urlRequest.setValue(LocalStorage.getTokenA(), forHTTPHeaderField: "authorization")
} else {
urlRequest.setValue(LocalStorage.getTokenB(), forHTTPHeaderField: ""authorization")
}
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
let response = request.task?.response as? HTTPURLResponse
if request.retryCount < self.retryLimit {
if let statusCode = response?.statusCode, statusCode == 401, !isRetrying {
self.isRetrying = true
self.determineError(error: error, completion: completion)
} else {
completion(.retryWithDelay(self.retryDelay))
}
} else {
NetworkClient.shared.session.cancelAllRequests()
completion(.doNotRetry)
}
}
private func determineError(error: Error, completion: @escaping (RetryResult) -> Void) {
if let afError = error as? AFError {
switch afError {
case .responseValidationFailed(let reason):
self.determineResponseValidationFailed(reason: reason, completion: completion)
default:
self.isRetrying = false
completion(.retryWithDelay(self.retryDelay))
}
}
}
private func determineResponseValidationFailed(reason: AFError.ResponseValidationFailureReason, completion: @escaping (RetryResult) -> Void) {
switch reason {
case .unacceptableStatusCode(let code):
switch code {
case AuthenticationAction.refreshTokenA.rawValue:
KeyApiConnector.refreshTokenA { _ in
self.isRetrying = false
completion(.retryWithDelay(self.retryDelay))
}
case AuthenticationAction.refreshTokenB.rawValue:
KeyApiConnector.refreshTokenB { _ in
self.isRetrying = false
completion(.retryWithDelay(self.retryDelay))
}
default: // AuthenticationAction.logout.rawValue
self.isRetrying = false
NetworkClient.shared.session.cancelAllRequests()
// Redirect to the login page
completion(.doNotRetry)
}
default:
self.isRetrying = false
completion(.retryWithDelay(self.retryDelay))
}
}
}
retry의 completion을 인자로 전달해서 처리할수 있다..!
첫 시도에서 RequestInterceptor를 알지못해서 토큰 갱신을 request에서 해줬다.
그랬더니 토큰갱신이 필요할 때 여러곳에서 Network API가 request될 때 토큰 갱신이 여러번 이뤄졌다!.
토큰 갱신은 필요할때 한번만 이뤄져야 한다. 불필요한 토큰 갱신 요청은 없어야한다.
이를 해결하기위해 RequestInterceptor를 사용해 retry를 했다~!