AsyncHTTPClient

这个包提供了一个基于 SwiftNIO 构建的简单 HTTP 客户端库。

这个库提供以下功能:

  1. 异步和非阻塞请求方法
  2. 简单的重定向跟随(cookie 头部将被丢弃)
  3. 流式 body 下载
  4. TLS 支持
  5. Cookie 解析(但不存储)

注意: 您需要 Xcode 10.2Swift 5.0 才能试用 AsyncHTTPClient


入门

添加依赖

在您的 Package.swift 中添加以下条目以开始使用 HTTPClient

.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0")

并将 AsyncHTTPClient 依赖添加到您的 target

.target(name: "MyApp", dependencies: ["AsyncHTTPClient"]),

请求-响应 API

以下代码片段说明了如何向远程服务器发出简单的 GET 请求。

请注意,该示例将产生一个新的 EventLoopGroup,它将创建新的线程,这是一个非常耗费资源的操作。在实际应用中使用 SwiftNIO 的其他部分(例如 Web 服务器)时,请优先使用 eventLoopGroupProvider: .shared(myExistingEventLoopGroup) 来共享 AsyncHTTPClient 使用的 EventLoopGroup 与应用程序的其他部分。

如果您的应用程序尚未使用 SwiftNIO,则可以接受使用 eventLoopGroupProvider: .createNew,但请确保在整个应用程序中共享返回的 HTTPClient 实例。不要创建大量的具有 eventLoopGroupProvider: .createNewHTTPClient 实例,这非常浪费并且可能耗尽程序的资源。

import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
httpClient.get(url: "https://swiftlang.cn").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
        }
    }
}

您应该始终关闭使用 try httpClient.syncShutdown() 创建的 HTTPClient 实例。请注意,您必须在 HTTP 客户端的所有请求完成之前调用 httpClient.syncShutdown,否则正在进行的请求很可能会因为其网络连接中断而失败。

使用指南

大多数常见的 HTTP 方法都已开箱即用。如果您需要更好地控制方法,或者想要添加标头或正文,请使用 HTTPRequest 结构体

import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
defer {
    try? httpClient.syncShutdown()
}

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

httpClient.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
        }
    }
}

重定向跟随

使用客户端配置启用 follow-redirects 行为

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew,
                            configuration: HTTPClient.Configuration(followRedirects: true))

超时

超时(连接和读取)也可以使用客户端配置进行设置

let timeout = HTTPClient.Timeout(connect: .seconds(1), read: .seconds(1))
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew,
                            configuration: HTTPClient.Configuration(timeout: timeout))

或者在每个请求的基础上进行设置

httpClient.execute(request: request, deadline: .now() + .milliseconds(1))

流式传输

在处理大量数据时,至关重要的是对响应正文进行流式传输,而不是在内存中聚合。使用委托协议完成处理响应流。以下示例演示了如何计算流式响应正文中的字节数

import NIO
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 Reponse 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://swiftlang.cn")
let delegate = CountingDelegate()

httpClient.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
    print(count)
}