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,应研究 Request
和 RequestProviding
类型。
安装
Requests 支持 Cocoapods、CocoaPods、Carthage 或 Swift 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/1
的GET
请求,并使用配置了从响应体中解码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
}
此方法创建一个配置了BodyProvider
的POST
请求,该提供者将以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)"
}
}
}
编写新的请求体提供者
这一点没什么惊喜。与 AuthenticationProvider
和 ResponseDecoder
一样,BodyProvider
的工作方式相同。请求体提供者是一个结构体,它包装一个接收 inout Header
并返回 RequestBody
的抛出函数。
public struct BodyProvider {
public init(encode: @escaping (inout Header) throws -> RequestBody)
...
}
在请求体提供者的主体中,你应该编码一些数据,更新 Header
的 ContentType
,然后返回体。请注意,返回的 RequestBody
可以是原始的 Data
或 InputStream
。
在 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
协议中的要求。与 Request
和 RequestConvertible
之间唯一的区别是协议中不包含关联的 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 许可证下发布的。