66//
77
88import Foundation
9- #if canImport(FoundationNetworking)
10- import FoundationNetworking
11- #endif
9+ import AsyncHTTPClient
1210
1311public enum FetchError : Error , Sendable {
1412 case invalidResponse
1513 case invalidURL
1614 case timeout
15+ case invalidLambdaContext
1716}
1817
1918public func fetch( _ request: FetchRequest ) async throws -> FetchResponse {
@@ -42,56 +41,44 @@ public func fetch(_ request: FetchRequest) async throws -> FetchResponse {
4241 }
4342
4443 // Set request resources
45- var httpRequest = URLRequest ( url: url)
44+ var httpRequest = HTTPClientRequest ( url: url. absoluteString )
4645
4746 // Set request method
48- httpRequest. httpMethod = request. method. rawValue
49-
50- // Set the timeout interval
51- if let timeoutInterval = request. timeoutInterval {
52- httpRequest. timeoutInterval = timeoutInterval
53- }
47+ httpRequest. method = . init( rawValue: request. method. rawValue)
5448
5549 // Set default content type based on body
5650 if let contentType = request. body? . defaultContentType {
5751 let name = HTTPHeaderKey . contentType. rawValue
58- httpRequest. setValue ( request. headers [ name] ?? contentType, forHTTPHeaderField : name )
52+ httpRequest. headers . add ( name : name , value : request. headers [ name] ?? contentType)
5953 }
6054
6155 // Set headers
6256 for (key, value) in request. headers {
63- httpRequest. setValue ( value , forHTTPHeaderField : key )
57+ httpRequest. headers . add ( name : key , value : value )
6458 }
6559
6660 // Write bytes to body
6761 switch request. body {
6862 case . bytes( let bytes) :
69- httpRequest. httpBody = Data ( bytes)
63+ httpRequest. body = . bytes ( bytes)
7064 case . data( let data) :
71- httpRequest. httpBody = data
65+ httpRequest. body = . bytes ( data)
7266 case . text( let text) :
73- httpRequest. httpBody = Data ( text. utf8)
67+ httpRequest. body = . bytes ( text. utf8, length : . known ( text . utf8 . count ) )
7468 case . json( let json) :
75- httpRequest. httpBody = json
69+ httpRequest. body = . bytes ( json)
7670 case . none:
7771 break
7872 }
7973
80- let ( data, response) : ( Data , HTTPURLResponse ) = try await withCheckedThrowingContinuation { continuation in
81- let task = URLSession . shared. dataTask ( with: httpRequest) { data, response, error in
82- if let data, let response = response as? HTTPURLResponse {
83- continuation. resume ( returning: ( data, response) )
84- } else {
85- continuation. resume ( throwing: error ?? FetchError . invalidResponse)
86- }
87- }
88- task. resume ( )
89- }
74+ let httpClient = request. httpClient ?? HTTPClient . vercelClient
75+
76+ let response = try await httpClient. execute ( httpRequest, timeout: request. timeout ?? . seconds( 60 ) )
9077
9178 return FetchResponse (
92- body: data ,
93- headers: response. allHeaderFields as! [ String : String ] ,
94- status: response. statusCode ,
79+ body: response . body ,
80+ headers: response. headers . reduce ( into : [ : ] ) { $0 [ $1 . name ] = $1 . value } ,
81+ status: . init ( response. status . code ) ,
9582 url: url
9683 )
9784}
@@ -108,3 +95,39 @@ public func fetch(_ urlPath: String, _ options: FetchRequest.Options = .options(
10895 let request = FetchRequest ( url, options)
10996 return try await fetch ( request)
11097}
98+
99+ extension HTTPClient {
100+
101+ fileprivate static let vercelClient = HTTPClient (
102+ eventLoopGroup: HTTPClient . defaultEventLoopGroup,
103+ configuration: . vercelConfiguration
104+ )
105+ }
106+
107+ extension HTTPClient . Configuration {
108+ /// The ``HTTPClient/Configuration`` for ``HTTPClient/shared`` which tries to mimic the platform's default or prevalent browser as closely as possible.
109+ ///
110+ /// Don't rely on specific values of this configuration as they're subject to change. You can rely on them being somewhat sensible though.
111+ ///
112+ /// - note: At present, this configuration is nowhere close to a real browser configuration but in case of disagreements we will choose values that match
113+ /// the default browser as closely as possible.
114+ ///
115+ /// Platform's default/prevalent browsers that we're trying to match (these might change over time):
116+ /// - macOS: Safari
117+ /// - iOS: Safari
118+ /// - Android: Google Chrome
119+ /// - Linux (non-Android): Google Chrome
120+ fileprivate static var vercelConfiguration : HTTPClient . Configuration {
121+ // To start with, let's go with these values. Obtained from Firefox's config.
122+ return HTTPClient . Configuration (
123+ certificateVerification: . fullVerification,
124+ redirectConfiguration: . follow( max: 20 , allowCycles: false ) ,
125+ timeout: Timeout ( connect: . seconds( 90 ) , read: . seconds( 90 ) ) ,
126+ connectionPool: . seconds( 600 ) ,
127+ proxy: nil ,
128+ ignoreUncleanSSLShutdown: false ,
129+ decompression: . enabled( limit: . ratio( 10 ) ) ,
130+ backgroundActivityLogger: nil
131+ )
132+ }
133+ }
0 commit comments