Swift中的HTTP请求体Request Bodies使用示例详解
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>正文</li><li>通用化body</li><li>空请求体 EmptyBody</li><li>数据体 DataBody</li><li>JSON体 JSONBody</li><li>表单 FormBody</li><li>其他Body Other Bodies</li></ul></div><p class="maodian"></p><h2>正文</h2><div class="cros igoods"><div class="goodsin" data-img="https://img14.360buyimg.com/pop/jfs/t3229/39/2924280771/392779/d242f52a/57e8d68eN57129bbf.jpg" data-name="精通Swift设计模式(图灵出品)" data-owner="京东自营" data-price="77.4" data-tgid="38" data-url="https://union-click.jd.com/jdc?e=&p=JF8BAMgJK1olXwUFU1xcAUoRA18IG1MTWgIAVm4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYCXFhaDEkVHDZNRwYlPGZYJBYFTkt0WQ1OQC8cLVpjAVoUaEcbM2gNHF4dXwMBZF5eDkwXAmoIK2sVXDZQOobrvpOysnPcsdTA1ZEyVW5dD00TBm4AGl8UXAAFZF5VDHtUVypcWBhdbTYyV25tOEsnAF9WdVpGWwQCUVlcZhZCXWlYTQRWMwMFVl1ZAUMnAW4JGVklbQ"></div></div>
<p>在进行HTTPRequest请求发送前,我们稍稍改进一下我们的结构体,最后,我们将会以下面的信息输出:</p>
<div class="jb51code"><pre class="brush:java;">public struct HTTPRequest {
private var urlComponents = URLComponents()
public var method: HTTPMethod = .get
public var headers: = [:]
public var body: Data?
}
</pre></div>
<p>在本节中,我们将着重讨论一下<code>body</code>属性,并对其进行改造。</p>
<p class="maodian"></p><h2>通用化body</h2>
<p>在HTTP简介那一节,我们了解到,一个请求体是原始二进制数据,但是,在与 <code>Web API</code> 通信时,这些数据有多种标准格式,例如 JSON 和表单提交。</p>
<p>我们可以将其概括为一种“给我们数据的东西”的形式,而不是要求此代码的客户手动构造其提交数据的二进制表示。</p>
<p>由于我们不打算对用于构造数据的算法施加任何限制,因此通过协议而不是具体类型来定义此功能是有意义的:</p>
<div class="jb51code"><pre class="brush:java;">public protocol HTTPBody { }
</pre></div>
<p>接下来,我们需要一种方法从其中一个值中获取<code>Data</code>,并在出现问题时选择性地报告错误:</p>
<div class="jb51code"><pre class="brush:java;">public protocol HTTPBody {
func encode() throws -> Data
}
</pre></div>
<p>我们可以在这一点上停下来,但还有另外两条信息值得拥有:</p>
<div class="jb51code"><pre class="brush:java;">public protocol HTTPBody {
var isEmpty: Bool { get }
var additionalHeaders: { get }
func encode() throws -> Data
}
</pre></div>
<p>如果我们能快速知道一个body是空的,那么我们就可以省去尝试检索任何编码数据和处理错误或空数据值的麻烦。</p>
<p>此外,某些类型的正文与请求中的header结合使用。 例如,当我们将值编码为 <code>JSON</code> 时,我们希望有一种方法可以自动指定 <code>Content-Type: application/json</code> 的header,而无需在请求中手动指定它。 为此,我们将允许这些类型声明额外的header,这些标头将作为最终请求的一部分结束。 为了进一步简化采用,我们可以为这些提供默认实现:</p>
<div class="jb51code"><pre class="brush:java;">extension HTTPBody {
public var isEmpty: Bool { return false }
public var additionalHeaders: { return [:] }
}
</pre></div>
<p>最后,我们可以将我们的类型更新到这个新的协议中</p>
<div class="jb51code"><pre class="brush:java;">public struct HTTPRequest {
private var urlComponents = URLComponents()
public var method: HTTPMethod = .get
public var headers: = [:]
public var body: HTTPBody?
}
</pre></div>
<p class="maodian"></p><h2>空请求体 EmptyBody</h2>
<p>最简单的HTTPBody是”无体“。有了这个协议,定义一个空请求体也是很方便的。</p>
<div class="jb51code"><pre class="brush:java;">public struct EmptyBody: HTTPBody {
public let isEmpty = true
public init() { }
public func encode() throws -> Data { Data() }
}
</pre></div>
<p>我们甚至可以将其设置为默认的主体值,从而完全消除对该属性的可选性的需要:</p>
<div class="jb51code"><pre class="brush:java;">public struct HTTPRequest {
private var urlComponents = URLComponents()
public var method: HTTPMethod = .get
public var headers: = [:]
public var body: HTTPBody = EmptyBody()
}
</pre></div>
<p class="maodian"></p><h2>数据体 DataBody</h2>
<p>下一个明显要实现的主体类型是返回给定的任何Data值的主体。 这将用于我们不一定有 <code>HTTPBody</code> 实现但也许我们已经有Data值本身要发送的情况。</p>
<p>具体实现如下:</p>
<div class="jb51code"><pre class="brush:java;">public struct DataBody: HTTPBody {
private let data: Data
public var isEmpty: Bool { data.isEmpty }
public var additionalHeaders:
public init(_ data: Data, additionalHeaders: = [:]) {
self.data = data
self.additionalHeaders = additionalHeaders
}
public func encode() throws -> Data { data }
}
</pre></div>
<p>有了这个,我们可以很轻松的将一个<code>Data</code>值封装进<code>HTTPBody</code>里:</p>
<div class="jb51code"><pre class="brush:java;">let otherData: Data = ...
var request = HTTPRequest()
request.body = DataBody(otherData)
</pre></div>
<p class="maodian"></p><h2>JSON体 JSONBody</h2>
<p>在发送网络请求时,将值编码为 <code>JSON</code> 是一项非常常见的任务。 制作一个 <code>HTTPBody</code> 来为我们处理这个现在很容易:</p>
<div class="jb51code"><pre class="brush:java;">public struct JSONBody: HTTPBody {
public let isEmpty: Bool = false
public var additionalHeaders = [
"Content-Type": "application/json; charset=utf-8"
]
private let encode: () throws -> Data
public init<T: Encodable>(_ value: T, encoder: JSONEncoder = JSONEncoder()) {
self.encode = { try encoder.encode(value) }
}
public func encode() throws -> Data { return try encode() }
}
</pre></div>
<p>首先,我们假设我们得到的任何值都会至少产生一些结果,因为即使是空字符串也会编码为非空 <code>JSON</code> 值。 因此,<code>isEmpty = false</code>。</p>
<p>接下来,大多数服务器在接收 <code>JSON</code> 正文时需要 <code>application/json</code> 的 <code>Content-Type</code>,因此我们假设这是常见情况,并在 <code>additionalHeaders</code> 中默认该值。 但是,我们会将该属性保留为 <code>var</code>,以防万一出现客户不希望这样的情况。</p>
<p>对于编码,我们需要接受一些通用值(要编码的东西),但最好不要让整个结构对编码类型通用。 我们可以通过将类型的泛型参数限制为初始化器来避免类型的泛型参数,然后在闭包中捕获泛型值。</p>
<p>我们还需要一种方法来提供自定义 <code>JSONEncoder</code>,以便客户有机会摆弄诸如 <code>.keyEncodingStrategy</code> 之类的东西。 但是,我们将提供一个默认编码器来简化使用。</p>
<p>最后,<code>encode()</code> 方法本身只是调用我们创建的闭包,它捕获通用值并通过 <code>JSONEncoder</code> 执行它。</p>
<p>其中一个的使用方法如下:</p>
<div class="jb51code"><pre class="brush:java;">struct PagingParameters: Encodable {
let page: Int
let number: Int
}
let parameters = PagingParameters(page: 0, number: 10)
var request = HTTPRequest()
request.body = JSONBody(parameters)
</pre></div>
<p>这样,正文将自动编码为 {"page":0,"number":10},我们的最终请求将具有正确的 <code>Content-Type</code> 标头。</p>
<p class="maodian"></p><h2>表单 FormBody</h2>
<p>我们将在本文中看到的最后一种主体是表示基本表单提交的body。 当我们专门讨论多部分表单上传时,我们将保存文件上传以备将来使用。</p>
<p>表单提交正文最终为粗略的 <code>URL</code> 编码键值对,例如 <code>name=Arthur&age=42</code>。</p>
<p>我们将从与我们的 <code>HTTPBody</code> 实现相同的基本结构开始:</p>
<div class="jb51code"><pre class="brush:java;">public struct FormBody: HTTPBody {
public var isEmpty: Bool { values.isEmpty }
public let additionalHeaders = [
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
]
private let values:
public init(_ values: ) {
self.values = values
}
public init(_ values: ) {
let queryItems = values.map { URLQueryItem(name: $0.key, value: $0.value) }
self.init(queryItems)
}
public func encode() throws -> Data {
let pieces = values.map { /* TODO */ }
let bodyString = pieces.joined(separator: "&")
return Data(bodyString.utf8)
}
}
</pre></div>
<p>和以前一样,我们有一个自定义的 <code>Content-Type</code> 标头来应用于请求。 我们还公开了几个初始化器,以便客户端可以以对他们有意义的方式描述这些值。 我们还删除了大部分 <code>encode()</code> 方法,省略了 <code>URLQueryItem</code> 值的实际编码。</p>
<p>不幸的是,对名称和值进行编码有点模棱两可。 如果你仔细阅读关于表单提交的古老规范,你会看到提到“换行规范化”和将空格编码为 <code>+</code> 的内容。 我们可以努力挖掘并找出这些东西的含义,但在实践中,<code>Web</code> 服务器往往可以很好地处理任何百分比编码的内容,甚至是空格。 我们将走捷径并假设这是真的。 我们还将全面假设字母数字字符在名称和值中是可以的,并且其他所有内容都应该被编码:</p>
<div class="jb51code"><pre class="brush:java;">private func urlEncode(_ string: String) -> String {
let allowedCharacters = CharacterSet.alphanumerics
return string.addingPercentEncoding(withAllowedCharacters: allowedCharacters) ?? ""
}
</pre></div>
<p>使用 <code>=</code> 字符组合名称和值:</p>
<div class="jb51code"><pre class="brush:java;">private func urlEncode(_ queryItem: URLQueryItem) -> String {
let name = urlEncode(queryItem.name)
let value = urlEncode(queryItem.value ?? "")
return "(name)=(value)"
}
</pre></div>
<p>有了这个,我们可以解决 <code>/* TODO */</code> 评论:</p>
<div class="jb51code"><pre class="brush:java;">public struct FormBody: HTTPBody {
public var isEmpty: Bool { values.isEmpty }
public let additionalHeaders = [
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
]
private let values:
public init(_ values: ) {
self.values = values
}
public init(_ values: ) {
let queryItems = values.map { URLQueryItem(name: $0.key, value: $0.value) }
self.init(queryItems)
}
public func encode() throws -> Data {
let pieces = values.map(self.urlEncode)
let bodyString = pieces.joined(separator: "&")
return Data(bodyString.utf8)
}
private func urlEncode(_ queryItem: URLQueryItem) -> String {
let name = urlEncode(queryItem.name)
let value = urlEncode(queryItem.value ?? "")
return "(name)=(value)"
}
private func urlEncode(_ string: String) -> String {
let allowedCharacters = CharacterSet.alphanumerics
return string.addingPercentEncoding(withAllowedCharacters: allowedCharacters) ?? ""
}
}
</pre></div>
<p>和以前一样,使用它变得很简单:</p>
<div class="jb51code"><pre class="brush:java;">var request = HTTPRequest()
request.body = FormBody(["greeting": "Hello, ", "target": "🌎"])
// the body is encoded as:
// greeting=Hello%2C%20&target=%F0%9F%8C%8E
</pre></div>
<p class="maodian"></p><h2>其他Body Other Bodies</h2>
<p>您可以在 HTTP 请求中发送的正文格式多种多样。 我已经提到过,我们将来会更仔细地研究多部分请求,但是这种 HTTPBody 方法几乎适用于您会遇到的每一种请求体。</p>
<p>在下一篇文章中,我们将描述 <code>HTTP</code> 请求加载抽象层并使用 <code>URLSession</code> 实现它。</p>
<p>以上就是Swift中的HTTP请求体Request Bodies使用示例详解的详细内容,更多关于Swift HTTP请求体Request Bodies的资料请关注琼殿技术社区其它相关文章!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Swift中Optional值的链式调用学习笔记</li><li>Swift 中如何使用 Option Pattern 改善可选项的 API 设计</li><li>Swift Package 技巧及混编兼容问题详解</li><li>Swift重构自定义空等运算符 “??=” 实例</li><li>Swift 重构重载运算符示例解析</li><li>Swift HTTP加载请求Loading Requests教程</li><li>Swift中的HTTP模拟测试示例详解</li><li>Swift中的可选项Optional解包方式实现原理</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]