Skip to content

Commit ecf4bca

Browse files
author
Johannes Weiss
committed
HTTPClient.shared a globally shared singleton & .browserLike configuration
1 parent 0c1d00d commit ecf4bca

File tree

6 files changed

+135
-84
lines changed

6 files changed

+135
-84
lines changed

README.md

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,9 @@ The code snippet below illustrates how to make a simple GET request to a remote
3030
```swift
3131
import AsyncHTTPClient
3232

33-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
34-
3533
/// MARK: - Using Swift Concurrency
3634
let request = HTTPClientRequest(url: "https://apple.com/")
37-
let response = try await httpClient.execute(request, timeout: .seconds(30))
35+
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
3836
print("HTTP head", response)
3937
if response.status == .ok {
4038
let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB
@@ -45,7 +43,7 @@ if response.status == .ok {
4543

4644

4745
/// MARK: - Using SwiftNIO EventLoopFuture
48-
httpClient.get(url: "https://apple.com/").whenComplete { result in
46+
HTTPClient.shared.get(url: "https://apple.com/").whenComplete { result in
4947
switch result {
5048
case .failure(let error):
5149
// process error
@@ -59,7 +57,8 @@ httpClient.get(url: "https://apple.com/").whenComplete { result in
5957
}
6058
```
6159

62-
You should always shut down `HTTPClient` instances you created using `try httpClient.shutdown()`. Please note that you must not call `httpClient.shutdown` before all requests of the HTTP client have finished, or else the in-flight requests will likely fail because their network connections are interrupted.
60+
If you create your own `HTTPClient` instances, you should shut down using `httpClient.shutdown()` when you're done with them. Failing to do so will leak resources.
61+
Please note that you must not call `httpClient.shutdown` before all requests of the HTTP client have finished, or else the in-flight requests will likely fail because their network connections are interrupted.
6362

6463
### async/await examples
6564

@@ -74,14 +73,13 @@ The default HTTP Method is `GET`. In case you need to have more control over the
7473
```swift
7574
import AsyncHTTPClient
7675

77-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
7876
do {
7977
var request = HTTPClientRequest(url: "https://apple.com/")
8078
request.method = .POST
8179
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
8280
request.body = .bytes(ByteBuffer(string: "some data"))
8381

84-
let response = try await httpClient.execute(request, timeout: .seconds(30))
82+
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
8583
if response.status == .ok {
8684
// handle response
8785
} else {
@@ -90,25 +88,18 @@ do {
9088
} catch {
9189
// handle error
9290
}
93-
// it's important to shutdown the httpClient after all requests are done, even if one failed
94-
try await httpClient.shutdown()
9591
```
9692

9793
#### Using SwiftNIO EventLoopFuture
9894

9995
```swift
10096
import AsyncHTTPClient
10197

102-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
103-
defer {
104-
try? httpClient.syncShutdown()
105-
}
106-
10798
var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST)
10899
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
109100
request.body = .string("some-body")
110101

111-
httpClient.execute(request: request).whenComplete { result in
102+
HTTPClient.shared.execute(request: request).whenComplete { result in
112103
switch result {
113104
case .failure(let error):
114105
// process error
@@ -123,7 +114,9 @@ httpClient.execute(request: request).whenComplete { result in
123114
```
124115

