使用URLSession和CombineAPI取回401后如何重新登录



我是组合和URLSession的新手,我正试图在收到401错误后找到登录的方法。我的URLSession设置。

API错误:

enum APIError: Error {
case requestFailed
case jsonConversionFailure
case invalidData
case responseUnsuccessful
case jsonParsingFailure
case authorizationFailed

var localizedDescription: String{
switch self{
case .requestFailed: return "Request Failed"
case .invalidData: return "Invalid Data"
case .responseUnsuccessful: return "Response Unsuccessful"
case .jsonParsingFailure: return "JSON Parsing Failure"
case .jsonConversionFailure: return "JSON Conversion Failure"
case .authorizationFailed: return "Failed to login the user."
}
}
} 

CombinatAPI本身,我正试图在.catch或.tryCatch中捕获401,但事实并不像我想象的那么容易。

//1- A Protocol that has an URLSession and a function that returns a publisher.
protocol CombineAPI{
var session: URLSession { get}
// var authenticationFeed: AuthenticationFeed { get }

func execute<T>(_ request: URLRequest, decodingType: T.Type, queue: DispatchQueue, retries: Int) -> AnyPublisher<T, Error> where T: Decodable
//func reauthenticate<T>(_ request: URLRequest, decodingType: T.Type, queue: DispatchQueue, retries: Int) -> AnyPublisher<T, Error> where T: Decodable
}
//2 - Extending CombineAPI so we can have a default implementation.
extension CombineAPI {
func authenticationFeed() -> URLRequest{
return AuthenticationFeed.login(parameters: UserCredentials(userName: UserSettings.sharedInstance.getEmail(), password: UserSettings.sharedInstance.getPassword())).request
}

func execute<T>(_ request: URLRequest,
decodingType: T.Type,
queue: DispatchQueue = .main,
retries: Int = 0) -> AnyPublisher<T, Error> where T: Decodable{
return session.dataTaskPublisher(for: request)
.tryMap {
guard let response = $0.response as? HTTPURLResponse, response.statusCode == 200 else{
let response = $0.response as? HTTPURLResponse
if response?.statusCode == 401{
throw APIError.authorizationFailed
}
print(response!.statusCode)
throw APIError.responseUnsuccessful
}
//Return the data if everything is good
return $0.data
}
.catch(){ _ in
//Try to relogin here or in tryCatch block

}
//      .tryCatch { error in
//        if Error as? APIError == .authorizationFailed {
//          let subcription = self.callFunction().switchToLatest().flatMap { session.dataTaskPublisher(for: request)}.eraseToAnyPublisher()
//          return subcription
//        }else{
//          throw APIError.responseUnsuccessful
//        }
//      }
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: queue)
.retry(retries)
.eraseToAnyPublisher()
}

func reauthenticate<T>( decodingType: Token.Type, queue: DispatchQueue = .main,retries: Int = 2) -> AnyPublisher<T, Error> where T: Decodable{
return session.dataTaskPublisher(for: self.authenticationFeed())
.tryMap{
guard let response = $0.response as? HTTPURLResponse, response.statusCode == 200 else{
let response = $0.response as? HTTPURLResponse
if response?.statusCode == 401{
throw APIError.authorizationFailed
}
print(response!.statusCode)
throw APIError.responseUnsuccessful
}
//Return the data if everything is good
return $0.data
}
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: queue)
.retry(retries)
.eraseToAnyPublisher()
}


}

这是将创建URL请求本身的提要:

enum UserFeed{
case getUser(userId: Int)
}
extension UserFeed: Endpoint{
var base: String {
return "http://192.168.1.15:8080"
}

var path: String {
switch self{
case .getUser(let userId): return "/api/v1/User/(userId)"
}
}


var request: URLRequest{
let url = urlComponents.url!
var request = URLRequest(url: url)
switch self{
case .getUser(_):
request.httpMethod = CallType.get.rawValue
request.setValue("*/*", forHTTPHeaderField: "accept")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(token,forHTTPHeaderField:  "tokenheader")
print(token)
return request
}
} 
}

然后,您要创建的客户端本身将在您的viewModel中,因此您可以对该类型的数据进行web请求:

import Foundation
import Combine
final class UserClient: CombineAPI{
var authenticate = PassthroughSubject<Token, Error>()


var session: URLSession

init(configuration: URLSessionConfiguration){
self.session = URLSession(configuration: configuration)

}

convenience init(){
self.init(configuration: .default)
}

func getFeedUser(_ feedKind: UserFeed) -> AnyPublisher<User, Error>{
return execute(feedKind.request, decodingType: User.self, retries: 2)
}
}

我一直试图向我的authenticationClient发出一个新的请求,但它返回了一个不同的数据类型,所以ComineAPI不喜欢它。我不确定我应该怎么做,否则,在我必须进行身份验证或获得新令牌之前,它会很好地工作?任何帮助都将不胜感激,谢谢。

我只需要它来登录,这样我就可以将新令牌保存到用户设置中,然后继续执行它停止的请求。如果我无法获得新令牌,那么我会返回一个错误,让用户登录。

因此,如果我理解正确,您希望能够捕获错误401,并发送与您之前使用的API请求不同的请求。在这种情况下,你想按照你写的那样执行以下操作:

func execute<T>(_ request: URLRequest,
decodingType: T.Type,
queue: DispatchQueue = .main,
retries: Int = 0) -> AnyPublisher<T, APIError> where T: Decodable{
return session.dataTaskPublisher(for: request)
.mapError { error in
if error.statusCode == 401{
return APIError.authorizationFailed
}
return APIError.someOtherGenericError
}
.map{$0}
.decode(type: T.self, decoder: JSONDecoder())
.catch(){ error in
if error == .authorizationFailed {
return session.dataTaskPublisher(for: request) // a new URLRequest that will call for the token generation.
}
}
.receive(on: queue)
.eraseToAnyPublisher()
}

现在您可以使用函数catch了,您需要返回一个与您提供的值相同的发布者,这意味着如果您定义了您想要一个"用户";对象,则catch-new-publisher必须返回相同类型的对象。

如果你想让它更通用,你可以处理在.sink闭包中生成令牌的.catch请求,也可以创建一个特定于登录而不使用通用函数的函数。

很抱歉给了我这么少的选择,这些解决方案是我脑海中唯一想到的。

希望能有所帮助。

相关内容

  • 没有找到相关文章

最新更新