请求 Build Status

Requests 是一个 Swift 库,专注于为构建和组织应用程序的 HTTP 请求提供便利。

Requests 负责执行网络请求。 您可以使用任何 想用的库来执行请求。 Requests 只是提供了一些类型,使构建请求和保持其井井有条更加愉快。

⚠️ Requests 正在积极开发中,并且 API 的某些区域将会发生更改。 在 Requests 达到 1.0 版本之前,任何非补丁 0.x 版本都可能包含破坏性的 API 更改。


使用指南

核心类型

Requests 包含一些构成库核心的类型,以及许多辅助类型。 所有类型的完整参考文档可以在这里找到。

核心类型是

如果您需要快速开始使用 Requests,您应该研究 RequestRequestProviding 类型。

安装

Requests 支持使用 CocoaPods, Carthage 或者 Swift 包管理器进行安装。 Requests 支持 macOS、iOS、tvOS 和 watchOS。 不支持 Linux,但可能可以工作。

⚠️Requests 处于 0.x 发布阶段时,请使用您的包管理器的悲观运算符将版本号固定到次要版本。

CocoaPods

将以下内容添加到您的 Podfile

pod "Requests", "~> 0.3.0"

Carthage

将以下内容添加到您的 Cartfile

github "alexjohnj/Requests" ~> 0.3.0

Swift Package Manager

将以下内容添加到您的 Package.swift 文件的 dependencies 中

dependencies: [
    .package(url: "https://github.com/alexjohnj/Requests.git", .upToNextMinor(from: "0.3.0"))
]

为 API 创建请求提供者

对于应用程序中的每个 API,创建一个符合 RequestProviding 协议的类型。 这些类型为 API 提供基本 URL

enum ExampleAPI: RequestProviding {
    case development
    case production

    var baseURL: URL {
        switch self {
        case .development:
            return URL("https://dev.example.com/api")
        case .production:
            return URL("https://live.example.com/api")
        }
    }
}

let api = ExampleAPI.development

RequestProviding 类型构成了为 API 构建 Request 的入口点。 RequestProviding 类型有几种方法可以构造到 API 的基本 Request

GET 一个资源

要构建一个请求来检索建模为 Decodable 结构的 JSON 编码资源,请在请求提供程序上使用 get(_:from:) 方法

struct User: Codable { }

let getUserRequest = api.get(.json(encoded: User.self), from: "/user/1/")

URLSession.shared.perform(getUserRequest) { result in
    switch result {
    case .success(let urlResponse, let user):
        // Do something with the user
        break

    case .failed(let response?, let error):
        // We got a HTTP response but also an error. Something probably went wrong decoding the JSON.
        break

    case .failed(nil, let error):
        // We didn't get a response. There was probably a network error.
        break
    }
}

此方法构造一个到 https://dev.example.com/api/user/1GET 请求,并使用 ResponseDecoder 对其进行配置,该解码器尝试从响应正文中解码 User 结构。 返回的请求是泛型的,覆盖其响应正文的类型(称为 Resource 以区别于 HTTPURLResponse)。

URLSession 上的 perform(_:) 方法执行请求并使用响应正文评估 ResponseDecoder。 然后,如果一切成功,它会将解码的 Resource 以及 HTTPURLResponse 传递给完成块。 否则,该块会收到一个 Error,可能还会收到一个 HTTPURLResponse

POST 一些数据

发送数据与检索资源类似。 要构建一个发布 JSON 编码 User 结构的请求,请在 API 请求提供程序上使用 post(_:to:) 方法

let user = User()
let createUserRequest = api.post(.json(encoded: user), to: "/user/")
URLSession.shared.perform(createUserRequest) { result in
    // Handle the result
}

此方法创建一个配置了 BodyProviderPOST 请求,该提供程序将用户结构编码为 JSON。 请求的 Resource 类型是 Void,这意味着请求的响应没有正文,或者请求不关心正文。 请注意,BodyProvider 将负责更新请求的标头,以指示它包含的内容类型。

验证请求

Requests 对验证请求具有基本支持。 如果可以使用请求的标头对其进行身份验证,请使用 AuthenticationProvider 使用所需的凭据更新标头

