第23章:足够多的网络内容¶
本章涵盖了一些关于iOS
应用程序和Web
服务器之间的HTTP
消息的基本信息。这足以让你为后面的章节做好准备,在这些章节中你将实现RWFreeView
的服务器下载。
这一章中没有SwiftUI
。
如果你已经对HTTP
消息了如指掌,请跳到"探索api.raywenderlich.com
"一节,熟悉一下你在下面几章中要用到的API
。
服务器和资源¶
许多应用程序与互联网上的计算机通信,以访问数据库和其他资源。我们称这些计算机为网络服务器,以回溯到最初的"万维网"。或云服务器,因为现在一切都在"云"中。"主机"是"服务器"的另一个术语。
像Safari
和RWFreeView
这样的应用程序是这些服务器的客户端。客户端向服务器发送请求,而服务器则发回一个响应。这种通信由符合超文本传输协议(HTTP
)的纯文本信息组成。超文本是结构化的文本,在包含文本的节点之间使用超链接。网页是用超文本标记语言(HTML
)编写的。
HTTP
有几种方法,包括POST
、GET
、PUT
和DELETE
。这些方法对应于数据库中的创建、读取、更新和删除功能。
客户端通常要求访问由服务器控制的资源。要访问互联网上的一个资源,你需要它的通用资源标识符(URI
)。这可能是一个通用资源定位器(URL
),它指定了资源的位置(服务器和路径)以及你应该使用的协议来访问它。
例如,https://raywenderlich.com/library
是一个指定HTTPS
协议的URL
,用来访问位于raywenderlich.com
服务器上的资源,路径为library
。在上一章中,计算属性linkURLString
使用一个URI
,如rw://betamax/videos/3021
来创建该剧集raywenderlich.com
页面的URL
。
Note
HTTPS
是HTTP
的安全、加密版本。它可以保护你的用户不被窃听。底层协议是相同的,但不是传输纯文本信息,而是在离开客户端或服务器之前对一切进行加密。
HTTP
消息¶
客户端的HTTP
请求信息包含头文件。一个POST
或PUT
请求有一个主体,包含新的或更新的数据。一个GET
请求通常有参数来过滤、分类或量化它想从服务器获得的数据。
服务器的HTTP
响应信息也有头文件和正文。响应的一个关键部分是状态代码--理想情况下,对GET
请求的响应是200 OK
,对POST
请求的响应是201 Created
。你不希望看到任何错误状态代码,如404 Not Found
。
HTTP
响应状态代码有很多很多。你会在https://http.cat找到它们的有趣表示。比如说。
Mozilla
(mzl.la/3o6qWNM)对状态代码提供了更常规的描述。
HTTP 418 I'm a teapot client error response code
表示服务器拒绝冲泡咖啡,因为它永久是一个茶壶。一个暂时没有咖啡的组合咖啡/茶壶应该返回503
。这个错误是指1998年和2014年愚人节笑话中定义的超文本咖啡壶控制协议。一些网站对他们不希望处理的请求使用这种响应,例如自动查询。
Note
1998年的HTCPCP
(bit.ly/3olM42q)愚人节笑话的灵感来自木马房咖啡壶(bit.ly/3pkCDSb),这是世界上第一个网络摄像机的主题。它建立于1991年,远早于物联网(IoT
)。
如果一个HTTP
消息有一个主体,它也有一个Content-Type
头。Content-Type
指定了HTTP
消息正文中数据的互联网媒体类型。
通常情况下,你会根据结构的不同,为文本数据使用三种内容类型。
JSON
(JavaScript
对象符号)是应用程序客户端用于HTTP
通信的最常见的数据格式。它是一种结构化的数据格式,由数字、字符串和数组及字典组成,可以包含字符串、数字和嵌套数组及字典。- 网络表格使用表单编码,它看起来像一个查询字符串。查询字符串是一个键值对的集合,用
&
分开,前面是?
。 - 网页是
HTML
。
在处理二进制数据时,一些最常用的类型是。PDF
、图像格式和多部分表格数据,当客户端将任何类型的二进制文件与文本元素一起发送。
REST API
¶
在第12章"苹果应用开发生态系统"中,你了解了可以用来开发iOS
应用的众多框架。苹果框架是一种应用程序编程接口(API
)。它告诉你如何使用由苹果工程师创建的标准组件。
另一种API
是客户端从服务器请求资源的规则集。你将为你的应用程序使用的大多数API
是REST API
,它使用HTTP
。对于服务器上的每个资源,REST API
文档告诉你如何构建一个请求。
- 资源的
URL
,称为其端点。 - 使用哪种
HTTP
方法。 - 要包括哪些
HTTP
头信息。 - 在请求正文中要写些什么。
Note
REST是"REpresentational State Transfer"
的缩写,是Roy Fielding
为万维网的架构风格所起的名字。这个术语描述了一个设计良好的Web应用是如何工作的。用户从网络资源(虚拟状态机)的网络中选择一个资源标识符,并使用GET
或POST
等方法来创建一个状态转换,将资源的表示转移给用户。
在下一章,你将设置RWFreeView
与REST API api.raywenderlich.com
通信。在这一章中,你将在raywenderlich.docs.apiary.io探索这个API
的文档。
发送和接收HTTP
信息¶
即使有很好的文档,你通常也要做一些实验,以弄清楚如何构建请求以获得你想要的确切资源,以及如何从服务器的响应中提取这些资源。那么,你如何发送请求和检查响应呢?
浏览器¶
进行简单的HTTP GET
请求的最简单的方法是在Safari
这样的浏览器应用程序中输入URL
。
➤ 在你最喜欢的浏览器中输入这个URL
:
https://www.raywenderlich.com/library
这是raywenderlich.com
库的端点。你会得到一个与此类似的页面。
这是服务器响应的主体,但你无法看到头信息。而且,除了一个简单的GET
请求,你不能做更多的事情。
cURL
¶
浏览器是一个完全自动化的HTTP
工具。在光谱的另一端是命令行工具cURL
(curl.se)--"成千上万的软件应用的互联网传输骨干"。
REST API
的文档通常会提供示例请求来告诉你如何使用它。很多时候,这些都是使用cURL
。
➤ 打开终端,输入这个命令:
curl https://api.github.com/zen
你向GitHub
的API
服务器发送一个HTTP
请求。响应是他们设计理念中的一个随机项目,比如"偏重于功能"或"避免管理上的分心"。
在GitHub
的Getting started with the REST API
(bit.ly/3iD717R)中还有很多请求实例。
但是,你说,curl
也没有显示任何响应头! 好吧,像所有的Unix
命令一样,curl
有大量的选项,包括--include
和它的快捷键-i
,可以在输出中包含HTTP
响应头。
➤ 输入这个命令:
curl -i https://api.github.com/zen
而你看到的是相当多的产出:
HTTP/2 200
server: GitHub.com
date: Tue, 27 Apr 2021 01:40:56 GMT
content-type: text/plain;charset=utf-8
...
x-ratelimit-limit: 60
x-ratelimit-remaining: 58
x-ratelimit-reset: 1619491224
x-ratelimit-used: 2
accept-ranges: bytes
x-github-request-id: EF14:706C:A3D763:B0E977:60876BA7
Avoid administrative distraction.
以x-
开头的标头是由组织设置的自定义标头。例如,x-ratelimit-limit
和x-ratelimit-used
表示一个客户在一个滚动的时间段(通常是一个小时)可以发出多少个请求,以及客户已经发出了多少个请求。
curl --verbose
或--v
选项显示请求头和更多内容。
➤ 输入这个命令:
curl -v https://api.github.com/zen
用-v
代替-i
会产生更多的输出--终端和服务器之间的每一次握手互动,使用的加密算法,服务器证书的细节,以及响应头文件。请求头信息只是以>
开头的五行:
> GET /zen HTTP/2
> Host: api.github.com
> User-Agent: curl/7.64.1
> Accept: /
>
以<
开头的行是响应头,以开头的行是由
cURL
提供的额外信息。
你可能不喜欢输入长结构的命令行,尤其是像这个创建新的GitHub
仓库的cURL
命令示例:
curl -i -H \
"Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4" \
-d '{ \
"name": "blog", \
"auto_init": true, \
"private": true, \
"gitignore_template": "nanoc" \
}' \
https://api.github.com/user/repos
这个POST
命令以请求头的形式发送授权数据,并以JSON
格式的数据发送请求正文。该端点没有命名一个特定的用户,因为GitHub
从令牌值中知道这一点。
使用cURL
的另一个问题。如果响应很复杂,就很难在终端中检查它。
➤ 输入这个命令:
curl https://api.raywenderlich.com/api/contents
这是对你将用于RWFreeView
的API
的一个请求。响应是相当令人头疼的:
如果你集中注意力,你也许能从这个输出中看到,响应体是一个字典,其中第一个值"data"
是一个字典数组。你可以使用像 codebeautify.org/jsonviewer这样的工具来格式化它,这样就更容易阅读了。
但有一个更好的解决方案:让你的HTTP
信息传递更容易的应用程序。
探索api.raywenderlich.com
¶
像RESTed
这样的应用程序让你通过填写字段和从下拉菜单中选择来创建HTTP
请求。你可以漂亮地打印响应并使用语法高亮。
➤ 在浏览器中,打开apple.co/3cb5CnP,点击Mac App Store
中的查看,并安装该应用程序。
请求内容¶
➤ 打开RESTed
,用这个URL
替换http://localhost:3000/
:
https://api.raywenderlich.com/api/contents
你设置了资源端点。你怎么知道要问什么呢?看看raywenderlich.docs.apiary.io的目录侧边栏。在参考文献部分,/contents
听起来是最一般的、最高级别的数据。
你怎么知道在/contents
前面要写什么?选择/contents
,然后向下滚动,找到这个灰色的字段,上面有200 OK
和一个披露指标:
➤ 这是一个按钮:点击它!
一个侧边栏打开,显示请求。这个侧边栏有很多功能。你很快就会看到其中的一些。
➤ 回到RESTed
中,保持选择GET
方法。打开首选项,勾选漂亮打印响应和应用语法高亮的方框:
➤ 关闭首选项并单击发送请求。
这里是响应时间、请求头、状态代码为200 OK
的响应头。
内容类型是application/vnd.api+json; charset=utf-8
。这里的关键信息是json
。这告诉你如何对响应体进行解码。
Note
UTF-8
字符串编码是Unicode
的一个版本,对于存储常规文本非常有效,但对于特殊符号或非西方字母则不太有效。不过,它仍然是当今处理Unicode
文本的最流行的方式。
➤ 向下滚动以查看响应体。
滚动浏览响应体,更容易看到有四个键的顶层字典:data
, included
, links
和meta
。你的应用程序需要的信息在data
值中,它是一个字典的数组。
数组中的每个字典包含一个内容项的attributes
。bit.ly/3sMFjdy描述了这些属性。在下一章中,你将使用JSONDecoder
来提取你想要的属性,并将它们存储在Episode
结构中,这样你就可以在RWFreeView
中显示它们。
媒体URLs
¶
➤ 注意,card_artwork_url
是一个URL
。继续在RESTed
中复制粘贴这些URL
之一并发送请求。
响应标题Content-Type
现在是image/png
,服务器是AmazonS3
。(内容响应的服务器是nginx
。)RESTed
能够显示图像。
你已经用uri
属性在浏览器中打开了RWFreeView
的一个情节。
你将使用video_identifier
值来获取视频的URL
,在PlayerView
中播放。例如,"SwiftUI vs. UIKit"
的video_identifier
是3021
。
➤ 以RESTed
方式发送此请求:
https://api.raywenderlich.com/api/videos/3021/stream
响应主体包含这个url
:
"url": "https://player.vimeo.com/external/357115704.m3u8?
s=19d68c614817e0266d6749271e5432675a45c559&oauth2_token_id=897711146"
➤ 这是你要传递给PlayerView
的链接。RESTed
不能播放它,所以把它粘贴到Safari
中,以看到它加载视频。
这就是服务器提供访问额外资源的方式。图片和视频并不直接嵌入搜索结果中,但你会得到一个URL
,允许你单独访问每个项目。
排序¶
/contents
的API
文档说你可以根据popularity
或released_at
排序,并告诉你可以根据哪些属性进行过滤。
➤ 在RESTed
中,重新发送内容请求:
https://api.raywenderlich.com/api/contents
➤ 扫描前几个数组项的released_at
值,你会看到默认的排序顺序是逆时间顺序。第一个项目是最近发布的,而后面的项目是较早发布的。
所以要改变请求,首先向你展示最受欢迎的项目。
➤ 在参数部分,单击+
来添加参数名称排序,参数值为-popularity
:
你要求的是相反的数字顺序,因为更高的popularity
值表示更受欢迎的内容项目。
➤ 点击发送请求。
现在,第一个数组项目的released_at
日期是2013
年,它的流行值是657882
! 当你发送这个请求时,它可能会更高。
过滤¶
➤ 向下滚动到响应体的底部,找到值为total_result_count
的meta
键。
2420
个项目符合/contents
请求中的过滤器。当你看到这篇文章时,可能会更多。这些项目中有许多不是关于iOS
或Swift
的。你怎么能只请求iOS
和Swift
项目?
在API
文档中,你可以过滤:列表中包括domain_ids
,这听起来像是一个解决方案的可能性。
➤ 在API
网页的侧边栏中,单击/domains
,然后单击其200 OK
按钮:
domains
请求已经完成,你将很快看到响应。但首先...
➤ 点击选择语言...(点击文本;这个按钮上的披露箭头不做任何事情):
这个工具可以用几种编程语言为这个HTTP
请求生成代码,包括cURL
和Swift
!
➤ 选择Swift
:
你将在下一章中写出与此类似的东西。这个工具很有用,既可以作为你的代码的起点,也可以用来检查你有没有忘记什么。
代码示例部分下面是响应。
➤ 继续滚动以查看正文:
它印制得很漂亮,比RESTed
多了一点色彩。而且,毫不奇怪,"iOS & Swift"
的id
值为1
。这是你创建你的过滤器端点所需要的参数值。
Note
对/contents
请求的响应已经在included
键的值中包含了这个信息。
➤ 关闭/domains
侧边栏,回到/contents
文档中,看看如何创建过滤器端点。
下面是这个例子:
➤ 因此,在RESTed
中,添加一个参数,名称为filter[domain_ids][]
,值为1
:
➤ 再添加两个参数:
- Name: filter[content_types][]
- Value: episode
和
- Name: filter[subscription_types][]
- Value: free
➤ 点击发送请求。
现在第一项是"SwiftUI vs. UIKit"
,流行值为43193
。
在RESTed
中设置了这些参数后,你可以很容易地通过取消勾选它的复选框来关闭其中任何一个参数。或者,你可以改变一个参数值,以检索不同的域或内容类型。
蜂巢式控制台¶
侧边栏有一个功能,看起来很好,但不太行得通。
➤ 打开/contents
侧边栏,点击尝试控制台,得到一个可以添加相同参数的表格:
但当你发送请求时,回应是我们遇到了一个错误。请稍后再试。
将生产改为调试代理可以工作,但不能漂亮地打印响应体。使用Mock Server
可以工作并且pretty-prints
,但在2018
年7
月24
日之后不包含任何结果。另外,你不能只关闭一些参数。重置值会删除所有参数。
URL-encoding
¶
当RESTed
或Apiary
将参数组合成查询字符串时,你可能注意到一些奇怪的符号:
https://api.raywenderlich.com/api/contents?
filter%5Bsubscription_types%5D%5B%5D=free&
filter%5Bdomain_ids%5D%5B%5D=1&
filter%5Bcontent_types%5D%5B%5D=episode&
sort=-popularity
Note
为了便于阅读,我缩进了这些行。这个字符串不能作为一个URL
使用。
RESTed
和Apiary
将你在参数名称中使用的方括号编码为%5B
和%5D
。通过互联网发送的URL只能包含字母、数字和这些标点符号:-
、_
、.
和~
。
其他标点符号,包括/
、?
和%
,被编码为一对十六进制数字,前面是转义字符%
。十六进制值是该字符在ASCII
中的字节值,例如,空格字符为20
(十进制为32
),%
字符为25
(十进制为37
)。空格字符也可以被编码为+
。对于非ASCII
字符,URL-encoding
使用其UTF-8
字节值。
当/
和?
是URL
中的分隔符时,它们不会被编码。
挑战:改变页面大小¶
➤ 向下滚动到响应体的底部,找到links
项。
links
的值是一个有五个键的字典。self
, first
, prev
, next
和 last
. 这些值是查询URL
。当前的响应是self
和first
。prev
键没有值,因为没有上一页。
分页是由两个你没有设置的查询词控制的。例如,next URL
包括这些参数:
page%5Bnumber%5D=2&page%5Bsize%5D=20
URL-decoded
,这是page[number]=2&page[size]=20
。所以next
有20
个项目,self
和first
页也是如此。最后一页有page[number]=22
和page[size]=20
,但实际上只有9
个项目,因为total_result_count
是429
。
你将在下一章实现的功能之一是让用户改变页面大小。你可能非常确定你需要设置page[size]
参数。
你的挑战是发送一个RESTed
请求,将页面大小改为5
(这将更容易计算少量的响应项)。
Note
这在Apiary
的Introduction ▸ Pagination
部分都有记录。
这是我的RESTed
请求和响应:
项目资料中的最后一个文件夹已经保存了本节中所有请求的RESTed
请求。
现在你已经为在RWFreeView
中实现HTTP
消息做好了准备。
POST
请求和认证¶
RWFreeView
不需要本节中的任何东西,但你未来的应用程序可能需要。
RWFreeView
只需要从服务器上获取资源,而且因为它只获取免费项目,所以你的用户不需要认证。
你通常需要为那些让用户访问受限材料或创建、更新或删除服务器记录的应用程序实施认证。考虑使用Sign In with Apple
。请按照我们的教程《使用SwiftUI
登录苹果》bit.ly/3iHqNix。
为了尝试POST
请求,你将使用RESTed
来发送类似于这个GitHub curl
例子的东西:
curl -i -H \
"Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4" \
-d '{ \
"name": "blog", \
"auto_init": true, \
"private": true, \
"gitignore_template": "nanoc" \
}' \
https://api.github.com/user/repos
这个例子展示了如何创建一个新的GitHub
仓库,所以它需要GitHub
用户认证。还记得当你在Xcode
中设置GitHub
账户时,你必须生成一个个人访问令牌吗?这里也需要一个。
➤ 如果你没有保存GitHub
个人访问令牌的纯文本副本,请在bit.ly/2Y71Ofh上生成一个新的。
➤ 在RESTed
中,添加头域授权与头值令牌。在token
后面粘贴你的个人访问令牌来完成这个值:
➤ 将端点设置为https://api.github.com/user/repos
,方法设置为 POST
,然后选择Form-encoded
,并设置参数名称 api-test-repo
,auto_init true
,private false
。
Note
如果你没有看到参数字段和HTTP
正文,请确保两个按钮都处于激活状态。
POST
请求参数不会追加到端点,而是出现在正文字段中。
➤ 单击发送请求。
这是一个故意的"失误",向你展示如果服务器期望POST
请求体是JSON
格式,但你却发送了表单编码的数据会发生什么。表单编码的数据看起来像URL
的查询字符串部分,因为网络表单就是这样发送的。
➤ 将表单编码改为JSON
编码,然后单击发送请求。
成功了! 检查你的GitHub
账户,看是否真的有一个名为api-test-repo
的新仓库:
➤ 再次点击发送请求。
如果你试图再次创建相同的repo
,服务器会返回错误信息"name already exists on this account"
以及这个端点的 documentation_url
。
关键点¶
- 客户端应用程序向服务器发送
HTTP
请求,而服务器则返回响应。 - 一个
HTTP
响应包含一个状态代码和一些内容。文本内容通常是JSON
格式,可能包含客户端应用程序可以用来访问媒体资源的URI
。 HTTP
请求遵循服务器REST API
的规则,其文档规定了资源端点、HTTP
方法和标头,以及如何构建POST
和PUT
请求体。- 你可以在一个浏览器应用程序中发送简单的
GET
请求。使用cURL
或像RESTed
这样的应用来创建和发送请求并检查响应。 api.raywenderlich.com
的文档包括一个侧边栏,你可以创建一个HTTP
请求,然后为你的应用程序生成Swift
代码,或者将请求发送到Mock Server
或Debugging Proxy
服务器。