티스토리 뷰
[iOS] Open API Generator 로 네트워크 코드 자동생성(2) - middleware로 토큰 관리, interceptor
jisuuuu 2024. 10. 29. 00:34Open API Generator 를 초기 세팅 이후 사용하면서 점차 적응해보고 있다.
Open API Generator 에 미들웨어를 적용해 네트워크 레이어에 필요한 여러 기능을 사용했다.
우선, 내가 필요했던 기능은 다음과 같다.
- 로깅
- 엑세스 토큰 주입
- 401 에러 캐치, 리프레시 토큰 갱신 후 재시도 (retry)
주로 사용했던 Alamofire 에서는 Interceptor 와 Event Monitor 로 사용했던 기능들인데,
다행스럽게도 미들웨어의 example 이 잘 되어 있고, Swift Concurrency 를 활용해 간결하게 되어 있어
어렵지 않게 적용할 수 있었다.
우선 미들웨어는 결국 인터셉터다. 프로토콜로 되어있는데,
public protocol ClientMiddleware: Sendable {
/// Intercepts an outgoing HTTP request and an incoming HTTP response.
/// - Parameters:
/// - request: An HTTP request.
/// - body: An HTTP request body.
/// - baseURL: A server base URL.
/// - operationID: The identifier of the OpenAPI operation.
/// - next: A closure that calls the next middleware, or the transport.
/// - Returns: An HTTP response and its body.
/// - Throws: An error if interception of the request and response fails.
func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?)
}
request 와 body 가 해당 함수로 들어오고 있고, next 와 return 을 모두 선언해주어야 하는데 이부분이 조금 헷갈렸다.
var next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?) = {
(_request, _body, _url) in
try await wrappingErrors {
try await transport.send(_request, body: _body, baseURL: _url, operationID: operationID)
} mapError: { error in
makeError(
request: request,
requestBody: requestBody,
baseURL: baseURL,
error: RuntimeError.transportFailed(error)
)
}
}
for middleware in middlewares.reversed() {
let tmp = next
next = { (_request, _body, _url) in
try await wrappingErrors {
try await middleware.intercept(
_request,
body: _body,
baseURL: _url,
operationID: operationID,
next: tmp
)
} mapError: { error in
makeError(
request: request,
requestBody: requestBody,
baseURL: baseURL,
error: RuntimeError.middlewareFailed(middlewareType: type(of: middleware), error)
)
}
}
}
let (response, responseBody): (HTTPResponse, HTTPBody?) = try await next(request, requestBody, baseURL)
return try await wrappingErrors {
try await deserializer(response, responseBody)
} mapError: { error in
makeError(
request: request,
requestBody: requestBody,
baseURL: baseURL,
response: response,
responseBody: responseBody,
error: error
)
}
Open API Generator 라이브러리의 미들웨어 사용 구현부를 들어가 보았다.
async / await 을 사용해 체인으로 만들어둔 것을 볼 수 있다.
tmp 에 sendable 을 순차적으로 저장하고, 사용하는 미들웨어 내부의 next 에 tmp를 넣어준다.
맨 위에 있는 try await transport.send() 가 실제 API 를 콜 하는 메소드인데,
이 구조로 실행을 하게 된다면 설정해둔 미들웨어를 모두 거친 체인을 만들게 되고
가장 아래에 있는 try await next(request, requestBody, baseURL) 에서 땅! 하고 호출하게 되면
미들웨어를 모두 통과한 request 가 장전이 되어 호출하게 된다.
그리고 해당 메소드에서 response 를 받게 되면 return 처리를 했기 때문에 다시 미들웨어로 순차적으로 응답이 들어간다.
(잘만듬..)
그래서 결론적으로 미들웨어의 interceptor 함수를 사용하는 Example 의 플로우를 분석해보자면
extension LoggingMiddleware: ClientMiddleware {
package func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
// 1. 요청 들어왔을 때
let (requestBodyToLog, requestBodyForNext) = try await bodyLoggingPolicy.process(body)
// - request 로그 찍기
log(request, requestBodyToLog)
do {
// 2. 다음 체인으로 보내고 response 받기
let (response, responseBody) = try await next(request, requestBodyForNext, baseURL)
// 3. 받은 리스폰스로 로그 찍기
let (responseBodyToLog, responseBodyForNext) = try await bodyLoggingPolicy.process(responseBody)
log(request, response, responseBodyToLog)
// 4. 다음 체인으로 response 리턴하기 (혹은 종료)
return (response, responseBodyForNext)
} catch {
log(request, failedWith: error)
throw error
}
}
}
이런 구조로 되어있다. 사용할 때는 간편하게 쓸 수 있다 !
사용한 미들웨어 중 accessToken 을 넣어주는 녀석은 이렇게 구현했다.
struct AuthenticationMiddleware {
/// The value for the `Authorization` header field.
private var accessToken: String {
guard let accessToken = TokenManager.accessToken else {
print("🪙 토큰이 비어있음.")
return ""
}
return "Bearer \(accessToken)"
}
}
extension AuthenticationMiddleware: ClientMiddleware {
func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
var request = request
request.headerFields[.authorization] = accessToken
return try await next(request, body, baseURL)
}
}
response 를 받았을 때는 따로 처리가 불필요해 바로 리턴해주었다.
하여튼 복잡해보이긴 하지만, example 들이 잘 되어 있어서 참고해 어렵지 않게 구현할 수 있을 것이다.
https://github.com/apple/swift-openapi-generator/tree/main/Examples
swift-openapi-generator/Examples at main · apple/swift-openapi-generator
Generate Swift client and server code from an OpenAPI document. - apple/swift-openapi-generator
github.com
'iOS' 카테고리의 다른 글
[iOS] demical keyboard type 에서 소수점이 comma로 나온다 (소수점 표기법) (0) | 2024.10.03 |
---|---|
[iOS] Open API Generator 로 네트워크 코드 자동생성(1) (2) | 2024.09.04 |
[iOS] Github Action 과 Tuist test 로 테스트 자동화 구축 (0) | 2024.08.20 |
[iOS] Dateformatter 가 고장나서 이상했던 경험 (0) | 2024.08.07 |
[iOS] 단일타겟 프로젝트를 멀티모듈로 바꾸었다. (1) | 2024.05.16 |
- Total
- Today
- Yesterday
- DateFormatter
- TextField
- 애플워치 데이터 전송
- demical
- watch connectivity
- musicplayer
- locale
- flo
- SwiftUI
- 2024년
- swift날짜
- 애플워치
- AVFoundation
- Xcode15
- auth
- keyboardtype
- Swift
- OAS
- watchOS
- KVO
- openapi-generator
- 소수점
- IOS
- easy cue
- open-api-generator
- 회고
- avplayer
- 토큰
- retry
- Xcode
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |