跳转至

第23章:足够多的网络内容

本章涵盖了一些关于iOS应用程序和Web服务器之间的HTTP消息的基本信息。这足以让你为后面的章节做好准备,在这些章节中你将实现RWFreeView的服务器下载。

这一章中没有SwiftUI

如果你已经对HTTP消息了如指掌,请跳到"探索api.raywenderlich.com"一节,熟悉一下你在下面几章中要用到的API

服务器和资源

HTTP requests and responses between client and server

许多应用程序与互联网上的计算机通信,以访问数据库和其他资源。我们称这些计算机为网络服务器,以回溯到最初的"万维网"。或云服务器,因为现在一切都在"云"中。"主机"是"服务器"的另一个术语。

SafariRWFreeView这样的应用程序是这些服务器的客户端。客户端向服务器发送请求,而服务器则发回一个响应。这种通信由符合超文本传输协议(HTTP)的纯文本信息组成。超文本是结构化的文本,在包含文本的节点之间使用超链接。网页是用超文本标记语言(HTML)编写的。

HTTP有几种方法,包括POSTGETPUTDELETE。这些方法对应于数据库中的创建、读取、更新和删除功能。

客户端通常要求访问由服务器控制的资源。要访问互联网上的一个资源,你需要它的通用资源标识符(URI)。这可能是一个通用资源定位器(URL),它指定了资源的位置(服务器和路径)以及你应该使用的协议来访问它。

例如,https://raywenderlich.com/library是一个指定HTTPS协议的URL,用来访问位于raywenderlich.com服务器上的资源,路径为library。在上一章中,计算属性linkURLString使用一个URI,如rw://betamax/videos/3021来创建该剧集raywenderlich.com页面的URL

Note

HTTPSHTTP的安全、加密版本。它可以保护你的用户不被窃听。底层协议是相同的,但不是传输纯文本信息,而是在离开客户端或服务器之前对一切进行加密。

HTTP消息

客户端的HTTP请求信息包含头文件。一个POSTPUT请求有一个主体,包含新的或更新的数据。一个GET请求通常有参数来过滤、分类或量化它想从服务器获得的数据。

服务器的HTTP响应信息也有头文件和正文。响应的一个关键部分是状态代码--理想情况下,对GET请求的响应是200 OK,对POST请求的响应是201 Created。你不希望看到任何错误状态代码,如404 Not Found

GitHub’s 404 page

HTTP响应状态代码有很多很多。你会在https://http.cat找到它们的有趣表示。比如说。

418 I’m a teapot

Mozillamzl.la/3o6qWNM)对状态代码提供了更常规的描述。

HTTP 418 I'm a teapot client error response code表示服务器拒绝冲泡咖啡,因为它永久是一个茶壶。一个暂时没有咖啡的组合咖啡/茶壶应该返回503。这个错误是指1998年和2014年愚人节笑话中定义的超文本咖啡壶控制协议。一些网站对他们不希望处理的请求使用这种响应,例如自动查询。

Note

1998年的HTCPCPbit.ly/3olM42q)愚人节笑话的灵感来自木马房咖啡壶(bit.ly/3pkCDSb),这是世界上第一个网络摄像机的主题。它建立于1991年,远早于物联网(IoT)。

如果一个HTTP消息有一个主体,它也有一个Content-Type头。Content-Type指定了HTTP消息正文中数据的互联网媒体类型。

通常情况下,你会根据结构的不同,为文本数据使用三种内容类型。

  • JSONJavaScript对象符号)是应用程序客户端用于HTTP通信的最常见的数据格式。它是一种结构化的数据格式,由数字、字符串和数组及字典组成,可以包含字符串、数字和嵌套数组及字典。
  • 网络表格使用表单编码,它看起来像一个查询字符串。查询字符串是一个键值对的集合,用&分开,前面是?
  • 网页是HTML

在处理二进制数据时,一些最常用的类型是。PDF、图像格式和多部分表格数据,当客户端将任何类型的二进制文件与文本元素一起发送。

REST API

