HTTP介绍
在本次HTTP相关文章中,我将和大家一起讨论HTTP相关的话题,以及如何很好的将其糅合在一个大的Swift Framework中。我们将一个支持以下情况的framework终结本次讨论:
- 自动重新请求
- 节流到可自定义的最大并发请求数
- 为整个连接堆栈或单个请求指定自定义服务器环境
- 重置堆栈时自动取消正在执行中的请求
- 基本网络状况
- 执行OAuth(或任何形式的鉴权请求)
- 多类型的上传
- 易于模仿的响应
- ...其他点
这所有的一切都是建立在URLSession
之上的。顺着这条线,我们将处理我向Apple工程师提供的有关内置网络堆栈状态的大量反馈。(令人惊奇的是,在过去的 2 年中,其中一些项目已经得到解决)
需要预先说明的是:我不会在线发布任何关于此框架的任何实际代码。相关文章只是描述问题和其解决方案,但具体的实现由你自己决定。我将嵌入一些小的代码片段来说明相关概念,但大部分内容则需要你自己以练习的形式自己完成。Coding for yourself is a greet thing!
但是,在开始之前,我们需要回答一个切实的问题:什么TMD是TMD HTTP???
什么是HTTP?
HTTP,超文本传输协议,是描述两个不同系统如何相互通信的规范。义如其名,这是一个基于文本的规范。这个规范对人类非常友好,因为文本阅读起来还是很容易的,不光对人类好,对机器也很友好,因为文本是极易压缩的。
HTTP定义了一个规范模型:"request/response"模型。你发出一个请求,就会得到一个响应。一个请求,一个响应。
一个请求具备以下几个格式:
- 一个请求行
- 零个或多个头标值行
- 一个空行
- 一个可选的请求体
请求行
一个请求行可能长下面这个样子
GET /api/ HTTP/1.1
该行的第一部分是请求方法。我们已经非常熟悉GET
和POST
请求了,但是有其类型的HTTP请求方法,比如:HEAD
, PUT
, DELETE
等。实际上,HTTP规范本身不限制请求方法的值。有些也会定义一些自己的方法,比如COPY
, LOCK
, PROPFIND
等等。
这就导致我们会遇见大量的Swift网络框架中的最简单的问题。通常是长这样的:
public enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
自从HTTP规范允许使用单词式方法,在swift中以enum
的形式定义他的值的的形式就是不正确的。因为一个枚举只容许有限多个值,但实际上是无限多个可能的单词式的值。最好的方法是使用struct
:
public struct HTTPMethod: Hashable {
public static let get = HTTPMethod(rawValue: "GET")
public static let post = HTTPMethod(rawValue: "POST")
public static let put = HTTPMethod(rawValue: "PUT")
public static let delete = HTTPMethod(rawValue: "DELETE")
public let rawValue: String
}
方法之后是路径,应该很容易识别为URL
的.path
最后是HTTP规范及其版本。目前使用的是HTTP2.0.0,但是2.0.0处理的是复杂的HTTP请求,我这里还是继续使用1.1版本的吧,因为1.1版本表述的是非复杂性的请求。
请求头 Headers
请求头是一系列的键值对,我们熟知的键值对长这样:
Host: swapi.dev
Connection: close
User-Agent: Paw/3.1.10(Macintosh; OS X/10.15.5) GCDHTTPRequest
有许多 HTTP 常用的标头字段,其中一些由规范定义,一些由规范约定,与请求方法一样,标头的可能名称没有限制,可能值也没有太多限制。
请求体 Body
任何HTTP请求都包含一个请求体,是的,即使是GET
请求也是允许有一个请求体的。但是,大多数服务器只会根据使用的请求方法来解释请求的主体,因此即使您确实包含带有GET
请求体,服务器也可能会忽略它。
请求体本身就是原始二进制数据,服务器如何解释它完全取决于服务器工程师和客户端工程师之间达成的约定。
将请求放在一起
如果你想执行一个基础的HTTP请求到某个API,你需要将这个请求整合成这样:
GET /api/ HTTP/1.1
Host:swapi.dev
Connection: close
User-Agent: Paw/3.1.10(Macintosh; OS X/10.15.5) GCDHTTPRequest
我们可以看到请求行,3个请求头,还有一个空行作为结尾。重点注意一下HTTP请求的每一行都是被两个字符\r\n
分开的。
另一个值得注意的点是Host
: header并不能必然地规定请求去哪了,因为URL的host部分是在请求发出之前就被DNS 解析过程给使用了,作为整个“我实际连接到什么服务器”流程的一部分。Host
的header是为了在调试时清晰明了,服务器可能会使用它来进行进一步的内部路由。例如,您可以想象所有Github
页面的服务器使用Host
标头来了解您真正想要的页面集。
关于请求的介绍到此结束。
什么是Response
我们得到的响应在结构上几乎与请求相同。它具备:
- 响应行
- 零个或多个头标值行
- 一个空行
- 一个可选的响应体
所以 上述请求的响应体长这样:
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Sat, 27 Jun 2020 19:13:53 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: close
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
ETag: "57e8c3fe1ac5cb74e15b96dc98767ce6"
Allow: GET, HEAD, OPTIONS
Strict-Transport-Security: max-age=15768000
{"people":"http://swapi.dev/api/people/","planets":"http://swapi.dev/api/planets/","films":"http://swapi.dev/api/films/","species":"http://swapi.dev/api/species/","vehicles":"http://swapi.dev/api/vehicles/","starships":"http://swapi.dev/api/starships/"}
跟请求一样,响应也是被\r\n
分开,响应体的第一行会有一个响应状态码,代表着当前请求是否成功。
整合一下
这是对 HTTP 请求和响应的概述。这个简单的模型描述了大多数互联网使用的基本通信。它不包括网络套接字或推送通知等内容,但每个网站和大多数网络 API 都会使用它。了解其工作原理的细节仍然是我作为软件工程师所知道和使用的最有价值和最有用的东西之一。
在下一篇文章中,我们将看看我们如何从这个规范转向我们的框架所需的基本结构。