Skip to content

#700 & #701: HTTPClient.shared a globally shared singleton #705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 14 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ The code snippet below illustrates how to make a simple GET request to a remote
```swift
import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)

/// MARK: - Using Swift Concurrency
let request = HTTPClientRequest(url: "https://apple.com/")
let response = try await httpClient.execute(request, timeout: .seconds(30))
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
print("HTTP head", response)
if response.status == .ok {
let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB
Expand All @@ -45,7 +43,7 @@ if response.status == .ok {


/// MARK: - Using SwiftNIO EventLoopFuture
httpClient.get(url: "https://apple.com/").whenComplete { result in
HTTPClient.shared.get(url: "https://apple.com/").whenComplete { result in
switch result {
case .failure(let error):
// process error
Expand All @@ -59,7 +57,8 @@ httpClient.get(url: "https://apple.com/").whenComplete { result in
}
```

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.
If you create your own `HTTPClient` instances, you should shut them down using `httpClient.shutdown()` when you're done using them. Failing to do so will leak resources.
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.

### async/await examples

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

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

let response = try await httpClient.execute(request, timeout: .seconds(30))
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
if response.status == .ok {
// handle response
} else {
Expand All @@ -90,26 +88,18 @@ do {
} catch {
// handle error
}
// it's important to shutdown the httpClient after all requests are done, even if one failed
try await httpClient.shutdown()
```

#### Using SwiftNIO EventLoopFuture

```swift
import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST)
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
request.body = .string("some-body")