let authToken = "DEADBEEF-DEADBEEF-DEADBEEF"
let updateUserRequest = api.patch("/user/1", with: .json(encoded: user))
    .authenticated(with: .bearerToken(authToken))

URLSession.shared.perform(updateUserRequest) { _ in }

这将构建一个 PATCH 请求,该请求将在标头中包含持有者令牌。 Requests 包括对附加的内置支持

您可以通过编写新的 AuthenticationProvider 来添加其他基于标头的身份验证方案。

自定义标头

Request 类型有几个用于设置请求标头的函数。 Header 类型对请求的标头进行建模,该标头由多个 Field 组成。 Field 由名称和值组成。

要设置请求的标头,请使用 with(header:) 方法

let getBioRequest = api.get(.text, from: "/user/1/bio")
    .with(header: [
        .acceptLanguage("en-scouse"),
        .accept(.plainText)
        ])

这会从 Field 数组构造一个新的 Header,并将请求的标头替换为它。

要将标头添加到请求或替换请求标头中的单个字段,请使用 adding(headerField:)adding(headerFields:)setting(headerField:) 之一。

⚠️请求的 BodyProviderAuthenticationProvider 都可以修改请求标头的字段。 它们所做的任何更改都将覆盖您在构建请求时指定的字段。

自定义查询参数

与标头类似,Request 类型提供了几个用于设置请求查询参数的函数

let searchRequest = api.get(.text, from: "/users/search")
    .with(query: [
        "query": "alex",
        "limit": "30",
        ])

这将生成一个到 URL https://dev.example.com/api/users/search?query=alex&limit=30 的请求。 请注意,Requests 使用 Foundation 的 URLQueryItem 来表示查询项,但提供了几个扩展,使构建它们更加整洁。

定义自定义标头字段

Requests 包括几个预定义的字段,用于常见的 HTTP 标头。 您可以通过在 FieldField.Name 类型上添加 static 属性来轻松添加新字段

extension Field.Name {
    static let applicationKey = Field.Name("X-APPLICATION-KEY")
}

extension Field {
    static let applicationKey: (String) -> Field = { Field(name: .applicationKey, value: $0) }
}

为 API 定义基本请求

某些 API 需要在所有 API 请求上设置的通用属性。 例如,API 可能需要在每个请求的标头中包含应用程序密钥。 您可以通过在符合 RequestProviding 的类型中实现可选方法来实现此目的。

request(to:using:) 方法是 RequestProviding 协议的核心方法。 它返回 API 的新 Request,并且是 RequestProviding 上所有其他请求构建方法的起点。

request(to:using:) 的自定义实现可以返回一个应用了默认值集的 Request

struct ExternalAPI: RequestProviding {
    let baseURL: URL = URL("https://api.external.org")

    func request(to endpoint: String, using method: HTTPMethod) -> Request<ExternalAPI, Void> {
        return Request(api: self, endpoint: endpoint, responseDecoder: .none, method: method)
            .adding(headerField: .applicationKey("DEAD-BEEF"))
    }
}

现在,从 ExternalAPI 构建的任何 Request 都将包含应用程序密钥标头字段。

编写新的响应解码器

Requests 附带了几个内置的 ResponseDecoder,用于 JSON 和文本数据。 如果需要,可以定义新的 ResponseDecoder

ResponseDecoder 是一种在其 Response 上的泛型结构,它包装一个抛出函数,该函数采用 HTTPURLResponse 和一些 Data 并生成 Response

public struct ResponseDecoder<Response> {

    public init(_ decode: @escaping (HTTPURLResponse, Data) throws -> Response)

    ...
}

添加新的响应解码器时,在 ResponseDecoder 类型的扩展中声明一个静态属性或函数,该属性或函数返回新的 ResponseDecoder。 这在使用 Request 构建方法时提供了对解码器的非限定访问,并且大大提高了请求定义的可读性。

例如,.text(encoding:) 响应解码器的定义是 ResponseDecoder<String> 类型上的静态函数

extension ResponseDecoder where Response == String {

    public static let text = ResponseDecoder<String>.text(encoding: .utf8)

