【问题标题】:Alamofire 5 RequestInterceptor, How to retry request without needing to use .validate() for token refresh?Alamofire 5 RequestInterceptor,如何重试请求而无需使用 .validate() 进行令牌刷新?
【发布时间】:2020-01-19 20:01:47
【问题描述】:

我正在使用 alamofire 5 (rc3) 在我的应用程序中重新编写网络,如果由于我的 JWT 令牌过期而导致请求失败,我将尝试重试请求,如果我简单地标记,我可以让它工作.validate() 到请求上,这意味着 API 401 响应导致请求“失败”,然后传递给 RequestRetrier,但是每隔一个 400-499 请求我的 API 以相同的形式返回数据,message 是有用,但通过使用 .validate() 它会丢弃 .decodeResponse() 提供的有用的对象化:

{
    "data": null,
    "message": "Token has expired",
    "status": "error"
    /* has 401 error code */
}
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)
        }
    }

}

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

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

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)
        }
    }
}

如果正文的“成功”键为假,您可以看到我在 case .success(let apiResponse) 中返回某种形式的错误响应。然而,这意味着请求永远不会被提交到 requestRetrier

如果我使用 .validate()

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{} 永远不会运行。这两种模式似乎是不一致的,有没有一种方法可以在特定调用上调用重试,例如解析后 if (token needs refreshing?) -&gt; retry this request

【问题讨论】:

  • 您想触发重试并将失败响应的数据保存在您的完成处理程序中吗?你为什么要这样做?
  • 我只需要在令牌过期时重试 401
  • 啊,validate 调用会将所有 400 多个状态代码都变成错误,因此如果您只希望 401s 或 401s 具有某些标头的初始故障,则需要对其进行自定义。否则,具有该范围代码的所有内容都将显示为 .failure 结果。
  • 是否有另一种更常见的模式来接受在失败部分转换的数据对象?
  • 我的意思是你可以通过让你的验证调用只捕获你想要的 401 来获得你想要的行为,允许所有其他响应正常传递给响应处理程序。也就是说,如果我正确理解您想要的内容。

标签: swift alamofire


【解决方案1】:

从根本上说,要触发重试,Alamofire 请求路径中的某些步骤必须引发错误。响应处理程序,如responseDecodable,只会在请求期间没有产生错误时解析响应数据。使用validate() 会为所有无效响应代码和响应Content-Types 产生错误。您最简单的选择是使用传递的闭包自定义validate(),以便仅在您想要触发重试的情况下产生错误。然后,您的响应处理程序将始终解析其数据,您可以根据需要处理其他故障。

更高级的解决方案是编写自己的ResponseSerializer,它会在出现某些错误时解析响应数据,但不是全部。

【讨论】:

  • 我们如何使用这种方法指定重试次数?
  • 我已经使用了 AF 5 测试中使用的方法来保持 retryCount 的变量,但是当我标记为 dontRetry 并再次验证时的问题被调用,它失败并且不重试但是我的实际操作API 完成处理程序不会调用,因为验证失败。那么我该如何处理这种情况。
猜你喜欢
  • 1970-01-01
  • 2021-09-02
  • 2021-12-14
  • 2017-12-12
  • 2018-11-07
  • 2012-10-02
  • 2021-11-26
  • 2020-06-02
  • 1970-01-01
相关资源
最近更新 更多