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, Carthage 或者 Swift 包管理器进行安装。 Requests 支持 macOS、iOS、tvOS 和 watchOS。 不支持 Linux,但可能可以工作。
⚠️ 当 Requests 处于0.x
发布阶段时,请使用您的包管理器的悲观运算符将版本号固定到次要版本。
将以下内容添加到您的 Podfile
中
pod "Requests", "~> 0.3.0"
将以下内容添加到您的 Cartfile
中
github "alexjohnj/Requests" ~> 0.3.0
将以下内容添加到您的 Package.swift
文件的 dependencies 中
dependencies: [
.package(url: "https://github.com/alexjohnj/Requests.git", .upToNextMinor(from: "0.3.0"))
]
对于应用程序中的每个 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
。
要构建一个请求来检索建模为 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/1
的 GET
请求,并使用 ResponseDecoder
对其进行配置,该解码器尝试从响应正文中解码 User
结构。 返回的请求是泛型的,覆盖其响应正文的类型(称为 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
类型是 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:)
之一。
⚠️ 请求的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 可能需要在每个请求的标头中包含应用程序密钥。 您可以通过在符合 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
的工作方式与 AuthenticationProvider
和 ResponseDecoder
相同。 正文提供程序是一种结构,它包装一个抛出函数,该函数采用 inout Header
并返回一个 RequestBody
public struct BodyProvider {
public init(encode: @escaping (inout Header) throws -> RequestBody)
...
}
在 BodyProvider
的正文中,您应该对某些数据进行编码,更新 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
协议实际上是 Requests 的核心。 事实上,在很长一段时间里,它是 Requests 的全部。 其他一切都是围绕该类型构建的,以简化其使用。
RequestConvertible
类型声明了将请求转换为 Foundation URLRequest
所需的所有信息。 协议上的扩展方法 (toURLRequest()
) 处理符合类型的实际转换。 如果您正在构建任何对请求进行操作的函数,您应该考虑将它们限制为符合 RequestConvertible
的类型,而不是 Request
类型本身,以获得最大的灵活性。
Request
类型的大部分属性直接映射到 RequestConvertible
协议中的要求。 Request
和 RequestConvertible
之间的唯一区别是协议中缺少关联的 API
类型。 RequestConvertible
缺少此类型的原因是使用它来组织请求打开的不同使用模型。
使用 RequestConvertible
协议,您可以使用协议继承和组合来组织应用程序的 HTTP 请求。 应用程序中的每个请求都是 RequestConvertible
类型。 API 的通用属性可以在从基本 RequestConvertible
协议继承的协议中声明。 这消除了对关联的 API
类型的需求。
这种组织系统有利有弊。 一些优点是
Resource
类型 --- 您可以使用嵌套在请求定义中的类型来满足协议的 Resource
关联类型要求。 这对于一次性响应非常方便,并使请求的模型及其关联的资源保持紧密的联系。它的一些缺点
RequestConvertible
子协议,您将会丢失默认实现。您需要了解您正在组合的两个协议的默认实现,才能正确地实现符合类型的属性。如前所述,Requests 并不关心执行网络请求,而只关心构建它们。即便如此,Requests 确实带有对 URLSession
的一个支持扩展,用于执行请求。这旨在帮助人们快速开始使用 Requests,但绝不意味着定义 Requests 应该如何使用。
如果您正在将 Requests 与另一个网络系统集成,请记住以下几点
RequestConvertible
类型上操作,而不是 Request
。Void
资源类型表示请求要么不期望,要么不关心响应的主体。您的函数应该尊重这一点,并且不应将 nil
响应主体视为 Void
请求的错误。ResponseDecoders
仅对 HTTP 响应进行操作。您的函数应该将非 HTTPURLResponse
实例视为错误。RequestConvertible
类型转换为 URLRequest
可能会失败。Requests 在 MIT 许可证下发布。