Skip to content

Commit d9ca2fc

Browse files
committed
Implement URLSession delegate for HTTPClient
Add followRedirects property to HTTPClientConfiguration
1 parent 893f6b2 commit d9ca2fc

File tree

2 files changed

+115
-15
lines changed

2 files changed

+115
-15
lines changed

Diff for: Sources/Basics/HTPClient+URLSession.swift

+105-13
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,120 @@ import struct TSCUtility.Versioning
88
import FoundationNetworking
99
#endif
1010

11-
public struct URLSessionHTTPClient: HTTPClientProtocol {
12-
private let configuration: URLSessionConfiguration
11+
public final class URLSessionHTTPClient: NSObject {
12+
public var configuration: HTTPClientConfiguration = .init()
1313

14-
public init(configuration: URLSessionConfiguration = .default) {
15-
self.configuration = configuration
14+
private var session: URLSession!
15+
private var taskDelegates: [URLSessionTask: TaskDelegate] = [:]
16+
17+
final class TaskDelegate: NSObject {
18+
private let configuration: HTTPClientConfiguration
19+
private let callback: (Result<HTTPClient.Response, Error>) -> Void
20+
private var accumulatedData: Data = Data()
21+
22+
required init(configuration: HTTPClientConfiguration, callback: @escaping (Result<HTTPClient.Response, Error>) -> Void) {
23+
self.configuration = configuration
24+
self.callback = callback
25+
}
1626
}
1727

28+
public required init(configuration: URLSessionConfiguration = .default) {
29+
super.init()
30+
self.session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
31+
}
32+
}
33+
34+
extension URLSessionHTTPClient: HTTPClientProtocol {
1835
public func execute(_ request: HTTPClient.Request, callback: @escaping (Result<HTTPClient.Response, Error>) -> Void) {
19-
let session = URLSession(configuration: self.configuration)
20-
let task = session.dataTask(with: request.urlRequest()) { data, response, error in
21-
if let error = error {
22-
callback(.failure(error))
23-
} else if let response = response as? HTTPURLResponse {
24-
callback(.success(response.response(body: data)))
25-
} else {
26-
callback(.failure(HTTPClientError.invalidResponse))
36+
let task = session.dataTask(with: request.urlRequest())
37+
taskDelegates[task] = TaskDelegate(configuration: configuration, callback: callback)
38+
39+
task.resume()
40+
}
41+
}
42+
43+
extension URLSessionHTTPClient: URLSessionDataDelegate {
44+
public func urlSession(_ session: URLSession,
45+
task: URLSessionTask,
46+
willPerformHTTPRedirection response: HTTPURLResponse,
47+
newRequest request: URLRequest,
48+
completionHandler: @escaping (URLRequest?) -> Void)
49+
{
50+
guard let delegate = taskDelegates[task] else {
51+
return completionHandler(request)
52+
}
53+
54+
delegate.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request, completionHandler: completionHandler)
55+
}
56+
57+
public func urlSession(_ session: URLSession,
58+
task: URLSessionTask,
59+
didCompleteWithError error: Error?)
60+
{
61+
guard let delegate = taskDelegates[task] else { return }
62+
defer { taskDelegates[task] = nil }
63+
64+
delegate.urlSession(session, task: task, didCompleteWithError: error)
65+
}
66+
67+
public func urlSession(_ session: URLSession,
68+
dataTask: URLSessionDataTask,
69+
didReceive data: Data)
70+
{
71+
guard let delegate = taskDelegates[dataTask] else { return }
72+
delegate.urlSession(session, dataTask: dataTask, didReceive: data)
73+
}
74+
}
75+
76+
77+
extension URLSessionHTTPClient.TaskDelegate: URLSessionDataDelegate {
78+
public func urlSession(_ session: URLSession,
79+
task: URLSessionTask,
80+
willPerformHTTPRedirection response: HTTPURLResponse,
81+
newRequest request: URLRequest,
82+
completionHandler: @escaping (URLRequest?) -> Void)
83+
{
84+
if configuration.followRedirects {
85+
completionHandler(request)
86+
} else {
87+
completionHandler(nil)
88+
89+
configuration.callbackQueue.async {
90+
self.callback(.success(response.response(body: nil)))
2791
}
2892
}
29-
task.resume()
93+
}
94+
95+
public func urlSession(_ session: URLSession,
96+
task: URLSessionTask,
97+
didCompleteWithError error: Error?)
98+
{
99+
if let error = error {
100+
configuration.callbackQueue.async {
101+
self.callback(.failure(error))
102+
}
103+
} else if let response = task.response as? HTTPURLResponse {
104+
let body = accumulatedData.isEmpty ? nil : accumulatedData
105+
configuration.callbackQueue.async {
106+
self.callback(.success(response.response(body: body)))
107+
}
108+
} else {
109+
configuration.callbackQueue.async {
110+
self.callback(.failure(HTTPClientError.invalidResponse))
111+
}
112+
}
113+
}
114+
115+
public func urlSession(_ session: URLSession,
116+
dataTask: URLSessionDataTask,
117+
didReceive data: Data)
118+
{
119+
accumulatedData.append(data)
30120
}
31121
}
32122

123+
// MARK: -
124+
33125
extension HTTPClient.Request {
34126
func urlRequest() -> URLRequest {
35127
var request = URLRequest(url: self.url)

Diff for: Sources/Basics/HTTPClient.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ public struct HTTPClient: HTTPClientProtocol {
5353
public init(configuration: HTTPClientConfiguration = .init(), handler: Handler? = nil, diagnosticsEngine: DiagnosticsEngine? = nil) {
5454
self.configuration = configuration
5555
self.diagnosticsEngine = diagnosticsEngine
56-
// FIXME: inject platform specific implementation here
57-
self.underlying = handler ?? URLSessionHTTPClient().execute
56+
if let handler = handler {
57+
self.underlying = handler
58+
} else {
59+
// FIXME: inject platform-specific implementation here
60+
let client = URLSessionHTTPClient()
61+
client.configuration = configuration
62+
self.underlying = client.execute
63+
}
5864
}
5965

6066
public func execute(_ request: Request, callback: @escaping (Result<Response, Error>) -> Void) {
@@ -207,13 +213,15 @@ public struct HTTPClientConfiguration {
207213
public var requestTimeout: DispatchTimeInterval?
208214
public var retryStrategy: HTTPClientRetryStrategy?
209215
public var circuitBreakerStrategy: HTTPClientCircuitBreakerStrategy?
216+
public var followRedirects: Bool
210217
public var callbackQueue: DispatchQueue
211218

212219
public init() {
213220
self.requestHeaders = .none
214221
self.requestTimeout = .none
215222
self.retryStrategy = .none
216223
self.circuitBreakerStrategy = .none
224+
self.followRedirects = true
217225
self.callbackQueue = .global()
218226
}
219227
}

0 commit comments

Comments
 (0)