-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathAsyncHTTPClientAdapter.swift
More file actions
147 lines (129 loc) · 4.93 KB
/
Copy pathAsyncHTTPClientAdapter.swift
File metadata and controls
147 lines (129 loc) · 4.93 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
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
//
// AsyncHTTPClientAdapter.swift
// SwiftOpenAI
//
// Created by Joe Fabisevich on 5/18/25.
//
#if os(Linux)
import AsyncHTTPClient
import Foundation
import NIOCore
import NIOFoundationCompat
import NIOHTTP1
/// Adapter that implements HTTPClient protocol using AsyncHTTPClient
public class AsyncHTTPClientAdapter: HTTPClient {
/// Initializes a new AsyncHTTPClientAdapter with the provided AsyncHTTPClient
/// - Parameter client: The AsyncHTTPClient instance to use
public init(client: AsyncHTTPClient.HTTPClient) {
self.client = client
}
deinit {
shutdown()
}
/// Creates a new AsyncHTTPClientAdapter with a default configuration
/// - Returns: A new AsyncHTTPClientAdapter instance
public static func createDefault() -> AsyncHTTPClientAdapter {
let httpClient = AsyncHTTPClient.HTTPClient(
eventLoopGroupProvider: .singleton,
configuration: AsyncHTTPClient.HTTPClient.Configuration(
certificateVerification: .fullVerification,
timeout: .init(
connect: .seconds(30),
read: .seconds(30)),
backgroundActivityLogger: nil))
return AsyncHTTPClientAdapter(client: httpClient)
}
/// 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 asyncHTTPClientRequest = try createAsyncHTTPClientRequest(from: request)
let response = try await client.execute(asyncHTTPClientRequest, deadline: .now() + .seconds(60))
let body = try await response.body.collect(upTo: 100 * 1024 * 1024) // 100 MB max
let data = Data(buffer: body)
let httpResponse = HTTPResponse(
statusCode: Int(response.status.code),
headers: convertHeaders(response.headers))
return (data, httpResponse)
}
/// 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 asyncHTTPClientRequest = try createAsyncHTTPClientRequest(from: request)
let response = try await client.execute(asyncHTTPClientRequest, deadline: .now() + .seconds(60))
let contentType = response.headers.first(name: "content-type") ?? ""
let httpResponse = HTTPResponse(
statusCode: Int(response.status.code),
headers: convertHeaders(response.headers))
if contentType.lowercased().contains("text/event-stream") {
let stream = AsyncThrowingStream<String, Error> { continuation in
Task {
do {
for try await byteBuffer in response.body {
if let string = byteBuffer.getString(at: 0, length: byteBuffer.readableBytes) {
let lines = string.split(separator: "\n", omittingEmptySubsequences: false)
for line in lines {
continuation.yield(String(line))
}
}
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
}
return (.lines(stream), httpResponse)
} else {
let byteStream = AsyncThrowingStream<UInt8, Error> { continuation in
Task {
do {
for try await byteBuffer in response.body {
for byte in byteBuffer.readableBytesView {
continuation.yield(byte)
}
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
}
return (.bytes(byteStream), httpResponse)
}
}
/// Properly shutdown the HTTP client
public func shutdown() {
try? client.shutdown().wait()
}
/// The underlying AsyncHTTPClient instance
private let client: AsyncHTTPClient.HTTPClient
/// Converts our HTTPRequest to AsyncHTTPClient's Request
/// - Parameter request: Our HTTPRequest
/// - Returns: AsyncHTTPClient Request
private func createAsyncHTTPClientRequest(from request: HTTPRequest) throws -> HTTPClientRequest {
var asyncHTTPClientRequest = HTTPClientRequest(url: request.url.absoluteString)
asyncHTTPClientRequest.method = NIOHTTP1.HTTPMethod(rawValue: request.method.rawValue)
// Add headers
for (key, value) in request.headers {
asyncHTTPClientRequest.headers.add(name: key, value: value)
}
// Add body if present
if let body = request.body {
asyncHTTPClientRequest.body = .bytes(body)
}
return asyncHTTPClientRequest
}
/// Converts NIOHTTP1 headers to a dictionary
/// - Parameter headers: NIOHTTP1 HTTPHeaders
/// - Returns: Dictionary of header name-value pairs
private func convertHeaders(_ headers: HTTPHeaders) -> [String: String] {
var result = [String: String]()
for header in headers {
result[header.name] = header.value
}
return result
}
}
#endif