我是组合和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请求,也可以创建一个特定于登录而不使用通用函数的函数。
很抱歉给了我这么少的选择,这些解决方案是我脑海中唯一想到的。
希望能有所帮助。