请求 0.3.2

Requests 0.3.2

Alex Jackson 负责。



Requests 0.3.2

  • Alex Jackson

Requests Build Status

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

Requests 关注执行网络请求。您可以使用 任何 工具 进行请求Requests 只提供了构建请求并保持其组织更为愉悦的一些类型。

⚠️ Requests 正在积极开发中,API 中的一些区域将会变动。直到 Requests 达到版本 1.0,任何非补丁 0.x 版本都可能是 API 破坏性的。


使用指南

核心类型

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

核心类型包括

  • RequestConvertible 协议 --- 遵从类型的特征可以是 HTTP 请求的属性,并且可以转换为 Foundation 的 URLRequest 实例。
  • Request 结构体 --- RequestConvertible 协议的具体实现,提供了一种流畅的接口来声明 API 请求。
  • 《RequestProviding》协议 --- 适配类型声明 API 的基础 URL,并可以为特定 API 初始化基础 Request 实例。
  • 《ResponseDecoder》结构 —— 一种包装解码 HTTP 响应类型的函数的类型。
  • 《BodyProvider》结构 —— 一种包装编码 RequestConvertible 类型主体的函数的类型。

如果您希望快速开始使用 Requests,应研究 RequestRequestProviding 类型。

安装

Requests 支持 Cocoapods、CocoaPodsCarthageSwift Package Manager 安装。支持 macOS、iOS、tvOS 和 watchOS。Linux 不支持,但可能工作。

⚠️Requests 处于 0.x 发布阶段时,使用 package 管理器的 悲观操作符 将版本号锁定到次要版本。

Cocoapods

将以下内容添加到您的 Podfile

pod "Requests", "~> 0.3.0"

Carthage

将以下内容添加到您的 Cartfile

github "alexjohnj/Requests" ~> 0.3.0

Swift Package Manager

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

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

获取资源

要构建一个请求以获取JSON编码的资源模型,该模型作为Decodable结构,请使用请求提供者上的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请求,并使用配置了从响应体中解码User结构的ResponseDecoder的配置。返回的请求对其响应体的类型进行泛型(称为Resource,以区分HTTPURLResponse)。

URLSession上的perform(_:)方法执行请求,并使用响应体评估ResponseDecoder。如果一切顺利,它将解码后的Resource与一个HTTPURLResponse传递给完成块。否则,块接收到一个Error和一个可能的HTTPURLResponse

发送数据

发送数据看起来与获取资源类似。要构建一个用于发送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类型为,表示请求的响应没有正文或请求不关心正文。请注意,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 包含内置支持附加

  • 承载令牌头
  • HTTP基本认证头

您可以通过编写一个新的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:)》中的任何一个。

⚠️请求的《BodyProvider》和《AuthenticationProvider》都可以修改请求头部的字段。它们所做的任何更改都将覆盖你在构建请求时指定的字段。

自定义查询参数

与头部类似,《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头部的预定义字段。你可以通过在《Field》和《Field.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 随包内置了几种内置的 JSON 和文本数据的 ResponseDecoder。如果需要,可以定义一个新的 ResponseDecoder

ResponseDecoder 是一个泛型结构体,它的泛型参数是 Response 类型,该结构体包装一个接收 HTTPURLResponse 和一些 Data 作为参数并返回 Response 类型的抛出函数。

public struct ResponseDecoder<Response> {

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

    ...
}

当添加一个新的响应解码器时,在 ResponseDecoder 类型的扩展中声明一个静态属性或函数,它返回一个新的 ResponseDecoder。这在使用与请求构建方法结合时提供了无符号访问解码器,并在很大程度上提高了请求定义的可读性。

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

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 声明为静态属性或函数,以便与请求类型的方法很好地结合。

extension AuthenticationProvider {

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

}

编写新的请求体提供者

这一点没什么惊喜。与 AuthenticationProviderResponseDecoder 一样,BodyProvider 的工作方式相同。请求体提供者是一个结构体,它包装一个接收 inout Header 并返回 RequestBody 的抛出函数。

public struct BodyProvider {

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

    ...
}

在请求体提供者的主体中,你应该编码一些数据,更新 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])负责处理符合的类型 actual conversion。如果您正在构建任何处理请求的函数,请考虑将它们约束为 RequestConvertible 符合类型,而不是 Request 类型本身,以获得最大的灵活性。

Request 类型的大多数属性直接映射到 RequestConvertible 协议中的要求。与 RequestRequestConvertible 之间唯一的区别是协议中不包含关联的 API 类型。RequestConvertible 缺乏此类型,因为它为通过它组织请求打开的不同的使用模型。

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

这种组织系统有优点和缺点。其中一些优点包括

  • 易于发现性 —— 每个 API 请求都是自己的类型(通常在其自己的文件中),因此可以轻松地在项目中搜索。
  • 轻松定义临时的 Resource 类型 —— 您可以使用位于请求定义内部的类型来满足协议的相关类型 Resource 要求。这对一次性响应很有用,并保持请求及其相关资源的模型紧密相关。

缺点包括

  • 模板 —— 此方法导致大量模板化代码。每个 API 请求都需要一个新的类型、一个新的文件(通常是)然后是一个协议来隐藏网络请求的实际构建和执行。
  • 协议组合不如函数组合 —— 如果您尝试组合两个都有相同属性默认实现的 RequestConvertible 子协议,您将丢失默认实现。您将通过协议的两个默认实现来实现符合类型的属性,需要了解您正在组合的协议的默认实现。

执行请求

如前所述,Requests 并不关心网络的执行请求,只关注构建请求。尽管如此,Requests 确实提供了对 URLSession 的支持以执行请求。这主要是为了帮助人们快速上手使用 Requests,但这绝不是定义如何使用 Requests 的目的所在。

如果您要将 Requests 与其他网络系统集成,请记住以下几点

  • 请将您的函数限制在操作 RequestConvertible 类型上,而不是 Request
  • Void 资源类型表示请求不需要或不在乎响应的主体。您的函数应尊重这一点,不要将 nil 响应主体视为 Void 请求的错误。
  • ResponseDecoders 只在 HTTP 响应上操作。您的函数应将非 HTTPURLResponse 实例视为错误。
  • RequestConvertible 类型转换为 URLRequest 可能有失败的风险。

许可证

Requests 是在 MIT 许可证下发布的。