-
Notifications
You must be signed in to change notification settings - Fork 730
/
RequestChainNetworkTransport.swift
196 lines (170 loc) · 8.27 KB
/
RequestChainNetworkTransport.swift
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import Foundation
#if !COCOAPODS
import ApolloAPI
#endif
/// An implementation of `NetworkTransport` which creates a `RequestChain` object
/// for each item sent through it.
open class RequestChainNetworkTransport: NetworkTransport {
/// The interceptor provider to use when constructing a request chain
let interceptorProvider: any InterceptorProvider
/// The GraphQL endpoint URL to use.
public let endpointURL: URL
/// Any additional HTTP headers that should be added to **every** request, such as an API key or a language setting.
///
/// If a header should only be added to _certain_ requests, or if its value might differ between requests,
/// you should add that header in an interceptor instead.
///
/// Defaults to an empty dictionary.
public private(set) var additionalHeaders: [String: String]
/// Set to `true` if Automatic Persisted Queries should be used to send a query hash instead of the full query body by default.
public let autoPersistQueries: Bool
/// Set to `true` if you want to use `GET` instead of `POST` for queries.
///
/// This can improve performance if your GraphQL server uses a CDN (Content Delivery Network)
/// to cache the results of queries that rarely change.
///
/// Mutation operations always use POST, even when this is `false`
///
/// Defaults to `false`.
public let useGETForQueries: Bool
/// Set to `true` to use `GET` instead of `POST` for a retry of a persisted query.
public let useGETForPersistedQueryRetry: Bool
/// The `RequestBodyCreator` object used to build your `URLRequest`.
///
/// Defaults to an ``ApolloRequestBodyCreator`` initialized with the default configuration.
public var requestBodyCreator: any RequestBodyCreator
/// Designated initializer
///
/// - Parameters:
/// - interceptorProvider: The interceptor provider to use when constructing a request chain
/// - endpointURL: The GraphQL endpoint URL to use
/// - additionalHeaders: Any additional headers that should be automatically added to every request. Defaults to an empty dictionary.
/// - autoPersistQueries: Pass `true` if Automatic Persisted Queries should be used to send a query hash instead of the full query body by default. Defaults to `false`.
/// - requestBodyCreator: The `RequestBodyCreator` object to use to build your `URLRequest`. Defaults to the provided `ApolloRequestBodyCreator` implementation.
/// - useGETForQueries: Pass `true` if you want to use `GET` instead of `POST` for queries, for example to take advantage of a CDN. Defaults to `false`.
/// - useGETForPersistedQueryRetry: Pass `true` to use `GET` instead of `POST` for a retry of a persisted query. Defaults to `false`.
public init(interceptorProvider: any InterceptorProvider,
endpointURL: URL,
additionalHeaders: [String: String] = [:],
autoPersistQueries: Bool = false,
requestBodyCreator: any RequestBodyCreator = ApolloRequestBodyCreator(),
useGETForQueries: Bool = false,
useGETForPersistedQueryRetry: Bool = false) {
self.interceptorProvider = interceptorProvider
self.endpointURL = endpointURL
self.additionalHeaders = additionalHeaders
self.autoPersistQueries = autoPersistQueries
self.requestBodyCreator = requestBodyCreator
self.useGETForQueries = useGETForQueries
self.useGETForPersistedQueryRetry = useGETForPersistedQueryRetry
}
/// Constructs a GraphQL request for the given operation.
///
/// Override this method if you need to use a custom subclass of `HTTPRequest`.
///
/// - Parameters:
/// - operation: The operation to create the request for
/// - cachePolicy: The `CachePolicy` to use when creating the request
/// - contextIdentifier: [optional] A unique identifier for this request, to help with deduping cache hits for watchers. Should default to `nil`.
/// - context: [optional] A context that is being passed through the request chain. Should default to `nil`.
/// - Returns: The constructed request.
open func constructRequest<Operation: GraphQLOperation>(
for operation: Operation,
cachePolicy: CachePolicy,
contextIdentifier: UUID? = nil,
context: (any RequestContext)? = nil
) -> HTTPRequest<Operation> {
let request = JSONRequest(
operation: operation,
graphQLEndpoint: self.endpointURL,
contextIdentifier: contextIdentifier,
clientName: self.clientName,
clientVersion: self.clientVersion,
additionalHeaders: self.additionalHeaders,
cachePolicy: cachePolicy,
context: context,
autoPersistQueries: self.autoPersistQueries,
useGETForQueries: self.useGETForQueries,
useGETForPersistedQueryRetry: self.useGETForPersistedQueryRetry,
requestBodyCreator: self.requestBodyCreator
)
if Operation.operationType == .subscription {
request.addHeader(
name: "Accept",
value: "multipart/mixed;\(MultipartResponseSubscriptionParser.protocolSpec),application/json"
)
} else {
request.addHeader(
name: "Accept",
value: "multipart/mixed;\(MultipartResponseDeferParser.protocolSpec),application/json"
)
}
return request
}
// MARK: - NetworkTransport Conformance
public var clientName = RequestChainNetworkTransport.defaultClientName
public var clientVersion = RequestChainNetworkTransport.defaultClientVersion
public func send<Operation: GraphQLOperation>(
operation: Operation,
cachePolicy: CachePolicy = .default,
contextIdentifier: UUID? = nil,
context: (any RequestContext)? = nil,
callbackQueue: DispatchQueue = .main,
completionHandler: @escaping (Result<GraphQLResult<Operation.Data>, any Error>) -> Void) -> any Cancellable {
let chain = makeChain(operation: operation, callbackQueue: callbackQueue)
let request = self.constructRequest(
for: operation,
cachePolicy: cachePolicy,
contextIdentifier: contextIdentifier,
context: context)
chain.kickoff(request: request, completion: completionHandler)
return chain
}
private func makeChain<Operation: GraphQLOperation>(
operation: Operation,
callbackQueue: DispatchQueue = .main
) -> any RequestChain {
let interceptors = self.interceptorProvider.interceptors(for: operation)
let chain = InterceptorRequestChain(interceptors: interceptors, callbackQueue: callbackQueue)
chain.additionalErrorHandler = self.interceptorProvider.additionalErrorInterceptor(for: operation)
return chain
}
}
extension RequestChainNetworkTransport: UploadingNetworkTransport {
/// Constructs an uploading (ie, multipart) GraphQL request
///
/// Override this method if you need to use a custom subclass of `HTTPRequest`.
///
/// - Parameters:
/// - operation: The operation to create a request for
/// - files: The files you wish to upload
/// - context: [optional] A context that is being passed through the request chain. Should default to `nil`.
/// - manualBoundary: [optional] A manually set boundary for your upload request. Defaults to nil.
/// - Returns: The created request.
public func constructUploadRequest<Operation: GraphQLOperation>(
for operation: Operation,
with files: [GraphQLFile],
context: (any RequestContext)? = nil,
manualBoundary: String? = nil) -> HTTPRequest<Operation> {
UploadRequest(graphQLEndpoint: self.endpointURL,
operation: operation,
clientName: self.clientName,
clientVersion: self.clientVersion,
additionalHeaders: self.additionalHeaders,
files: files,
manualBoundary: manualBoundary,
context: context,
requestBodyCreator: self.requestBodyCreator)
}
public func upload<Operation: GraphQLOperation>(
operation: Operation,
files: [GraphQLFile],
context: (any RequestContext)?,
callbackQueue: DispatchQueue = .main,
completionHandler: @escaping (Result<GraphQLResult<Operation.Data>, any Error>) -> Void) -> any Cancellable {
let request = self.constructUploadRequest(for: operation, with: files, context: context)
let chain = makeChain(operation: operation, callbackQueue: callbackQueue)
chain.kickoff(request: request, completion: completionHandler)
return chain
}
}