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