在第12章"苹果应用开发生态系统"中,你了解了可以用来开发iOS应用的众多框架。苹果框架是一种应用程序编程接口(API)。它告诉你如何使用由苹果工程师创建的标准组件。

另一种API是客户端从服务器请求资源的规则集。你将为你的应用程序使用的大多数APIREST API,它使用HTTP。对于服务器上的每个资源,REST API文档告诉你如何构建一个请求。

  • 资源的URL,称为其端点。
  • 使用哪种HTTP方法。
  • 要包括哪些HTTP头信息。
  • 在请求正文中要写些什么。

Note

REST是"REpresentational State Transfer"的缩写,是Roy Fielding为万维网的架构风格所起的名字。这个术语描述了一个设计良好的Web应用是如何工作的。用户从网络资源(虚拟状态机)的网络中选择一个资源标识符,并使用GETPOST等方法来创建一个状态转换,将资源的表示转移给用户。

在下一章,你将设置RWFreeViewREST API api.raywenderlich.com通信。在这一章中,你将在raywenderlich.docs.apiary.io探索这个API的文档。

发送和接收HTTP信息

即使有很好的文档,你通常也要做一些实验,以弄清楚如何构建请求以获得你想要的确切资源,以及如何从服务器的响应中提取这些资源。那么,你如何发送请求和检查响应呢?

浏览器

进行简单的HTTP GET请求的最简单的方法是在Safari这样的浏览器应用程序中输入URL

➤ 在你最喜欢的浏览器中输入这个URL

https://www.raywenderlich.com/library

这是raywenderlich.com库的端点。你会得到一个与此类似的页面。

HTTP response to raywenderlich.com/library request

这是服务器响应的主体,但你无法看到头信息。而且,除了一个简单的GET请求,你不能做更多的事情。

cURL

浏览器是一个完全自动化的HTTP工具。在光谱的另一端是命令行工具cURLcurl.se)--"成千上万的软件应用的互联网传输骨干"。

REST API的文档通常会提供示例请求来告诉你如何使用它。很多时候,这些都是使用cURL

➤ 打开终端,输入这个命令:

curl https://api.github.com/zen

你向GitHubAPI服务器发送一个HTTP请求。响应是他们设计理念中的一个随机项目,比如"偏重于功能"或"避免管理上的分心"。

GitHubGetting started with the REST APIbit.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-limitx-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

这是对你将用于RWFreeViewAPI的一个请求。响应是相当令人头疼的:

Response body using cURL

如果你集中注意力,你也许能从这个输出中看到,响应体是一个字典,其中第一个值"data"是一个字典数组。你可以使用像 codebeautify.org/jsonviewer这样的工具来格式化它,这样就更容易阅读了。

Response body at 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和一个披露指标:

There’s a button here...

➤ 这是一个按钮:点击它!

The request endpoint

一个侧边栏打开,显示请求。这个侧边栏有很多功能。你很快就会看到其中的一些。

➤ 回到RESTed中,保持选择GET方法。打开首选项,勾选漂亮打印响应和应用语法高亮的方框:

Turn on pretty-print and syntax highlighting.

➤ 关闭首选项并单击发送请求。

RESTed response headers

这里是响应时间、请求头、状态代码为200 OK的响应头。

内容类型是application/vnd.api+json; charset=utf-8。这里的关键信息是json。这告诉你如何对响应体进行解码。

Note

UTF-8字符串编码是Unicode的一个版本,对于存储常规文本非常有效,但对于特殊符号或非西方字母则不太有效。不过,它仍然是当今处理Unicode文本的最流行的方式。

➤ 向下滚动以查看响应体。

RESTed response body

滚动浏览响应体,更容易看到有四个键的顶层字典:data, included, linksmeta。你的应用程序需要的信息在data值中,它是一个字典的数组。

数组中的每个字典包含一个内容项的attributesbit.ly/3sMFjdy描述了这些属性。在下一章中,你将使用JSONDecoder来提取你想要的属性,并将它们存储在Episode结构中,这样你就可以在RWFreeView中显示它们。

