Skip to content

Commit 2848911

Browse files
author
Johannes Weiss
committed
HTTPClient.shared a globally shared singleton & .browserLike configuration
1 parent 2ff11f5 commit 2848911

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed
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/HTTPClient.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public class HTTPClient {
7878

7979
private var state: State
8080
private let stateLock = NIOLock()
81+
private let canBeShutDown: Bool
8182

8283
internal static let loggingDisabled = Logger(label: "AHC-do-not-log", factory: { _ in SwiftLogNoOpLogHandler() })
8384

@@ -133,9 +134,20 @@ public class HTTPClient {
133134
/// - eventLoopGroup: The `EventLoopGroup` that the ``HTTPClient`` will use.
134135
/// - configuration: Client configuration.
135136
/// - backgroundActivityLogger: The `Logger` that will be used to log background any activity that's not associated with a request.
136-
public required init(eventLoopGroup: any EventLoopGroup,
137-
configuration: Configuration = Configuration(),
138-
backgroundActivityLogger: Logger) {
137+
public convenience init(eventLoopGroup: any EventLoopGroup,
138+
configuration: Configuration = Configuration(),
139+
backgroundActivityLogger: Logger) {
140+
self.init(eventLoopGroup: eventLoopGroup,
141+
configuration: configuration,
142+
backgroundActivityLogger: backgroundActivityLogger,
143+
canBeShutDown: true)
144+
}
145+
146+
internal required init(eventLoopGroup: EventLoopGroup,
147+
configuration: Configuration = Configuration(),
148+
backgroundActivityLogger: Logger,
149+
canBeShutDown: Bool) {
150+
self.canBeShutDown = canBeShutDown
139151
self.eventLoopGroup = eventLoopGroup
140152
self.configuration = configuration
141153
self.poolManager = HTTPConnectionPool.Manager(
@@ -247,6 +259,12 @@ public class HTTPClient {
247259
}
248260

249261
private func shutdown(requiresCleanClose: Bool, queue: DispatchQueue, _ callback: @escaping ShutdownCallback) {
262+
guard self.canBeShutDown else {
263+
queue.async {
264+
callback(HTTPClientError.shutdownUnsupported)
265+
}
266+
return
267+
}
250268
do {
251269
try self.stateLock.withLock {
252270
guard case .upAndRunning = self.state else {
@@ -1069,6 +1087,7 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
10691087
case getConnectionFromPoolTimeout
10701088
case deadlineExceeded
10711089
case httpEndReceivedAfterHeadWith1xx
1090+
case shutdownUnsupported
10721091
}
10731092

10741093
private var code: Code
@@ -1150,6 +1169,8 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
11501169
return "Deadline exceeded"
11511170
case .httpEndReceivedAfterHeadWith1xx:
11521171
return "HTTP end received after head with 1xx"
1172+
case .shutdownUnsupported:
1173+
return "The global singleton HTTP client cannot be shut down"
11531174
}
11541175
}
11551176

@@ -1214,6 +1235,11 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
12141235
return HTTPClientError(code: .serverOfferedUnsupportedApplicationProtocol(proto))
12151236
}
12161237

1238+
/// The globally shared singleton ``HTTPClient`` cannot be shut down.
1239+
public static var shutdownUnsupported: HTTPClientError {
1240+
return HTTPClientError(code: .shutdownUnsupported)
1241+
}
1242+
12171243
/// The request deadline was exceeded. The request was cancelled because of this.
12181244
public static let deadlineExceeded = HTTPClientError(code: .deadlineExceeded)
12191245

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 {
16+
/// A globally shared, singleton ``HTTPClient``.
17+
///
18+
/// The returned client uses the following settings:
19+
/// - configuration is ``HTTPClient/Configuration/browserLike`` (matching the platform's default/prevalent browser as well as possible)
20+
/// - `EventLoopGroup` is ``HTTPClient/defaultEventLoopGroup`` (matching the platform default)
21+
/// - logging is disabled
22+
public static var shared: HTTPClient {
23+
return globallySharedHTTPClient
24+
}
25+
}
26+
27+
private let globallySharedHTTPClient: HTTPClient = {
28+
let httpClient = HTTPClient(
29+
eventLoopGroup: HTTPClient.defaultEventLoopGroup,
30+
configuration: .browserLike,
31+
backgroundActivityLogger: HTTPClient.loggingDisabled,
32+
canBeShutDown: false
33+
)
34+
return httpClient
35+
}()

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,4 +3525,15 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
35253525
let response = try client.get(url: self.defaultHTTPBinURLPrefix + "get").wait()
35263526
XCTAssertEqual(.ok, response.status)
35273527
}
3528+
3529+
func testSingletonClientWorks() throws {
3530+
let response = try HTTPClient.shared.get(url: self.defaultHTTPBinURLPrefix + "get").wait()
3531+
XCTAssertEqual(.ok, response.status)
3532+
}
3533+
3534+
func testSingletonClientCannotBeShutDown() {
3535+
XCTAssertThrowsError(try HTTPClient.shared.shutdown().wait()) { error in
3536+
XCTAssertEqual(.shutdownUnsupported, error as? HTTPClientError)
3537+
}
3538+
}
35283539
}

0 commit comments

Comments
 (0)