    public static func text(encoding: String.Encoding) -> ResponseDecoder<String> {
        return ResponseDecoder { _, data in
            guard let string = String(data: data, encoding: encoding) else {
                throw CocoaError(.fileReadInapplicableStringEncoding,
                                 userInfo: [NSStringEncodingErrorKey: encoding.rawValue])
            }

            return string
        }
    }
}

使用它,响应解码器的调用站点看起来非常整洁

let getBookRequest = api.get(.text(encoding: .ascii), from: "/book/1/contents")

// Or for UTF-8
let getOtherBookRequest = api.get(.text, from: "/book/2/contents")

对于 Swift 来说,这种方法有点非常规 --- 协议通常是更 Swift 的解决方案。 但是,这里的目标是优化调用站点的可读性,而不是协议的实现。 因为您会比编写它们更频繁地使用请求提供程序(尤其是当 Requests 添加更多内置组件时),我相信这是一个值得的权衡。

编写新的身份验证提供程序

ResponseDecoder 类型一样,AuthenticationProvider 是一种包装函数的结构。 身份验证提供程序包装一个改变 inout Header 的函数

public struct AuthenticationProvider {

    public init(authenticate: @escaping (inout Header) -> Void)

    ...
}

同样,将 AuthenticationProvider 声明为 AuthenticationProvider 类型上的静态属性或函数,以便它们与 Request 类型的方法一起很好地读取

extension AuthenticationProvider {

    static let custom: (String) -> AuthenticationProvider = { customToken in
        AuthenticationProvider { header in
            header[.authorization] = "Custom \(customToken)"
        }
    }

}

编写新的正文提供程序

没有惊喜。 BodyProvider 的工作方式与 AuthenticationProviderResponseDecoder 相同。 正文提供程序是一种结构,它包装一个抛出函数,该函数采用 inout Header 并返回一个 RequestBody

public struct BodyProvider {

    public init(encode: @escaping (inout Header) throws -> RequestBody)

    ...
}

BodyProvider 的正文中,您应该对某些数据进行编码,更新 HeaderContentType,然后返回正文。 请注意,返回的 RequestBody 可以包装原始 DataInputStream

BodyProvider 扩展中的静态函数中声明新的正文提供程序

extension BodyProvider {
    static func text(_ text: String) -> BodyProvider {
        return BodyProvider { header in
            guard let data = text.data(using: .utf8) else {
                throw TextBodyEncodingError.utf8EncodingFailed
            }

            header.set(.contentType(.plainText))
            return .data(data)
        }
    }
}

⚠️仅在调用任何抛出函数后更新请求的标头。

高级用法

RequestConvertible 协议

RequestConvertible 协议实际上是 Requests 的核心。 事实上,在很长一段时间里,它是 Requests 的全部。 其他一切都是围绕该类型构建的,以简化其使用。

RequestConvertible 类型声明了将请求转换为 Foundation URLRequest 所需的所有信息。 协议上的扩展方法 (toURLRequest()) 处理符合类型​​的实际转换。 如果您正在构建任何对请求进行操作的函数,您应该考虑将它们限制为符合 RequestConvertible 的类型,而不是 Request 类型本身,以获得最大的灵活性。

Request 类型的大部分属性直接映射到 RequestConvertible 协议中的要求。 RequestRequestConvertible 之间的唯一区别是协议中缺少关联的 API 类型。 RequestConvertible 缺少此类型的原因是使用它来组织请求打开的不同使用模型。

使用 RequestConvertible 协议,您可以使用协议继承和组合来组织应用程序的 HTTP 请求。 应用程序中的每个请求都是 RequestConvertible 类型。 API 的通用属性可以在从基本 RequestConvertible 协议继承的协议中声明。 这消除了对关联的 API 类型的需求。

这种组织系统有利有弊。 一些优点是

它的一些缺点

执行请求

如前所述,Requests 并不关心执行网络请求,而只关心构建它们。即便如此,Requests 确实带有对 URLSession 的一个支持扩展,用于执行请求。这旨在帮助人们快速开始使用 Requests,但绝不意味着定义 Requests 应该如何使用。

如果您正在将 Requests 与另一个网络系统集成,请记住以下几点

许可证

Requests 在 MIT 许可证下发布。