Last active
June 8, 2023 04:25
-
-
Save NunciosChums/841b2c3935b2028b2162842d479de143 to your computer and use it in GitHub Desktop.
Moya + renewal token when Unauthorized(401)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
import RxSwift | |
/// 인증 관련 API | |
final class AuthService: BaseService<AuthAPI> { | |
static let shared = AuthService() | |
private override init() {} | |
/// 토큰 재발급 | |
func renewalToken(refreshToken: String) -> Single<Response> { | |
return request(.renewalToken(refreshToken)) | |
} | |
} | |
// MARK: - API | |
enum AuthAPI { | |
/// 토큰 재발급 | |
case renewalToken(String) | |
} | |
extension AuthAPI: BaseAPI { | |
var path: String { | |
let apiPath = "/api-as/v1" | |
switch self { | |
case .renewalToken: | |
return "\(apiPath)/\("renewalToken".lowercased())" | |
} | |
} | |
var method: Moya.Method { | |
switch self { | |
case .renewalToken: | |
return .post | |
} | |
} | |
var task: Task { | |
switch self { | |
case let .renewalToken(refreshToken): | |
return .requestParameters( | |
parameters: ["refreshToken": refreshToken], | |
encoding: JSONEncoding.default | |
) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
protocol BaseAPI: TargetType {} | |
extension BaseAPI { | |
var baseURL: URL { URL(string: "https://api.github.com")! } | |
var method: Moya.Method { .get } | |
var sampleData: Data { Data() } | |
var task: Task { .requestPlain } | |
var headers: [String: String]? { nil } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Moya | |
import RxMoya | |
import RxSwift | |
/// 네트워크 호출 상속용 | |
/// https://github.com/Moya/Moya | |
class BaseService<API: TargetType> { | |
// moya에서 지원하는 로깅 플러그인 | |
// private let provider = MoyaProvider<API>(plugins: [NetworkLoggerPlugin()]) | |
// 커스텀 로깅 플러그인 | |
private let provider = MoyaProvider<API>(plugins: [RequestLoggingPlugin()]) | |
/// 네트워크 호출 | |
/// help from https://github.com/Moya/Moya/issues/1177#issuecomment-345132374 | |
func request(_ api: API) -> Single<Response> { | |
return provider.rx.request(api) | |
.flatMap { | |
// 401(Unauthorized) 발생 시 자동으로 토큰을 재발급 받는다 | |
if $0.statusCode == 401 { | |
throw TokenError.tokenExpired | |
} else { | |
return Single.just($0) | |
} | |
} | |
.retryWhen { (error: Observable<TokenError>) in | |
error.flatMap { error -> Single<Response> in | |
AuthService.shared.renewalToken() // 토큰 재발급 받기 | |
} | |
} | |
.handleResponse() | |
.filterSuccessfulStatusCodes() | |
.retry(2) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
import RxSwift | |
/// 서버에서 보내주는 오류 문구 파싱용 | |
extension PrimitiveSequence where Trait == SingleTrait, Element == Response { | |
func handleResponse() -> Single<Element> { | |
return flatMap { response in | |
// 토큰 재발급 받았을 때 토큰 변경함 | |
if let newToken = try? response.map(Token.self) { | |
UserDefaults.accessToken = newToken.accessToken | |
UserDefaults.refreshToken = newToken.refreshToken | |
} | |
if (200 ... 299) ~= response.statusCode { | |
return Single.just(response) | |
} | |
if var error = try? response.map(ResponseError.self) { | |
error.statusCode = response.statusCode | |
return Single.error(error) | |
} | |
// Its an error and can't decode error details from server, push generic message | |
let genericError = ResponseError(statusCode: response.statusCode | |
serverName: "unknown Server Name", | |
error: "unknown error", | |
message: "empty message") | |
return Single.error(genericError) | |
} | |
} | |
} | |
/// 토큰 만료 에러 | |
enum TokenError: Swift.Error { | |
case tokenExpired | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TestService.shared.user(name: "Moya") | |
.map(TestModel.self) | |
.subscribe( | |
onSuccess: { | |
print("=== user: \($0)") | |
}, | |
onError: { | |
print("==== error: \($0)") | |
} | |
).disposed(by: disposeBag) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
/// 네트워크 호출 결과 로그 표시 | |
final class RequestLoggingPlugin: PluginType { | |
/// API를 보내기 직전에 호출 | |
func willSend(_ request: RequestType, target: TargetType) { | |
guard let httpRequest = request.request else { | |
print("--> invalid request") | |
return | |
} | |
let url = httpRequest.description | |
let method = httpRequest.httpMethod ?? "unknown method" | |
var log = "--> \(method) \(url)\n" | |
log.append("API: \(target)\n") | |
if let headers = httpRequest.allHTTPHeaderFields, !headers.isEmpty { | |
log.append("header: \(headers)\n") | |
} | |
if let body = httpRequest.httpBody, let bodyString = String(bytes: body, encoding: String.Encoding.utf8) { | |
log.append("\(bodyString)\n") | |
} | |
log.append("--> END \(method)") | |
print(log) | |
} | |
/// API Response | |
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) { | |
switch result { | |
case let .success(response): | |
onSuceed(response, target: target, isFromError: false) | |
case let .failure(error): | |
onFail(error, target: target) | |
} | |
} | |
func onSuceed(_ response: Response, target: TargetType, isFromError: Bool) { | |
let request = response.request | |
let url = request?.url?.absoluteString ?? "nil" | |
let statusCode = response.statusCode | |
var log = "<-- \(statusCode) \(url)\n" | |
log.append("API: \(target)\n") | |
response.response?.allHeaderFields.forEach { | |
log.append("\($0): \($1)\n") | |
} | |
if let reString = String(bytes: response.data, encoding: String.Encoding.utf8) { | |
log.append("\(reString)\n") | |
} | |
log.append("<-- END HTTP (\(response.data.count)-byte body)") | |
print(log) | |
} | |
func onFail(_ error: MoyaError, target: TargetType) { | |
if let response = error.response { | |
onSuceed(response, target: target, isFromError: true) | |
return | |
} | |
var log = "<-- \(error.errorCode) \(target)\n" | |
log.append("\(error.failureReason ?? error.errorDescription ?? "unknown error")\n") | |
log.append("<-- END HTTP") | |
print(log) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct ResponseError: Decodable, Error { | |
var statusCode: Int? | |
let message: String | |
let documentation_url: String | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
struct TestModel: Decodable { | |
let id: Int | |
let name: String | |
let login: String | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
import RxSwift | |
final class TestService: BaseService<TestAPI> { | |
static let shared = TestService() | |
private override init() {} | |
/// 아이디로 사용자 정보 가져오기 | |
/// - Parameters: | |
/// - name: 로그인 아이디 | |
func user(name: String) -> Single<Response> { | |
return request(.profile(name)) | |
} | |
} | |
enum TestAPI { | |
case profile(String) | |
} | |
extension TestAPI: BaseAPI { | |
var path: String { | |
switch self { | |
case let .profile(name): | |
return "/users/\(name)" | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct Token: Decodable { | |
let tokenType: String | |
let accessToken: String | |
let refreshToken: String | |
let expiresAt: TimeInterval | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment