티스토리 뷰

Open 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

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
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
글 보관함