Skip to content

Commit

Permalink
HTTPClient.shared a globally shared singleton & .browserLike configur…
Browse files Browse the repository at this point in the history
…ation (#705)

Co-authored-by: Johannes Weiss <[email protected]>
  • Loading branch information
weissi and Johannes Weiss authored Apr 3, 2024
1 parent 36292f9 commit e0977cf
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 92 deletions.
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

0 comments on commit e0977cf

Please sign in to comment.