AsyncHTTPClient

本软件包提供了一个基于 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")]),

请求-响应 API

下面的代码片段说明了如何向远程服务器发出简单的 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 示例

async/await API 的示例可以在此仓库的 Examples 文件夹中找到。

使用指南

默认的 HTTP 方法是 GET。如果您需要更好地控制方法,或者想要添加标头或 body,请使用 HTTPClientRequest 结构体。

使用 Swift 并发

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
}

使用 SwiftNIO EventLoopFuture

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 中的字节数。

使用 Swift 并发

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)
}

使用 HTTPClientResponseDelegate 和 SwiftNIO EventLoopFuture

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")
    }

Unix 域套接字路径

连接到绑定到套接字路径的服务器很容易。

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"
)

禁用 HTTP/2

通过在 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