使用 retryWhen 根据 http 错误代码更新令牌

Using retryWhen to update tokens based on http error code

我在 How to refresh oauth token using moya and rxswift 上找到了这个例子,我不得不稍微修改一下才能编译。此代码对我的场景有效 80%。它的问题是它将 运行 用于所有 http 错误,而不仅仅是 401 错误。我想要的是将所有其他 http 错误作为错误传递,这样我就可以在其他地方处理它们,而不是在这里吞下它们。

使用此代码,如果我得到一个 HttpStatus 500,它会 运行 验证码 3 次,这显然不是我想要的。

我试图更改此代码以仅处理 401 错误,但似乎无论我做什么我都无法编译代码。它总是抱怨错误的 return 类型,"Cannot convert return expression of type Observable<Response> to return type Observable<Response>" 这对我来说毫无意义..

我想要的:处理 401,但在出现所有其他错误时停止

import RxSwift
import KeychainAccess
import Moya

public extension ObservableType where E == Response {

  /// Tries to refresh auth token on 401 errors and retry the request.
  /// If the refresh fails, the signal errors.
  public func retryWithAuthIfNeeded() -> Observable<E> {
    return self.retryWhen {
      (e: Observable<ErrorType>) in
      return Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: {  })
        .flatMap { i in
          return AuthProvider.sharedInstance.request(
            .LoginFacebookUser(
              accessToken: AuthenticationManager.defaultInstance().getLoginTokenFromKeyChain(),
              useFaceBookLogin: AuthenticationManager.defaultInstance().isFacebookLogin())
            )
            .filterSuccessfulStatusCodes()
            .mapObject(Accesstoken.self)
            .catchError {
              error in
              log.debug("ReAuth error: \(error)")
              if case Error.StatusCode(let response) = error {
                if response.statusCode == 401 {
                  // Force logout after failed attempt
                  log.debug("401:, force user logout")
                  NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
                }
              }
              return Observable.error(error)
            }.flatMapLatest({
              token -> Observable<Accesstoken> in
              AuthenticationManager.defaultInstance().storeServiceTokenInKeychain(token)
              return Observable.just(token)
            })
      }
    }
  }
}

编译错误

哪一行有编译错误?在我看来,应该是这一行:

.catchError {
    error in
    //...
    return Observable.error(error)  // is this the line causing the compilation error?
}

如果是这样,可能是因为 catchError 期望块 return 一个 Observable<Response> 可以在出现错误的情况下继续,而不是 Observable<ErrorType>.

在任何一种情况下,它都有助于用更多类型注释您的代码,以便您可以查明此类问题,并帮助 Swift 编译器,后者通常无法解决此类问题在其自己的。所以像这样的事情会对你有所帮助:

.catchError {
    error -> Observable<Response> in
    //...
    return Observable.error(error)  // Swift should have a more accurate and helpful error message here now
}

请注意,我只是向您展示错误是什么以及如何 Xcode 为您提供更好的错误消息。您尝试的 return 仍然不正确。

仅在 401

重试

我不确定为什么您希望此代码以不同方式对待 401(除了发布到通知中心和日志记录)。事实上,您正在捕获错误,但您总是 returning 一个 Observable 并在末尾有一个 Error 事件 (return Observable.error(error)),所以它会永不重试。

要让 401 重试,您应该 return 来自 retryWhen 块的 Observable,它将发送一个 Next 事件(表示你想重试)。对于所有其他状态代码,Observable 应该发送一个 Error(正如您当前所做的那样),这将表示您 不想 重试,并且您希望传播错误。

所以像这样:

.retryWhen { errorObservable -> Observable<ErrorType> in
    log.debug("ReAuth error: \(error)")
    if case Error.StatusCode(let response) = error where response.statusCode == 401 {
        log.debug("401:, force user logout")
        NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
        // If `401`, then return the `Observable<ErrorType>` which was given to us
        // It will emit a `.Next<ErrorType>`
        // Since it is a `.Next` event, `retryWhen` will retry.
        return errorObservable
    }
    else {
        // If not `401`, then `flatMap` the `Observable<ErrorType>` which
        // is about to emit a `.Next<ErrorType>` into
        // an `Observable<ErrorType>` which will instead emit a `.Error<ErrorType>`.
        // Since it is an `.Error` event, `retryWhen` will *not* retry.
        // Instead, it will propagate the error.
        return errorObservable.flatMap { Observable.error([=12=]) }
    }
}

当您 catchError 时,如果不是 401 错误,那么您只需 throw 错误。这会将错误发送到管道。

有一个不使用 Observable 的不同解决方案来解决这个问题。 它是在纯 RxSwift 上编写的,returns 失败时的经典错误。

The easy way to refresh session token of Auth0 with RxSwift and Moya

该解决方案的主要优点是它可以很容易地应用于类似于 Auth0 的不同服务,允许在移动应用程序中对用户进行身份验证。