Skip to content

Commit 7326782

Browse files
committed
feat(api): Update URL interceptor to be async (#2876)
1 parent 0041986 commit 7326782

File tree

7 files changed

+195
-62
lines changed

7 files changed

+195
-62
lines changed

Amplify/Categories/API/Interceptor/URLRequestInterceptor.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,25 @@ import Foundation
1919
/// plugin.
2020
public protocol URLRequestInterceptor {
2121

22-
// swiftlint:disable:next todo
23-
// TODO: turn async https://github.com/aws-amplify/amplify-ios/issues/73
2422
/// Inspect and optionally modify the request, returning either the original
2523
/// unmodified request or a modified copy.
2624
/// - Parameter request: The URLRequest
2725
func intercept(_ request: URLRequest) throws -> URLRequest
26+
27+
/// Inspect and optionally modify the request, returning either the original
28+
/// unmodified request or a modified copy in the completion handler.
29+
/// - Parameter request: The URLRequest
30+
func intercept(_ request: URLRequest, completion: @escaping (Result<URLRequest, Error>) -> Void)
31+
}
32+
33+
public extension URLRequestInterceptor {
34+
35+
func intercept(_ request: URLRequest, completion: (Result<URLRequest, Error>) -> Void) {
36+
do {
37+
let interceptedRequest = try intercept(request)
38+
completion(.success(interceptedRequest))
39+
} catch {
40+
completion(.failure(error))
41+
}
42+
}
2843
}

AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,29 @@ struct AuthTokenURLRequestInterceptor: URLRequestInterceptor {
4141
mutableRequest.setValue(token, forHTTPHeaderField: "authorization")
4242
return mutableRequest as URLRequest
4343
}
44+
45+
func intercept(_ request: URLRequest, completion: @escaping (Result<URLRequest, Error>) -> Void) {
46+
guard let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
47+
completion(.failure(APIError.unknown("Could not get mutable request", "")))
48+
return
49+
}
50+
51+
authTokenProvider.getToken { result in
52+
do {
53+
let token = try result.get()
54+
mutableRequest.setValue(NSDate().aws_stringValue(AWSDateISO8601DateFormat2),
55+
forHTTPHeaderField: URLRequestConstants.Header.xAmzDate)
56+
mutableRequest.setValue(URLRequestConstants.ContentType.applicationJson,
57+
forHTTPHeaderField: URLRequestConstants.Header.contentType)
58+
mutableRequest.setValue(AmplifyAWSServiceConfiguration.baseUserAgent(),
59+
forHTTPHeaderField: URLRequestConstants.Header.userAgent)
60+
mutableRequest.setValue(token, forHTTPHeaderField: "authorization")
61+
completion(.success(mutableRequest as URLRequest))
62+
return
63+
} catch {
64+
let apiError = APIError.operationError("Failed to retrieve authorization token.", "", error)
65+
completion(.failure(apiError))
66+
}
67+
}
68+
}
4469
}

AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,49 @@ struct IAMURLRequestInterceptor: URLRequestInterceptor {
6262

6363
return mutableRequest as URLRequest
6464
}
65+
66+
func intercept(_ request: URLRequest, completion: @escaping (Result<URLRequest, Error>) -> Void) {
67+
68+
guard let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
69+
let apiError = APIError.unknown("Could not get mutable request", "")
70+
completion(.failure(apiError))
71+
return
72+
}
73+
74+
let serviceConfiguration = AmplifyAWSServiceConfiguration(
75+
region: region,
76+
credentialsProvider: iamCredentialsProvider.getCredentialsProvider())
77+
mutableRequest.setValue(NSDate().aws_stringValue(AWSDateISO8601DateFormat2),
78+
forHTTPHeaderField: URLRequestConstants.Header.xAmzDate)
79+
mutableRequest.setValue(URLRequestConstants.ContentType.applicationJson,
80+
forHTTPHeaderField: URLRequestConstants.Header.contentType)
81+
mutableRequest.setValue(serviceConfiguration.userAgent,
82+
forHTTPHeaderField: URLRequestConstants.Header.userAgent)
83+
84+
let endpoint: AWSEndpoint
85+
switch endpointType {
86+
case .graphQL:
87+
endpoint = AWSEndpoint(region: region,
88+
serviceName: URLRequestConstants.appSyncServiceName,
89+
url: mutableRequest.url)
90+
case .rest:
91+
endpoint = AWSEndpoint(region: region,
92+
service: AWSServiceType.APIGateway,
93+
url: mutableRequest.url)
94+
}
95+
96+
let signer: AWSSignatureV4Signer = AWSSignatureV4Signer(
97+
credentialsProvider: iamCredentialsProvider.getCredentialsProvider(),
98+
endpoint: endpoint)
99+
100+
signer.interceptRequest(mutableRequest).continueWith { task in
101+
if let error = task.error {
102+
let apiError = APIError.operationError("Got error trying to sign", "", error)
103+
completion(.failure(apiError))
104+
return nil
105+
}
106+
completion(.success(mutableRequest as URLRequest))
107+
return nil
108+
}
109+
}
65110
}

