Alamofire 5 RequestInterceptor,如何在不使用.validate()进行令牌刷新的情况下重试请求?

时间:2019-09-18 19:57:35

标签: swift alamofire

我正在使用alamofire 5(rc3)在我的应用程序中重写网络,如果由于JWT令牌过期而导致请求失败,我将尝试重试该请求,如果我简单地进行标记,就可以使其正常工作Setting AGSMapView(frame: .zero)到请求上,表示API 401响应导致请求“失败”,然后传递给.validate(),但是每隔400-499个请求,我的API都会以相同的形式返回数据,并且RequestRetrier是有用的,但是通过使用message,它会丢弃.decodeResponse()给出的有用的对象标识。

.validate()
{
    "data": null,
    "message": "Token has expired",
    "status": "error"
    /* has 401 error code */
}

在网络管理器中调用我的API的示例函数如下所示,会话只是连接了网络拦截器(并且可以正常工作)的普通会话。

典型的API调用函数如下所示:

class NetworkInterceptor: RequestInterceptor {

    // MARK: - RequestAdapter
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        print("adapting")
        var adaptedRequest = urlRequest
        let token = NetworkService.sharedInstance.authToken
        adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        completion(.success(adaptedRequest))
    }


    // MARK: - RequestRetrier
    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        if let response = request.task?.response as? HTTPURLResponse, let WWWheader = response.allHeaderFields["Www-Authenticate"] as? String, response.statusCode == 401, WWWheader == "Bearer realm=\"everywhere\"" {
            print("Refreshing token for retry...")
            NetworkService.sharedInstance.refreshTokens { (success, _, _) in
                print("Refreshed token, retrying request")
                completion(.retry)
            }
        } else {
            completion(.doNotRetry)
        }
    }

}

您可以看到,如果正文的“成功”键为false,则在case .success(let apiResponse)内返回某种形式的错误响应。但是,这意味着该请求永远不会放入requestRetrier

但是,如果我使用.validate()

func sendMove(id: Int, move: Move, completion: @escaping APICompletionHandler<GameRender>) {
    session.request(APIRouter.sendMove(id: id, move: move)).responseDecodable { (response: DataResponse<APIResponse<GameRender>, AFError>) in
        switch response.result {
        case .success(let apiResponse):
            if apiResponse.status == .success {
                // Data from API and success
                completion(true, apiResponse.data, apiResponse.message)
            } else {
                // Data from API but not success
                completion(false, apiResponse.data, apiResponse.message)
            }
        case .failure(let data):
            // Could not get anything from API
            completion(false, nil, data.localizedDescription)
        }
    }
}

现在您可以看到开关第一部分中的func sendMove(id: Int, move: Move, completion: @escaping APICompletionHandler<GameRender>) { session.request(APIRouter.sendMove(id: id, move: move)).validate().responseDecodable { (response: DataResponse<APIResponse<GameRender>, AFError>) in switch response.result { case .success(let apiResponse): if apiResponse.status == .success { // Data from API and success completion(true, apiResponse.data, apiResponse.message) } else { // Data from API but not success // NOW THIS NEVER RUNS completion(false, apiResponse.data, apiResponse.message) } case .failure(let data): // Could not get anything from API completion(false, nil, data.localizedDescription) } } } 永远不会运行。这两种模式似乎是矛盾的,例如,是否有一种方法可以在解析后在特定的调用上调用重试。 else{}

1 个答案:

答案 0 :(得分:2)

从根本上讲,要触发重试,沿着Alamofire的请求路径的某些步骤必须抛出错误。像responseDecodable这样的响应处理程序将仅在请求期间未产生任何错误的情况下解析响应数据。对于所有无效的响应代码和响应validate(),使用Content-Type会产生错误。您最简单的选择是使用传递的闭包来自定义validate(),以仅在您要触发重试的情况下产生错误。然后,您的响应处理程序将始终解析其数据,并且您可以根据需要处理其他故障。

更高级的解决方案是编写自己的ResponseSerializer,当存在某些错误(但不是全部)时,解析响应数据。