媒体URLs

➤ 注意,card_artwork_url是一个URL。继续在RESTed中复制粘贴这些URL之一并发送请求。

RESTed: Card artwork

响应标题Content-Type现在是image/png,服务器是AmazonS3。(内容响应的服务器是nginx。)RESTed能够显示图像。

你已经用uri属性在浏览器中打开了RWFreeView的一个情节。

你将使用video_identifier值来获取视频的URL,在PlayerView中播放。例如,"SwiftUI vs. UIKit"video_identifier3021

➤ 以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中,以看到它加载视频。

Open video URL in Safari.

这就是服务器提供访问额外资源的方式。图片和视频并不直接嵌入搜索结果中,但你会得到一个URL,允许你单独访问每个项目。

排序

/contentsAPI文档说你可以根据popularityreleased_at排序,并告诉你可以根据哪些属性进行过滤。

➤ 在RESTed中,重新发送内容请求:

https://api.raywenderlich.com/api/contents

➤ 扫描前几个数组项的released_at值,你会看到默认的排序顺序是逆时间顺序。第一个项目是最近发布的,而后面的项目是较早发布的。

所以要改变请求,首先向你展示最受欢迎的项目。

➤ 在参数部分,单击+来添加参数名称排序,参数值为-popularity

Request: sort on popularity

你要求的是相反的数字顺序,因为更高的popularity值表示更受欢迎的内容项目。

➤ 点击发送请求。

Response: sort on popularity

现在,第一个数组项目的released_at日期是2013年,它的流行值是657882! 当你发送这个请求时,它可能会更高。

过滤

➤ 向下滚动到响应体的底部,找到值为total_result_countmeta键。

Contents meta item

2420个项目符合/contents请求中的过滤器。当你看到这篇文章时,可能会更多。这些项目中有许多不是关于iOSSwift的。你怎么能只请求iOSSwift项目?

API文档中,你可以过滤:列表中包括domain_ids,这听起来像是一个解决方案的可能性。

➤ 在API网页的侧边栏中,单击/domains,然后单击其200 OK按钮:

Apiary /domains sidebar

domains请求已经完成,你将很快看到响应。但首先...

➤ 点击选择语言...(点击文本;这个按钮上的披露箭头不做任何事情):

Apiary sidebar: Select Language...

这个工具可以用几种编程语言为这个HTTP请求生成代码,包括cURLSwift!

➤ 选择Swift

Apiary sidebar: generated Swift code

你将在下一章中写出与此类似的东西。这个工具很有用,既可以作为你的代码的起点,也可以用来检查你有没有忘记什么。

代码示例部分下面是响应。

➤ 继续滚动以查看正文:

Apiary sidebar: /domains response body

它印制得很漂亮,比RESTed多了一点色彩。而且,毫不奇怪,"iOS & Swift"id值为1。这是你创建你的过滤器端点所需要的参数值。

Note

/contents请求的响应已经在included键的值中包含了这个信息。

➤ 关闭/domains侧边栏,回到/contents文档中,看看如何创建过滤器端点。

下面是这个例子:

Apiary /contents: filter example

➤ 因此,在RESTed中,添加一个参数,名称为filter[domain_ids][],值为1

Request: filter on domain id

➤ 再添加两个参数:

  • Name: filter[content_types][]
  • Value: episode

  • Name: filter[subscription_types][]
  • Value: free

Request: filter on content and subscription types

➤ 点击发送请求。

Response: most popular free iOS & Swift episodes

现在第一项是"SwiftUI vs. UIKit",流行值为43193

RESTed中设置了这些参数后,你可以很容易地通过取消勾选它的复选框来关闭其中任何一个参数。或者,你可以改变一个参数值,以检索不同的域或内容类型。

RESTed lets you edit or turn off parameters.

蜂巢式控制台

侧边栏有一个功能,看起来很好,但不太行得通。

➤ 打开/contents侧边栏,点击尝试控制台,得到一个可以添加相同参数的表格:

Apiary sidebar: Try console

