本软件包提供了一个基于 SwiftNIO 构建的 HTTP 客户端库。
此库提供以下功能:
在您的 Package.swift
中添加以下条目以开始使用 HTTPClient
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0")
并将 AsyncHTTPClient
依赖项添加到您的目标。
.target(name: "MyApp", dependencies: [.product(name: "AsyncHTTPClient", package: "async-http-client")]),
下面的代码片段说明了如何向远程服务器发出简单的 GET 请求。
import AsyncHTTPClient
/// MARK: - Using Swift Concurrency
let request = HTTPClientRequest(url: "https://apple.com/")
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
// handle body
} else {
// handle remote error
}
/// MARK: - Using SwiftNIO EventLoopFuture
HTTPClient.shared.get(url: "https://apple.com/").whenComplete { result in
switch result {
case .failure(let error):
// process error
case .success(let response):
if response.status == .ok {
// handle response
} else {
// handle remote error
}
}
}
如果您创建了自己的 HTTPClient
实例,您应该在使用完毕后使用 httpClient.shutdown()
关闭它们。不这样做会导致资源泄漏。请注意,您不得在 HTTP 客户端的所有请求完成之前调用 httpClient.shutdown
,否则正在进行的请求很可能会因为其网络连接中断而失败。
async/await API 的示例可以在此仓库的 Examples
文件夹中找到。
默认的 HTTP 方法是 GET
。如果您需要更好地控制方法,或者想要添加标头或 body,请使用 HTTPClientRequest
结构体。
import AsyncHTTPClient
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.shared.execute(request, timeout: .seconds(30))
if response.status == .ok {
// handle response
} else {
// handle remote error
}
} catch {
// handle error
}
import AsyncHTTPClient
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.shared.execute(request: request).whenComplete { result in
switch result {
case .failure(let error):
// process error
case .success(let response):
if response.status == .ok {
// handle response
} else {
// handle remote error
}
}
}
全局共享实例 HTTPClient.shared
默认会跟随重定向。如果您创建了自己的 HTTPClient
,您可以使用客户端配置启用重定向跟随行为。
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton,
configuration: HTTPClient.Configuration(followRedirects: true))
超时(连接和读取)也可以使用客户端配置进行设置
let timeout = HTTPClient.Configuration.Timeout(connect: .seconds(1), read: .seconds(1))
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton,
configuration: HTTPClient.Configuration(timeout: timeout))
或在每个请求的基础上设置。
httpClient.execute(request: request, deadline: .now() + .milliseconds(1))
在处理大量数据时,至关重要的是流式传输响应 body,而不是在内存中聚合。以下示例演示了如何计算流式响应 body 中的字节数。
do {
let request = HTTPClientRequest(url: "https://apple.com/")
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
let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init)
var receivedBytes = 0
// asynchronously iterates over all body fragments
// this loop will automatically propagate backpressure correctly
for try await buffer in response.body {
// for this example, we are just interested in the size of the fragment
receivedBytes += buffer.readableBytes
if let expectedBytes = expectedBytes {
// if the body size is known, we calculate a progress indicator
let progress = Double(receivedBytes) / Double(expectedBytes)
print("progress: \(Int(progress * 100))%")
}
}
print("did receive \(receivedBytes) bytes")
} catch {
print("request failed:", error)
}
import NIOCore
import NIOHTTP1
class CountingDelegate: HTTPClientResponseDelegate {
typealias Response = Int
var count = 0
func didSendRequestHead(task: HTTPClient.Task<Response>, _ head: HTTPRequestHead) {
// this is executed right after request head was sent, called once
}
func didSendRequestPart(task: HTTPClient.Task<Response>, _ part: IOData) {
// this is executed when request body part is sent, could be called zero or more times
}
func didSendRequest(task: HTTPClient.Task<Response>) {
// this is executed when request is fully sent, called once
}
func didReceiveHead(
task: HTTPClient.Task<Response>,
_ head: HTTPResponseHead
) -> EventLoopFuture<Void> {
// this is executed when we receive HTTP response head part of the request
// (it contains response code and headers), called once in case backpressure
// is needed, all reads will be paused until returned future is resolved
return task.eventLoop.makeSucceededFuture(())
}
func didReceiveBodyPart(
task: HTTPClient.Task<Response>,
_ buffer: ByteBuffer
) -> EventLoopFuture<Void> {
// this is executed when we receive parts of the response body, could be called zero or more times
count += buffer.readableBytes
// in case backpressure is needed, all reads will be paused until returned future is resolved
return task.eventLoop.makeSucceededFuture(())
}
func didFinishRequest(task: HTTPClient.Task<Response>) throws -> Int {
// this is called when the request is fully read, called once
// this is where you return a result or throw any errors you require to propagate to the client
return count
}
func didReceiveError(task: HTTPClient.Task<Response>, _ error: Error) {
// this is called when we receive any network-related error, called once
}
}
let request = try HTTPClient.Request(url: "https://apple.com/")
let delegate = CountingDelegate()
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
print(count)
}
基于上面的 HTTPClientResponseDelegate
示例,您可以构建更复杂的委托,内置的 FileDownloadDelegate
就是其中之一。它允许异步流式传输下载的数据,同时报告下载进度,如下例所示。
let request = try HTTPClient.Request(
url: "https://swiftlang.cn/builds/development/ubuntu1804/latest-build.yml"
)
let delegate = try FileDownloadDelegate(path: "/tmp/latest-build.yml", reportProgress: {
if let totalBytes = $0.totalBytes {
print("Total bytes count: \(totalBytes)")
}
print("Downloaded \($0.receivedBytes) bytes so far")
})
HTTPClient.shared.execute(request: request, delegate: delegate).futureResult
.whenSuccess { progress in
if let totalBytes = progress.totalBytes {
print("Final total bytes count: \(totalBytes)")
}
print("Downloaded finished with \(progress.receivedBytes) bytes downloaded")
}
连接到绑定到套接字路径的服务器很容易。
HTTPClient.shared.execute(
.GET,
socketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource"
).whenComplete (...)
也可以通过 TLS 连接到 Unix 域套接字路径。
HTTPClient.shared.execute(
.POST,
secureSocketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource",
body: .string("hello")
).whenComplete (...)
可以直接构造 URL 以在其他场景中执行。
let socketPathBasedURL = URL(
httpURLWithSocketPath: "/tmp/myServer.socket",
uri: "/path/to/resource"
)
let secureSocketPathBasedURL = URL(
httpsURLWithSocketPath: "/tmp/myServer.socket",
uri: "/path/to/resource"
)
通过在 HTTPClient.Configuration
上将 httpVersion
设置为 .http1Only
,可以独占使用 HTTP/1。
var configuration = HTTPClient.Configuration()
configuration.httpVersion = .http1Only
let client = HTTPClient(
eventLoopGroupProvider: .singleton,
configuration: configuration
)
请查看 SECURITY.md 以了解 AsyncHTTPClient 的安全流程。
最新版本的 AsyncHTTPClient 支持 Swift 5.6 及更高版本。以下详细列出了 AsyncHTTPClient 版本支持的最低 Swift 版本。
AsyncHTTPClient | 最低 Swift 版本 |
---|---|
1.0.0 ..< 1.5.0 |
5.0 |
1.5.0 ..< 1.10.0 |
5.2 |
1.10.0 ..< 1.13.0 |
5.4 |
1.13.0 ..< 1.18.0 |
5.5.2 |
1.18.0 ..< 1.20.0 |
5.6 |
1.20.0 ..< 1.21.0 |
5.7 |
1.21.0 ... |
5.8 |