Alamofire 5 调整和重试请求

Posted

技术标签:

【中文标题】Alamofire 5 调整和重试请求【英文标题】:Alamofire 5 Adapting and Retrying Requests 【发布时间】:2019-03-19 12:13:54 【问题描述】:

我正在尝试使用 Alamofire 5.0.0-beta.3 实现我的 OAuth2 流程。正如我所看到的,文档仍然适用于 Alamofire 4,如 github 页面中所述。

我正在尝试按照 Alamofire 4 的文档制作 Oauth2 处理程序。随着类名的更改,我在制作它时完全迷失了方向。

这是我正在关注的代码:

class OAuth2Handler: RequestAdapter, RequestRetrier 
    private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void

    private let sessionManager: SessionManager = 
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders

        return SessionManager(configuration: configuration)
    ()

    private let lock = NSLock()

    private var clientID: String
    private var baseURLString: String
    private var accessToken: String
    private var refreshToken: String

    private var isRefreshing = false
    private var requestsToRetry: [RequestRetryCompletion] = []

    // MARK: - Initialization

    public init(clientID: String, baseURLString: String, accessToken: String, refreshToken: String) 
        self.clientID = clientID
        self.baseURLString = baseURLString
        self.accessToken = accessToken
        self.refreshToken = refreshToken
    

    // MARK: - RequestAdapter

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest 
        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) 
            var urlRequest = urlRequest
            urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
            return urlRequest
        

        return urlRequest
    

    // MARK: - RequestRetrier

    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) 
        lock.lock() ; defer  lock.unlock() 

        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 
            requestsToRetry.append(completion)

            if !isRefreshing 
                refreshTokens  [weak self] succeeded, accessToken, refreshToken in
                    guard let strongSelf = self else  return 

                    strongSelf.lock.lock() ; defer  strongSelf.lock.unlock() 

                    if let accessToken = accessToken, let refreshToken = refreshToken 
                        strongSelf.accessToken = accessToken
                        strongSelf.refreshToken = refreshToken
                    

                    strongSelf.requestsToRetry.forEach  $0(succeeded, 0.0) 
                    strongSelf.requestsToRetry.removeAll()
                
            
         else 
            completion(false, 0.0)
        
    

    // MARK: - Private - Refresh Tokens

    private func refreshTokens(completion: @escaping RefreshCompletion) 
        guard !isRefreshing else  return 

        isRefreshing = true

        let urlString = "\(baseURLString)/oauth2/token"

        let parameters: [String: Any] = [
            "access_token": accessToken,
            "refresh_token": refreshToken,
            "client_id": clientID,
            "grant_type": "refresh_token"
        ]

        sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
            .responseJSON  [weak self] response in
                guard let strongSelf = self else  return 

                if 
                    let json = response.result.value as? [String: Any], 
                    let accessToken = json["access_token"] as? String, 
                    let refreshToken = json["refresh_token"] as? String 
                
                    completion(true, accessToken, refreshToken)
                 else 
                    completion(false, nil, nil)
                

                strongSelf.isRefreshing = false
            
    

这是 alamofire 4 的使用方法:

let baseURLString = "https://some.domain-behind-oauth2.com"

let oauthHandler = OAuth2Handler(
    clientID: "12345678",
    baseURLString: baseURLString,
    accessToken: "abcd1234",
    refreshToken: "ef56789a"
)

let sessionManager = SessionManager()
sessionManager.adapter = oauthHandler
sessionManager.retrier = oauthHandler

let urlString = "\(baseURLString)/some/endpoint"

sessionManager.request(urlString).validate().responseJSON  response in
    debugPrint(response)

这是我为实现这一点而遵循的链接。 https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests

【问题讨论】:

你能正常工作吗?我使用 RequestInterceptor 类尝试了建议的解决方案,但由于某种原因,我的请求被重试了很多次。 【参考方案1】:

看看这样的东西。

 struct EnvironmentInterceptor: RequestInterceptor 

    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) 
        var adaptedRequest = urlRequest
        guard let token = AtraqService.shared.user?.token.accessToken else 
        completion(.success(adaptedRequest))
        return
        
        adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        completion(.success(adaptedRequest))
     

     func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) 
        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 
        //get token
        
    

然后

        Session(configuration: configuration,  interceptor: EnvironmentInterceptor())

终于

request().validate().response...

【讨论】:

我试过了,效果很好。但是由于某种原因,该请求被重试了很多次。你有一个例子吗? Alamofire 文档示例甚至调用了某种 self.lock() 方法。 为什么你使用 'struct' 作为 EnvironmentInterceptor? 不要使用 responseJSON 或 responseDATA,只使用响应。这个链接解释了为什么重试调用了两次:)。 github.com/Alamofire/Alamofire/issues/2562 @ahsan 你能保存要重试的请求列表吗?【参考方案2】:

如果 Alamofire 5 出于某种原因没有拦截(调整或重试)您的请求,那么只需尝试检查委托签名 as described here.

func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (AFResult<URLRequest>) -> Void) 
    var modifiedURLRequest = urlRequest
    let apiToken = config.apiToken
    modifiedURLRequest.setValue(apiToken, forHTTPHeaderField: Constants.apiTokenHeader)
    completion(.success(modifiedURLRequest))


func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) 
    completion(.doNotRetry)

这就是区别(看签名):

func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: @escaping (AFResult<URLRequest>) -> Void) 
    var modifiedURLRequest = urlRequest
    let apiToken = config.apiToken
    modifiedURLRequest.setValue(apiToken, forHTTPHeaderField: Constants.apiTokenHeader)
    completion(.success(modifiedURLRequest))


func retry(_ request: Alamofire.Request, for session: Alamofire.Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) 
    completion(.doNotRetry)

【讨论】:

以上是关于Alamofire 5 调整和重试请求的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Alamofire 重试请求?

修改有效载荷的 Alamofire 重试请求

Alamofire 重试请求 - 反应方式

为啥我无法从 Alamofire 获取请求结果

trigger.io 如何处理文件上传请求中的带宽限制、超时和重试?

Alamofire 请求大约需要 10 秒