網站首頁 編程語言 正文
正文
在進行HTTPRequest請求發送前,我們稍稍改進一下我們的結構體,最后,我們將會以下面的信息輸出:
public struct HTTPRequest { private var urlComponents = URLComponents() public var method: HTTPMethod = .get public var headers: [String: String] = [:] public var body: Data? }
在本節中,我們將著重討論一下body
屬性,并對其進行改造。
通用化body
在HTTP簡介那一節,我們了解到,一個請求體是原始二進制數據,但是,在與 Web API
通信時,這些數據有多種標準格式,例如 JSON 和表單提交。
我們可以將其概括為一種“給我們數據的東西”的形式,而不是要求此代碼的客戶手動構造其提交數據的二進制表示。
由于我們不打算對用于構造數據的算法施加任何限制,因此通過協議而不是具體類型來定義此功能是有意義的:
public protocol HTTPBody { }
接下來,我們需要一種方法從其中一個值中獲取Data
,并在出現問題時選擇性地報告錯誤:
public protocol HTTPBody { func encode() throws -> Data }
我們可以在這一點上停下來,但還有另外兩條信息值得擁有:
public protocol HTTPBody { var isEmpty: Bool { get } var additionalHeaders: [String: String] { get } func encode() throws -> Data }
如果我們能快速知道一個body是空的,那么我們就可以省去嘗試檢索任何編碼數據和處理錯誤或空數據值的麻煩。
此外,某些類型的正文與請求中的header結合使用。 例如,當我們將值編碼為 JSON
時,我們希望有一種方法可以自動指定 Content-Type: application/json
的header,而無需在請求中手動指定它。 為此,我們將允許這些類型聲明額外的header,這些標頭將作為最終請求的一部分結束。 為了進一步簡化采用,我們可以為這些提供默認實現:
extension HTTPBody { public var isEmpty: Bool { return false } public var additionalHeaders: [String: String] { return [:] } }
最后,我們可以將我們的類型更新到這個新的協議中
public struct HTTPRequest { private var urlComponents = URLComponents() public var method: HTTPMethod = .get public var headers: [String: String] = [:] public var body: HTTPBody? }
空請求體 EmptyBody
最簡單的HTTPBody是”無體“。有了這個協議,定義一個空請求體也是很方便的。
public struct EmptyBody: HTTPBody { public let isEmpty = true public init() { } public func encode() throws -> Data { Data() } }
我們甚至可以將其設置為默認的主體值,從而完全消除對該屬性的可選性的需要:
public struct HTTPRequest { private var urlComponents = URLComponents() public var method: HTTPMethod = .get public var headers: [String: String] = [:] public var body: HTTPBody = EmptyBody() }
數據體 DataBody
下一個明顯要實現的主體類型是返回給定的任何Data值的主體。 這將用于我們不一定有 HTTPBody
實現但也許我們已經有Data值本身要發送的情況。
具體實現如下:
public struct DataBody: HTTPBody { private let data: Data public var isEmpty: Bool { data.isEmpty } public var additionalHeaders: [String: String] public init(_ data: Data, additionalHeaders: [String: String] = [:]) { self.data = data self.additionalHeaders = additionalHeaders } public func encode() throws -> Data { data } }
有了這個,我們可以很輕松的將一個Data
值封裝進HTTPBody
里:
let otherData: Data = ... var request = HTTPRequest() request.body = DataBody(otherData)
JSON體 JSONBody
在發送網絡請求時,將值編碼為 JSON
是一項非常常見的任務。 制作一個 HTTPBody
來為我們處理這個現在很容易:
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() } }
首先,我們假設我們得到的任何值都會至少產生一些結果,因為即使是空字符串也會編碼為非空 JSON
值。 因此,isEmpty = false
。
接下來,大多數服務器在接收 JSON
正文時需要 application/json
的 Content-Type
,因此我們假設這是常見情況,并在 additionalHeaders
中默認該值。 但是,我們會將該屬性保留為 var
,以防萬一出現客戶不希望這樣的情況。
對于編碼,我們需要接受一些通用值(要編碼的東西),但最好不要讓整個結構對編碼類型通用。 我們可以通過將類型的泛型參數限制為初始化器來避免類型的泛型參數,然后在閉包中捕獲泛型值。
我們還需要一種方法來提供自定義 JSONEncoder
,以便客戶有機會擺弄諸如 .keyEncodingStrategy
之類的東西。 但是,我們將提供一個默認編碼器來簡化使用。
最后,encode()
方法本身只是調用我們創建的閉包,它捕獲通用值并通過 JSONEncoder
執行它。
其中一個的使用方法如下:
struct PagingParameters: Encodable { let page: Int let number: Int } let parameters = PagingParameters(page: 0, number: 10) var request = HTTPRequest() request.body = JSONBody(parameters)
這樣,正文將自動編碼為 {"page":0,"number":10},我們的最終請求將具有正確的 Content-Type
標頭。
表單 FormBody
我們將在本文中看到的最后一種主體是表示基本表單提交的body。 當我們專門討論多部分表單上傳時,我們將保存文件上傳以備將來使用。
表單提交正文最終為粗略的 URL
編碼鍵值對,例如 name=Arthur&age=42
。
我們將從與我們的 HTTPBody
實現相同的基本結構開始:
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: [URLQueryItem] public init(_ values: [URLQueryItem]) { self.values = values } public init(_ values: [String: String]) { 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) } }
和以前一樣,我們有一個自定義的 Content-Type
標頭來應用于請求。 我們還公開了幾個初始化器,以便客戶端可以以對他們有意義的方式描述這些值。 我們還刪除了大部分 encode()
方法,省略了 URLQueryItem
值的實際編碼。
不幸的是,對名稱和值進行編碼有點模棱兩可。 如果你仔細閱讀關于表單提交的古老規范,你會看到提到“換行規范化”和將空格編碼為 +
的內容。 我們可以努力挖掘并找出這些東西的含義,但在實踐中,Web
服務器往往可以很好地處理任何百分比編碼的內容,甚至是空格。 我們將走捷徑并假設這是真的。 我們還將全面假設字母數字字符在名稱和值中是可以的,并且其他所有內容都應該被編碼:
private func urlEncode(_ string: String) -> String { let allowedCharacters = CharacterSet.alphanumerics return string.addingPercentEncoding(withAllowedCharacters: allowedCharacters) ?? "" }
使用 =
字符組合名稱和值:
private func urlEncode(_ queryItem: URLQueryItem) -> String { let name = urlEncode(queryItem.name) let value = urlEncode(queryItem.value ?? "") return "(name)=(value)" }
有了這個,我們可以解決 /* TODO */
評論:
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: [URLQueryItem] public init(_ values: [URLQueryItem]) { self.values = values } public init(_ values: [String: String]) { 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) ?? "" } }
和以前一樣,使用它變得很簡單:
var request = HTTPRequest() request.body = FormBody(["greeting": "Hello, ", "target": "??"]) // the body is encoded as: // greeting=Hello%2C%20&target=%F0%9F%8C%8E
其他Body Other Bodies
您可以在 HTTP 請求中發送的正文格式多種多樣。 我已經提到過,我們將來會更仔細地研究多部分請求,但是這種 HTTPBody 方法幾乎適用于您會遇到的每一種請求體。
在下一篇文章中,我們將描述 HTTP
請求加載抽象層并使用 URLSession
實現它。
原文鏈接:https://juejin.cn/post/7195770707325304891
相關推薦
- 2022-12-05 Django中QuerySet查詢優化之prefetch_related詳解_python
- 2022-09-14 jQuery實現簡易計算器功能_jquery
- 2022-09-30 Python實現圖像增強_python
- 2022-05-02 分布式利器redis及redisson的延遲隊列實踐_Redis
- 2022-10-11 kafka-報錯kafka.common.InconsistentClusterIdExceptio
- 2022-04-17 Failed to bind properties under spring.servlet.mul
- 2023-05-15 shell?提取文件名和目錄名的方法實現_linux shell
- 2022-07-12 strcpy、strncpy與memcpy的區別你了解嗎?
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支