-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathURLSessionHTTPClientAdapter.swift
More file actions
114 lines (96 loc) · 3.8 KB
/
Copy pathURLSessionHTTPClientAdapter.swift
File metadata and controls
114 lines (96 loc) · 3.8 KB
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
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if !os(Linux)
/// Adapter that implements HTTPClient protocol using URLSession
public class URLSessionHTTPClientAdapter: HTTPClient {
/// Initializes a new URLSessionHTTPClientAdapter with the provided URLSession
/// - Parameter urlSession: The URLSession instance to use. Defaults to `URLSession.shared`.
public init(urlSession: URLSession = .shared) {
self.urlSession = urlSession
}
/// Fetches data for a given HTTP request
/// - Parameter request: The HTTP request to perform
/// - Returns: A tuple containing the data and HTTP response
public func data(for request: HTTPRequest) async throws -> (Data, HTTPResponse) {
let urlRequest = try createURLRequest(from: request)
let (data, urlResponse) = try await urlSession.data(for: urlRequest)
guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
throw URLError(.badServerResponse) // Or a custom error
}
let response = HTTPResponse(
statusCode: httpURLResponse.statusCode,
headers: convertHeaders(httpURLResponse.allHeaderFields))
return (data, response)
}
/// Fetches a byte stream for a given HTTP request
/// - Parameter request: The HTTP request to perform
/// - Returns: A tuple containing the byte stream and HTTP response
public func bytes(for request: HTTPRequest) async throws -> (HTTPByteStream, HTTPResponse) {
let urlRequest = try createURLRequest(from: request)
let (asyncBytes, urlResponse) = try await urlSession.bytes(for: urlRequest)
guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
throw URLError(.badServerResponse) // Or a custom error
}
let response = HTTPResponse(
statusCode: httpURLResponse.statusCode,
headers: convertHeaders(httpURLResponse.allHeaderFields))
let contentType = httpURLResponse.value(forHTTPHeaderField: "Content-Type") ?? httpURLResponse.mimeType ?? ""
if contentType.lowercased().contains("text/event-stream") {
let stream = AsyncThrowingStream<String, Error> { continuation in
Task {
do {
for try await line in asyncBytes.lines {
continuation.yield(line)
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
}
return (.lines(stream), response)
} else {
let byteStream = AsyncThrowingStream<UInt8, Error> { continuation in
Task {
do {
for try await byte in asyncBytes {
continuation.yield(byte)
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
}
return (.bytes(byteStream), response)
}
}
private let urlSession: URLSession
/// Converts our HTTPRequest to URLRequest
/// - Parameter request: Our HTTPRequest
/// - Returns: URLRequest
private func createURLRequest(from request: HTTPRequest) throws -> URLRequest {
var urlRequest = URLRequest(url: request.url)
urlRequest.httpMethod = request.method.rawValue
for (key, value) in request.headers {
urlRequest.setValue(value, forHTTPHeaderField: key)
}
urlRequest.httpBody = request.body
return urlRequest
}
/// Converts HTTPURLResponse headers to a dictionary [String: String]
/// - Parameter headers: The headers from HTTPURLResponse (i.e. `allHeaderFields`)
/// - Returns: Dictionary of header name-value pairs
private func convertHeaders(_ headers: [AnyHashable: Any]) -> [String: String] {
var result = [String: String]()
for (key, value) in headers {
if let keyString = key as? String, let valueString = value as? String {
result[keyString] = valueString
}
}
return result
}
}
#endif