但当你发送请求时,回应是我们遇到了一个错误。请稍后再试。

Apiary sidebar: Try console: select server

将生产改为调试代理可以工作,但不能漂亮地打印响应体。使用Mock Server可以工作并且pretty-prints,但在2018724日之后不包含任何结果。另外,你不能只关闭一些参数。重置值会删除所有参数。

URL-encoding

RESTedApiary将参数组合成查询字符串时,你可能注意到一些奇怪的符号:

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使用。

RESTedApiary将你在参数名称中使用的方括号编码为%5B%5D。通过互联网发送的URL只能包含字母、数字和这些标点符号:-_.~

其他标点符号,包括/?%,被编码为一对十六进制数字,前面是转义字符%。十六进制值是该字符在ASCII中的字节值,例如,空格字符为20(十进制为32),%字符为25(十进制为37)。空格字符也可以被编码为+。对于非ASCII字符,URL-encoding使用其UTF-8字节值。

/?URL中的分隔符时,它们不会被编码。

挑战:改变页面大小

➤ 向下滚动到响应体的底部,找到links项。

Filter query response: links

links的值是一个有五个键的字典。self, first, prev, nextlast. 这些值是查询URL。当前的响应是selffirstprev键没有值,因为没有上一页。

分页是由两个你没有设置的查询词控制的。例如,next URL包括这些参数:

page%5Bnumber%5D=2&page%5Bsize%5D=20

URL-decoded,这是page[number]=2&page[size]=20。所以next20个项目,selffirst页也是如此。最后一页有page[number]=22page[size]=20,但实际上只有9个项目,因为total_result_count429

你将在下一章实现的功能之一是让用户改变页面大小。你可能非常确定你需要设置page[size]参数。

你的挑战是发送一个RESTed请求,将页面大小改为5(这将更容易计算少量的响应项)。

Note

这在ApiaryIntroduction ▸ Pagination部分都有记录。

这是我的RESTed请求和响应:

Set page size = 5

项目资料中的最后一个文件夹已经保存了本节中所有请求的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后面粘贴你的个人访问令牌来完成这个值:

Authorization header with (dummy) personal access token

➤ 将端点设置为https://api.github.com/user/repos,方法设置为 POST,然后选择Form-encoded,并设置参数名称 api-test-repoauto_init trueprivate false

POST request parameters: Form-encoded

Note

如果你没有看到参数字段和HTTP正文,请确保两个按钮都处于激活状态。

POST请求参数不会追加到端点,而是出现在正文字段中。

➤ 单击发送请求。

Response: 400 Bad Request. Problems parsing JSON

这是一个故意的"失误",向你展示如果服务器期望POST请求体是JSON格式,但你却发送了表单编码的数据会发生什么。表单编码的数据看起来像URL的查询字符串部分,因为网络表单就是这样发送的。

➤ 将表单编码改为JSON编码,然后单击发送请求。

JSON-encoded parameters: Response: 201 Created

成功了! 检查你的GitHub账户,看是否真的有一个名为api-test-repo的新仓库:

GitHub: New repository created

➤ 再次点击发送请求。

GitHub: 422 Unprocessable Entity

如果你试图再次创建相同的repo,服务器会返回错误信息"name already exists on this account"以及这个端点的 documentation_url

关键点

  • 客户端应用程序向服务器发送HTTP请求,而服务器则返回响应。
  • 一个HTTP响应包含一个状态代码和一些内容。文本内容通常是JSON格式,可能包含客户端应用程序可以用来访问媒体资源的URI
  • HTTP请求遵循服务器REST API的规则,其文档规定了资源端点、HTTP方法和标头,以及如何构建POSTPUT请求体。
  • 你可以在一个浏览器应用程序中发送简单的GET请求。使用cURL或像RESTed这样的应用来创建和发送请求并检查响应。
  • api.raywenderlich.com的文档包括一个侧边栏,你可以创建一个HTTP请求,然后为你的应用程序生成Swift代码,或者将请求发送到Mock ServerDebugging Proxy服务器。