httpClient.execute(request: request).whenComplete { result in
HTTPClient.shared.execute(request: request).whenComplete { result in
switch result {
case .failure(let error):
// process error
Expand All @@ -124,7 +114,9 @@ httpClient.execute(request: request).whenComplete { result in
```

### Redirects following
Enable follow-redirects behavior using the client configuration:

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:

```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton,
configuration: HTTPClient.Configuration(followRedirects: true))
Expand All @@ -148,10 +140,9 @@ The following example demonstrates how to count the number of bytes in a streami

#### Using Swift Concurrency
```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
do {
let request = HTTPClientRequest(url: "https://apple.com/")
let response = try await httpClient.execute(request, timeout: .seconds(30))
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
print("HTTP head", response)

// if defined, the content-length headers announces the size of the body
Expand All @@ -174,8 +165,6 @@ do {
} catch {
print("request failed:", error)
}
// it is important to shutdown the httpClient after all requests are done, even if one failed
try await httpClient.shutdown()
```

#### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture
Expand Down Expand Up @@ -235,7 +224,7 @@ class CountingDelegate: HTTPClientResponseDelegate {
let request = try HTTPClient.Request(url: "https://apple.com/")
let delegate = CountingDelegate()

httpClient.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
print(count)
}
```
Expand All @@ -248,7 +237,6 @@ asynchronously, while reporting the download progress at the same time, like in
example:

```swift
let client = HTTPClient(eventLoopGroupProvider: .singleton)
let request = try HTTPClient.Request(
url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml"
)
Expand All @@ -260,7 +248,7 @@ let delegate = try FileDownloadDelegate(path: "/tmp/latest-build.yml", reportPro
print("Downloaded \($0.receivedBytes) bytes so far")
})

client.execute(request: request, delegate: delegate).futureResult
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult
.whenSuccess { progress in
if let totalBytes = progress.totalBytes {
print("Final total bytes count: \(totalBytes)")
Expand All @@ -272,8 +260,7 @@ client.execute(request: request, delegate: delegate).futureResult
### Unix Domain Socket Paths
Connecting to servers bound to socket paths is easy:
```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
httpClient.execute(
HTTPClient.shared.execute(
.GET,
socketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource"
Expand All @@ -282,8 +269,7 @@ httpClient.execute(

Connecting over TLS to a unix domain socket path is possible as well:
```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
httpClient.execute(
HTTPClient.shared.execute(
.POST,
secureSocketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource",
Expand Down
41 changes: 41 additions & 0 deletions Sources/AsyncHTTPClient/Configuration+BrowserLike.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2023 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

extension HTTPClient.Configuration {
/// The ``HTTPClient/Configuration`` for ``HTTPClient/shared`` which tries to mimic the platform's default or prevalent browser as closely as possible.
///
/// Don't rely on specific values of this configuration as they're subject to change. You can rely on them being somewhat sensible though.
///
/// - note: At present, this configuration is nowhere close to a real browser configuration but in case of disagreements we will choose values that match
/// the default browser as closely as possible.
///
/// Platform's default/prevalent browsers that we're trying to match (these might change over time):
/// - macOS: Safari
/// - iOS: Safari
/// - Android: Google Chrome
/// - Linux (non-Android): Google Chrome
public static var singletonConfiguration: HTTPClient.Configuration {
// To start with, let's go with these values. Obtained from Firefox's config.
return HTTPClient.Configuration(
certificateVerification: .fullVerification,
redirectConfiguration: .follow(max: 20, allowCycles: false),
timeout: Timeout(connect: .seconds(90), read: .seconds(90)),
connectionPool: .seconds(600),
proxy: nil,
ignoreUncleanSSLShutdown: false,
decompression: .enabled(limit: .ratio(10)),
backgroundActivityLogger: nil
)
}
}
60 changes: 7 additions & 53 deletions Sources/AsyncHTTPClient/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ The code snippet below illustrates how to make a simple GET request to a remote
```swift
import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

/// MARK: - Using Swift Concurrency
let request = HTTPClientRequest(url: "https://apple.com/")
let response = try await httpClient.execute(request, timeout: .seconds(30))
Expand All @@ -53,7 +47,7 @@ if response.status == .ok {


/// MARK: - Using SwiftNIO EventLoopFuture
httpClient.get(url: "https://apple.com/").whenComplete { result in
HTTPClient.shared.get(url: "https://apple.com/").whenComplete { result in
switch result {
case .failure(let error):
// process error
Expand Down Expand Up @@ -82,19 +76,13 @@ The default HTTP Method is `GET`. In case you need to have more control over the
```swift
import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

do {
var request = HTTPClientRequest(url: "https://apple.com/")
request.method = .POST
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
request.body = .bytes(ByteBuffer(string: "some data"))

let response = try await httpClient.execute(request, timeout: .seconds(30))
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
if response.status == .ok {
// handle response
} else {
Expand All @@ -103,26 +91,18 @@ do {
} catch {
// handle error
}
// it's important to shutdown the httpClient after all requests are done, even if one failed
try await httpClient.shutdown()
```

#### Using SwiftNIO EventLoopFuture

```swift
import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST)
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
request.body = .string("some-body")

httpClient.execute(request: request).whenComplete { result in
HTTPClient.shared.execute(request: request).whenComplete { result in
switch result {
case .failure(let error):
// process error
Expand Down Expand Up @@ -161,15 +141,9 @@ The following example demonstrates how to count the number of bytes in a streami

##### Using Swift Concurrency
```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

do {
let request = HTTPClientRequest(url: "https://apple.com/")
let response = try await httpClient.execute(request, timeout: .seconds(30))
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
print("HTTP head", response)

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

##### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture
Expand Down Expand Up @@ -266,12 +238,6 @@ asynchronously, while reporting the download progress at the same time, like in
example:

```swift
let client = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

let request = try HTTPClient.Request(
url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml"
)
Expand All @@ -283,7 +249,7 @@ let delegate = try FileDownloadDelegate(path: "/tmp/latest-build.yml", reportPro
print("Downloaded \($0.receivedBytes) bytes so far")
})

client.execute(request: request, delegate: delegate).futureResult
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult
.whenSuccess { progress in
if let totalBytes = progress.totalBytes {
print("Final total bytes count: \(totalBytes)")
Expand All @@ -295,13 +261,7 @@ client.execute(request: request, delegate: delegate).futureResult
#### Unix Domain Socket Paths
Connecting to servers bound to socket paths is easy:
```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

httpClient.execute(
HTTPClient.shared.execute(
.GET,
socketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource"
Expand All @@ -310,13 +270,7 @@ httpClient.execute(

Connecting over TLS to a unix domain socket path is possible as well:
```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
// Shutdown is guaranteed to work if it's done precisely once (which is the case here).
try! httpClient.syncShutdown()
}

httpClient.execute(
HTTPClient.shared.execute(
.POST,
secureSocketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource",
Expand Down
Loading