AmplifyPlugins/API/AWSAPICategoryPlugin/Operation/AWSGraphQLOperation.swift

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,35 +100,65 @@ final public class AWSGraphQLOperation<R: Decodable>: GraphQLOperation<R> {
100100
}
101101

102102
// Create request
103-
let urlRequest = GraphQLOperationRequestUtils.constructRequest(with: endpointConfig.baseURL,
104-
requestPayload: requestPayload)
103+
let urlRequest = GraphQLOperationRequestUtils.constructRequest(
104+
with: endpointConfig.baseURL,
105+
requestPayload: requestPayload)
105106

106107
// Intercept request
107-
let finalRequest = requestInterceptors.reduce(urlRequest) { (request, interceptor) -> URLRequest in
108-
do {
109-
return try interceptor.intercept(request)
110-
} catch let error as APIError {
111-
dispatch(result: .failure(error))
112-
cancel()
113-
return request
114-
} catch {
115-
dispatch(result: .failure(APIError.operationError("Failed to intercept request fully.",
116-
"Something wrong with the interceptor",
117-
error)))
118-
cancel()
119-
return request
108+
chainInterceptors(iterator: requestInterceptors.makeIterator(),
109+
request: urlRequest) { finalRequest in
110+
if self.isCancelled {
111+
self.finish()
112+
return
120113
}
114+
115+
// Begin network task
116+
Amplify.API.log.debug("Starting network task for \(self.request.operationType) \(self.id)")
117+
let task = self.session.dataTaskBehavior(with: finalRequest)
118+
self.mapper.addPair(operation: self, task: task)
119+
task.resume()
121120
}
121+
}
122+
123+
private func chainInterceptors<I: IteratorProtocol>(
124+
iterator: I,
125+
request: URLRequest,
126+
completion: @escaping (URLRequest) -> Void
127+
) where I.Element == URLRequestInterceptor {
122128

123129
if isCancelled {
124-
finish()
130+
completion(request)
125131
return
126132
}
127133

128-
// Begin network task
129-
Amplify.API.log.debug("Starting network task for \(request.operationType) \(id)")
130-
let task = session.dataTaskBehavior(with: finalRequest)
131-
mapper.addPair(operation: self, task: task)
132-
task.resume()
134+
var mutableIterator = iterator
135+
guard let interceptor = mutableIterator.next() else {
136+
completion(request)
137+
return
138+
}
139+
interceptor.intercept(request) { result in
140+
do {
141+
let interceptedRequest = try result.get()
142+
self.chainInterceptors(
143+
iterator: mutableIterator,
144+
request: interceptedRequest,
145+
completion: completion)
146+
return
147+
} catch let error as APIError {
148+
self.dispatch(result: .failure(error))
149+
self.cancel()
150+
completion(request)
151+
return
152+
} catch {
153+
self.dispatch(result: .failure(
154+
APIError.operationError("Failed to intercept request fully.",
155+
"Something wrong with the interceptor",
156+
error)))
157+
self.cancel()
158+
completion(request)
159+
return
160+
}
161+
162+
}
133163
}
134164
}

AmplifyPlugins/API/AWSAPICategoryPlugin/Operation/AWSRESTOperation.swift

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -92,37 +92,66 @@ final public class AWSRESTOperation: AmplifyOperation<
9292
}
9393

9494
// Construct URL Request with url and request body
95-
let urlRequest = RESTOperationRequestUtils.constructURLRequest(with: url,
96-
operationType: request.operationType,
97-
headers: request.headers,
98-
requestPayload: request.body)
95+
let urlRequest = RESTOperationRequestUtils.constructURLRequest(
96+
with: url,
97+
operationType: request.operationType,
98+
headers: request.headers,
99+
requestPayload: request.body)
99100

100101
// Intercept request
101-
let finalRequest = requestInterceptors.reduce(urlRequest) { (request, interceptor) -> URLRequest in
102-
do {
103-
return try interceptor.intercept(request)
104-
} catch let error as APIError {
105-
dispatch(result: .failure(error))
106-
cancel()
107-
return request
108-
} catch {
109-
dispatch(result: .failure(APIError.operationError("Failed to intercept request fully.",
110-
"Something wrong with the interceptor",
111-
error)))
112-
cancel()
113-
return request
102+
chainInterceptors(iterator: requestInterceptors.makeIterator(),
103+
request: urlRequest) { finalRequest in
104+
if self.isCancelled {
105+
self.finish()
106+
return
114107
}
108+
109+
// Begin network task
110+
let task = self.session.dataTaskBehavior(with: finalRequest)
111+
self.mapper.addPair(operation: self, task: task)
112+
task.resume()
115113
}
114+
}
115+
116+
private func chainInterceptors<I: IteratorProtocol>(
117+
iterator: I,
118+
request: URLRequest,
119+
completion: @escaping (URLRequest) -> Void
120+
) where I.Element == URLRequestInterceptor {
116121

117122
if isCancelled {
118-
finish()
123+
completion(request)
124+
return
125+
}
126+
127+
var mutableIterator = iterator
128+
guard let interceptor = mutableIterator.next() else {
129+
completion(request)
119130
return
120131
}
132+
interceptor.intercept(request) { result in
133+
do {
134+
let interceptedRequest = try result.get()
135+
self.chainInterceptors(
136+
iterator: mutableIterator,
137+
request: interceptedRequest,
138+
completion: completion)
139+
return
140+
} catch let error as APIError {
141+
self.dispatch(result: .failure(error))
142+
self.cancel()
143+
completion(request)
144+
return
145+
} catch {
146+
self.dispatch(result: .failure(
147+
APIError.operationError("Failed to intercept request fully.",
148+
"Something wrong with the interceptor",
149+
error)))
150+
self.cancel()
151+
completion(request)
152+
return
153+
}
121154

122-
// Begin network task
123-
let task = session.dataTaskBehavior(with: finalRequest)
124-
mapper.addPair(operation: self, task: task)
125-
task.resume()
155+
}
126156
}
127157
}
128-

