Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 | 31 |
Tags
- Refactoring
- 리펙터링
- uiscrollview
- UICollectionView
- 애니메이션
- ios
- uitableview
- MVVM
- rxswift
- clean architecture
- Human interface guide
- Clean Code
- 리팩토링
- UITextView
- 클린 코드
- swift documentation
- 스위프트
- map
- RxCocoa
- combine
- Protocol
- Observable
- HIG
- Xcode
- 리펙토링
- collectionview
- swiftUI
- ribs
- tableView
- SWIFT
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 4. GraphQL - Apollo의 request pipeline, Interceptor, token, header 본문
iOS framework
[iOS - swift] 4. GraphQL - Apollo의 request pipeline, Interceptor, token, header
jake-kim 2022. 3. 3. 23:121. GraphQL - 개념
3. GraphQL - Apollo 모델 생성 (generate model)
4. GraphQL - Apollo의 request pipeline, Interceptor, token, header
5. GraphQL - Apollo의 fetch 예제 (Pagination)
Request pipeline, Interceptor
- 작업이 실행되면 작업이라는 개체가 InterceptorProvider를 생성 - RequestChain이 되는 것
- 이 chain이 First Interceptor, Second Interceptor들을 실행
- Interceptor의 기능
- HTTP Header 추가 가능
- 서버에 request 기능
- Interceptor가 작업 결과를 Apollo iOS 캐시에 기록도 가능
- InterceptorProvider의 종류 (Apollo에서는 DefalutInterceptorProvider를 강력히 권장)
- DefaultInterceptor (Source는 여기서 확인)
- Custom Interceptor: InterceptorProvider를 상속받아서 구현
- 예시 코드 (ApolloInterceptor를 상속받아서 구현)
// https://www.apollographql.com/docs/ios/request-pipeline/#example-interceptor-provider
import Foundation
import Apollo
struct NetworkInterceptorProvider: InterceptorProvider {
// These properties will remain the same throughout the life of the `InterceptorProvider`, even though they
// will be handed to different interceptors.
private let store: ApolloStore
private let client: URLSessionClient
init(store: ApolloStore,
client: URLSessionClient) {
self.store = store
self.client = client
}
func interceptors<Operation: GraphQLOperation>(for operation: Operation) -> [ApolloInterceptor] {
return [
MaxRetryInterceptor(),
CacheReadInterceptor(store: self.store),
UserManagementInterceptor(),
RequestLoggingInterceptor(),
NetworkFetchInterceptor(client: self.client),
ResponseLoggingInterceptor(),
ResponseCodeInterceptor(),
JSONResponseParsingInterceptor(cacheKeyForObject: self.store.cacheKeyForObject),
AutomaticPersistedQueryInterceptor(),
CacheWriteInterceptor(store: self.store)
]
}
}
- 위에서 들어가는 Interceptor 인스턴스들은 모두 ApolloInterceptor를 상속받은 구현체
- ex) RequestLoggingInterceptor(), ResponseLoggingInterceptor()
// https://www.apollographql.com/docs/ios/request-pipeline/#requestlogginginterceptor
import Apollo
class RequestLoggingInterceptor: ApolloInterceptor {
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void) {
Logger.log(.debug, "Outgoing request: \(request)")
chain.proceedAsync(request: request,
response: response,
completion: completion)
}
}
class ResponseLoggingInterceptor: ApolloInterceptor {
enum ResponseLoggingError: Error {
case notYetReceived
}
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void) {
defer {
// Even if we can't log, we still want to keep going.
chain.proceedAsync(request: request,
response: response,
completion: completion)
}
guard let receivedResponse = response else {
chain.handleErrorAsync(ResponseLoggingError.notYetReceived,
request: request,
response: response,
completion: completion)
return
}
Logger.log(.debug, "HTTP Response: \(receivedResponse.httpResponse)")
if let stringData = String(bytes: receivedResponse.rawData, encoding: .utf8) {
Logger.log(.debug, "Data: \(stringData)")
} else {
Logger.log(.error, "Could not convert data to string!")
}
}
}
Header
- Header를 넣어주려면 위에서 알아보았듯이, InterceptorProvider의 안에 ApolloInterceptor를 상속받은 인스턴스를 넣어주는 방식으로 사용
- 흐름
- ApolloClient(networkTranport:store:)를 싱글톤으로 사용 (networkTransport는 Intercetor, store는 캐싱)
- networkTransport에 InterceptorProvider를 상속받은 커스텀 인스턴스를 주입
- InterceptorProvider를 상속받은 커스텀 인스턴스안에 ApolloInterceptor를 상속받은 request 로깅, response 로깅, 헤더 정보 주입
- ApolloInterceptor를 상속받아서 헤더정보를 삽입 & 토큰 정보도 주입
* 테스트에 사용될 토큰 관리 클래스 생성
class TokenManager {
enum RenewError: Error {
case unknown
}
static let shared = TokenManager()
var token: Token? = Token()
func renewToken(completion: @escaping (Result<Token, RenewError>) -> Void) {
completion(.success(Token()))
}
private init() {}
}
struct Token {
let value = "test-token"
var isExpired: Bool { Bool.random() }
}
- interceptAsync 메소드를 정의하여 request.addHeader를 이용해 토큰정보나 다른 정보를 헤더에 추가
// MyAPI+ApolloClient.swif
// https://www.apollographql.com/docs/ios/request-pipeline/#usermanagementinterceptor
private class HeaderInterceptor: ApolloInterceptor {
enum UserError: Error {
case noUserLoggedIn
}
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void
) {
request.addHeader(name: "Language", value: Locale.current.languageCode ?? "*")
guard let token = TokenManager.shared.token else {
chain.handleErrorAsync(UserError.noUserLoggedIn,
request: request,
response: response,
completion: completion)
return
}
if token.isExpired {
TokenManager.shared.renewToken { [weak self] tokenRenewResult in
guard let self = self else {
return
}
switch tokenRenewResult {
case .failure(let error):
chain.handleErrorAsync(
error,
request: request,
response: response,
completion: completion
)
case .success(let token):
self.addTokenAndProceed(
token,
to: request,
chain: chain,
response: response,
completion: completion
)
}
}
} else {
self.addTokenAndProceed(
token,
to: request,
chain: chain,
response: response,
completion: completion
)
}
}
private func addTokenAndProceed<Operation: GraphQLOperation>(
_ token: Token,
to request: HTTPRequest<Operation>,
chain: RequestChain,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>
) -> Void) {
request.addHeader(name: "Authorization", value: "Bearer \(token.value)")
chain.proceedAsync(request: request,
response: response,
completion: completion)
}
}
- 위에서 정의된 HeaderInterceptor를 InterceptorProvider를 상속받아서 커스텀한 NetworkInterceptorProvider의 interceptors 메소드 리턴값에 추가
// MyAPI+ApolloClient.swif
// https://www.apollographql.com/docs/ios/request-pipeline/#example-interceptor-provider
private struct NetworkInterceptorProvider: InterceptorProvider {
private let store: ApolloStore
private let client: URLSessionClient
init(store: ApolloStore,
client: URLSessionClient) {
self.store = store
self.client = client
}
func interceptors<Operation: GraphQLOperation>(for operation: Operation) -> [ApolloInterceptor] {
return [
MaxRetryInterceptor(),
CacheReadInterceptor(store: self.store),
HeaderInterceptor(), // <- 여기
RequestLoggingInterceptor(),
NetworkFetchInterceptor(client: self.client),
ResponseLoggingInterceptor(),
ResponseCodeInterceptor(),
JSONResponseParsingInterceptor(cacheKeyForObject: self.store.cacheKeyForObject),
AutomaticPersistedQueryInterceptor(),
CacheWriteInterceptor(store: self.store)
]
}
}
- 위에서 IntercerptorProvider를 상속받아서 정의한 인스턴스를 ApolloClient 생성자에 주입
// MyAPI+ApolloClient.swif
private enum Constants {
static let requestTimeoutSeconds = 30.0
static let endpoint = URL(string: "https://apollo-fullstack-tutorial.herokuapp.com/graphql")!
}
private var client: ApolloClient {
let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.timeoutIntervalForRequest = Constants.requestTimeoutSeconds
let sessionClient = URLSessionClient(sessionConfiguration: sessionConfiguration, callbackQueue: .main)
let store = ApolloStore(cache: InMemoryNormalizedCache())
let networkInterceptorProvider = NetworkInterceptorProvider(store: store, client: sessionClient)
let requestChainNetworkTransport = RequestChainNetworkTransport(
interceptorProvider: networkInterceptorProvider,
endpointURL: Constants.endpoint
)
return ApolloClient(networkTransport: requestChainNetworkTransport, store: store)
}
* 전체 코드: https://github.com/JK0369/ExApollo
* 참고
https://www.apollographql.com/docs/ios/request-pipeline/#usermanagementinterceptor
'iOS framework' 카테고리의 다른 글
[iOS - swift] RxGesture 사용 방법 (스와이프, UIPageControl) (0) | 2022.03.12 |
---|---|
[iOS - swift] 5. GraphQL - Apollo의 fetch 예제 (Pagination) (0) | 2022.03.04 |
[iOS - swift] 3. GraphQL - Apollo 모델 생성 (generate model) (0) | 2022.03.02 |
[iOS - swift] 2. GraphQL - Apollo 사용 개념 (0) | 2022.03.01 |
[iOS - swift] 1. GraphQL, Apollo - 개념 (0) | 2022.02.28 |
Comments