125116
### Redirects following
126-
Enable follow-redirects behavior using the client configuration:
117+
118+
The globally shared instance `HTTPClient.shared` follows redirects by default. If you create your own `HTTPClient`, you can enable the follow-redirects behavior using the client configuration:
119+
127120
```swift
128121
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton,
129122
configuration: HTTPClient.Configuration(followRedirects: true))
@@ -147,10 +140,9 @@ The following example demonstrates how to count the number of bytes in a streami
147140

148141
#### Using Swift Concurrency
149142
```swift
150-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
151143
do {
152144
let request = HTTPClientRequest(url: "https://apple.com/")
153-
let response = try await httpClient.execute(request, timeout: .seconds(30))
145+
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
154146
print("HTTP head", response)
155147

156148
// if defined, the content-length headers announces the size of the body
@@ -173,8 +165,6 @@ do {
173165
} catch {
174166
print("request failed:", error)
175167
}
176-
// it is important to shutdown the httpClient after all requests are done, even if one failed
177-
try await httpClient.shutdown()
178168
```
179169

180170
#### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture
@@ -234,7 +224,7 @@ class CountingDelegate: HTTPClientResponseDelegate {
234224
let request = try HTTPClient.Request(url: "https://apple.com/")
235225
let delegate = CountingDelegate()
236226

237-
httpClient.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
227+
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
238228
print(count)
239229
}
240230
```
@@ -247,7 +237,6 @@ asynchronously, while reporting the download progress at the same time, like in
247237
example:
248238

249239
```swift
250-
let client = HTTPClient(eventLoopGroupProvider: .singleton)
251240
let request = try HTTPClient.Request(
252241
url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml"
253242
)
@@ -259,7 +248,7 @@ let delegate = try FileDownloadDelegate(path: "/tmp/latest-build.yml", reportPro
259248
print("Downloaded \($0.receivedBytes) bytes so far")
260249
})
261250

262-
client.execute(request: request, delegate: delegate).futureResult
251+
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult
263252
.whenSuccess { progress in
264253
if let totalBytes = progress.totalBytes {
265254
print("Final total bytes count: \(totalBytes)")
@@ -271,8 +260,7 @@ client.execute(request: request, delegate: delegate).futureResult
271260
### Unix Domain Socket Paths
272261
Connecting to servers bound to socket paths is easy:
273262
```swift
274-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
275-
httpClient.execute(
263+
HTTPClient.shared.execute(
276264
.GET,
277265
socketPath: "/tmp/myServer.socket",
278266
urlPath: "/path/to/resource"
@@ -281,8 +269,7 @@ httpClient.execute(
281269

282270
Connecting over TLS to a unix domain socket path is possible as well:
283271
```swift
284-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
285-
httpClient.execute(
272+
HTTPClient.shared.execute(
286273
.POST,
287274
secureSocketPath: "/tmp/myServer.socket",
288275
urlPath: "/path/to/resource",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
extension HTTPClient.Configuration {
16+
/// A ``HTTPClient/Configuration`` that tries to mimick the platform's default or prevalent browser as closely as possible.
17+
///
18+
/// Platform's default/prevalent browsers that we're trying to match (these might change over time):
19+
/// - macOS: Safari
20+
/// - iOS: Safari
21+
/// - Android: Google Chrome
22+
/// - Linux (non-Android): Google Chrome
23+
///
24+
/// - note: Don't rely on the values in here never changing, they will be adapted to better match the platform's default/prevalent browser.
25+
public static var browserLike: HTTPClient.Configuration {
26+
// To start with, let's go with these values. Obtained from Firefox's config.
27+
return HTTPClient.Configuration(
28+
certificateVerification: .fullVerification,
29+
redirectConfiguration: .follow(max: 20, allowCycles: false),
30+
timeout: Timeout(connect: .seconds(90), read: .seconds(90)),
31+
connectionPool: .seconds(600),
32+
proxy: nil,
33+
ignoreUncleanSSLShutdown: false,
34+
decompression: .enabled(limit: .ratio(10)),
35+
backgroundActivityLogger: nil
36+
)
37+
}
38+
}

Sources/AsyncHTTPClient/Docs.docc/index.md

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ The code snippet below illustrates how to make a simple GET request to a remote
3434
```swift
3535
import AsyncHTTPClient
3636

37-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
38-
defer {
39-
try! httpClient.shutdown().wait()
40-
}
41-
4237
/// MARK: - Using Swift Concurrency
4338
let request = HTTPClientRequest(url: "https://apple.com/")
4439
let response = try await httpClient.execute(request, timeout: .seconds(30))
@@ -52,7 +47,7 @@ if response.status == .ok {
5247

5348

5449
/// MARK: - Using SwiftNIO EventLoopFuture
55-
httpClient.get(url: "https://apple.com/").whenComplete { result in
50+
HTTPClient.shared.get(url: "https://apple.com/").whenComplete { result in
5651
switch result {
5752
case .failure(let error):
5853
// process error
@@ -81,18 +76,13 @@ The default HTTP Method is `GET`. In case you need to have more control over the
8176
```swift
8277
import AsyncHTTPClient
8378

84-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
85-
defer {
86-
try! httpClient.syncShutdown()
87-
}
88-
8979
do {
9080
var request = HTTPClientRequest(url: "https://apple.com/")
9181
request.method = .POST
9282
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
9383
request.body = .bytes(ByteBuffer(string: "some data"))
9484

95-
let response = try await httpClient.execute(request, timeout: .seconds(30))
85+
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
9686
if response.status == .ok {
9787
// handle response
9888
} else {
@@ -101,25 +91,18 @@ do {
10191
} catch {
10292
// handle error
10393
}
104-
// it's important to shutdown the httpClient after all requests are done, even if one failed
105-
try await httpClient.shutdown()
10694
```
10795

10896
#### Using SwiftNIO EventLoopFuture
10997

11098
```swift
11199
import AsyncHTTPClient
112100

113-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
114-
defer {
115-
try! httpClient.syncShutdown()
116-
}
117-
118101
var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST)
119102
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
120103
request.body = .string("some-body")
121104

122-
httpClient.execute(request: request).whenComplete { result in
105+
HTTPClient.shared.execute(request: request).whenComplete { result in
123106
switch result {
124107
case .failure(let error):
125108
// process error
@@ -158,14 +141,9 @@ The following example demonstrates how to count the number of bytes in a streami
158141

159142
##### Using Swift Concurrency
160143
```swift
161-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
162-
defer {
163-
try! httpClient.syncShutdown()
164-
}
165-
166144
do {
167145
let request = HTTPClientRequest(url: "https://apple.com/")
168-
let response = try await httpClient.execute(request, timeout: .seconds(30))
146+
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
169147
print("HTTP head", response)
170148

171149
// if defined, the content-length headers announces the size of the body
@@ -188,8 +166,6 @@ do {
188166
} catch {
189167
print("request failed:", error)
190168
}
191-
// it is important to shutdown the httpClient after all requests are done, even if one failed
192-
try await httpClient.shutdown()
193169
```
194170

195171
##### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture
@@ -262,11 +238,6 @@ asynchronously, while reporting the download progress at the same time, like in
262238
example:
263239

264240
```swift
265-
let client = HTTPClient(eventLoopGroupProvider: .singleton)
266-
defer {
267-
try! httpClient.syncShutdown()
268-
}
269-
270241
let request = try HTTPClient.Request(
271242
url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml"
272243
)
@@ -278,7 +249,7 @@ let delegate = try FileDownloadDelegate(path: "/tmp/latest-build.yml", reportPro
278249
print("Downloaded \($0.receivedBytes) bytes so far")
279250
})
280251

281-
client.execute(request: request, delegate: delegate).futureResult
252+
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult
282253
.whenSuccess { progress in
283254
if let totalBytes = progress.totalBytes {
284255
print("Final total bytes count: \(totalBytes)")
@@ -290,12 +261,7 @@ client.execute(request: request, delegate: delegate).futureResult
290261
#### Unix Domain Socket Paths
291262
Connecting to servers bound to socket paths is easy:
292263
```swift
293-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
294-
defer {
295-
try! httpClient.syncShutdown()
296-
}
297-
298-
httpClient.execute(
264+
HTTPClient.shared.execute(
299265
.GET,
300266
socketPath: "/tmp/myServer.socket",
301267
urlPath: "/path/to/resource"
@@ -304,12 +270,7 @@ httpClient.execute(
304270

305271
Connecting over TLS to a unix domain socket path is possible as well:
306272
```swift
307-
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
308-
defer {
309-
try! httpClient.syncShutdown()
310-
}
311-
312-
httpClient.execute(
273+
HTTPClient.shared.execute(
313274
.POST,
314275
secureSocketPath: "/tmp/myServer.socket",
315276
urlPath: "/path/to/resource",

0 commit comments

Comments
 (0)