AmplifyPlugins/API/Podfile.lock

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ DEPENDENCIES:
6363

6464
SPEC REPOS:
6565
trunk:
66+
- AppSyncRealTimeClient
6667
- AWSAuthCore
6768
- AWSCognitoIdentityProvider
6869
- AWSCognitoIdentityProviderASF
@@ -83,26 +84,20 @@ EXTERNAL SOURCES:
8384
:path: "../../"
8485
AmplifyTestCommon:
8586
:path: "../../"
86-
AppSyncRealTimeClient:
87-
:branch: main
88-
:git: https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git
8987
AWSPluginsCore:
9088
:path: "../../"
9189
CwlPreconditionTesting:
9290
:git: https://github.com/mattgallagher/CwlPreconditionTesting.git
9391
:tag: 2.1.0
9492

9593
CHECKOUT OPTIONS:
96-
AppSyncRealTimeClient:
97-
:commit: 814d7a42a99371949732fc1e0fd72ad0edfcf6a9
98-
:git: https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git
9994
CwlPreconditionTesting:
10095
:git: https://github.com/mattgallagher/CwlPreconditionTesting.git
10196
:tag: 2.1.0
10297

10398
SPEC CHECKSUMS:
10499
Amplify: e5b6bd8f5229d43abe147bc7390d7dce3007acea
105-
AmplifyPlugins: da16305d9b3b76d20c10517c815082eed0589a8e
100+
AmplifyPlugins: 7c517b9fd824e6c20922335c9a0a252138388de7
106101
AmplifyTestCommon: 95c7c6c3849f7de46458754afbaf62f1d5158128
107102
AppSyncRealTimeClient: 49901c6f21e541bec09281854c5695a6c1309bf7
108103
AWSAuthCore: 42e2d4927d94e5e323b63b2ae885f287c51b5889

AmplifyPlugins/DataStore/Podfile.lock

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ PODS:
33
- Amplify/Default (= 1.29.3)
44
- Amplify/Default (1.29.3)
55
- AmplifyPlugins/AWSAPIPlugin (1.29.3):
6-
- AppSyncRealTimeClient (~> 3.0)
6+
- AppSyncRealTimeClient (~> 3.1)
77
- AWSCore (~> 2.30.1)
88
- AWSPluginsCore (= 1.29.3)
99
- AmplifyPlugins/AWSCognitoAuthPlugin (1.29.3):
@@ -106,15 +106,9 @@ CHECKOUT OPTIONS:
106106
:tag: 2.1.0
107107

108108
SPEC CHECKSUMS:
109-
<<<<<<< HEAD
110109
Amplify: e5b6bd8f5229d43abe147bc7390d7dce3007acea
111-
AmplifyPlugins: da16305d9b3b76d20c10517c815082eed0589a8e
110+
AmplifyPlugins: 7c517b9fd824e6c20922335c9a0a252138388de7
112111
AmplifyTestCommon: 95c7c6c3849f7de46458754afbaf62f1d5158128
113-
=======
114-
Amplify: 955400b4b5f29832357f682e15c659d049028306
115-
AmplifyPlugins: a82d1c5ddb2b5a8fcd23dd447f84b04f93dc00c2
116-
AmplifyTestCommon: 9b452352797cd82faa67e296669bb94bd531a5eb
117-
>>>>>>> 6d33bd24f (feat(api): Add async protocol implementation for token request interceptors (#2870))
118112
AppSyncRealTimeClient: 49901c6f21e541bec09281854c5695a6c1309bf7
119113
AWSAuthCore: 42e2d4927d94e5e323b63b2ae885f287c51b5889
120114
AWSCognitoIdentityProvider: 2331f5f4c8c1775c6a228bb2dae2d353198401e8
@@ -134,4 +128,4 @@ SPEC CHECKSUMS:
134128

135129
PODFILE CHECKSUM: 0bab7193bebdf470839514f327440893b0d26090
136130

137-
COCOAPODS: 1.11.3
131+
COCOAPODS: 1.12.0

0 commit comments

Comments
 (0)