1.3 当你访问一个网址时发生了什么
因为我们将在本书中重点介绍HTTP消息,所以本节将从较高的层次概述在浏览器的地址栏中输入URL后经历的过程。
第一步:提取域名
一旦你输入http://www.google.com/,浏览器会根据URL确定域名。域名标识你要访问的网站,并且必须遵守RFC定义的特定规则。例如,域名只能包含字母、数字和下划线。国际化域名是一个例外,这超出了本书的范围,如果想了解它们的用法,请参考RFC 3490。在我们列举的例子中,域名是www.google.com。域名是用于查找服务器地址的一种方式。
第二步:解析IP地址
确定域名后,浏览器使用IP来查找与域名关联的IP地址,此过程称为解析IP地址。互联网上的每个域名都必须被解析为IP地址才能工作。
IP地址存在两种类型:Internet协议版本4(IPv4)和Internet协议版本6(IPv6)。IPv4地址是由以点连接的四个数字组成的,每个数字在0到255之间。IPv6是Internet协议的最新版本,它是为了解决“可用IPv4地址耗尽”的问题而设计的。IPv6地址由八组四位十六进制数字组成,用冒号隔开,而且也有缩写的方法。例如,8.8.8.8是IPv4地址,2001:4860:4860::8888是IPv6地址的缩写。
如果仅使用域名查找IP地址,计算机会向域名系统(DNS)服务器发送请求。DNS服务器由互联网上的专用服务器组成,这些服务器具有所有域名及其匹配的IP地址的注册表。前面IPv4和IPv6地址用到的是Google DNS服务器。
在这个例子中,连接到的DNS服务器将匹配到www.google.com的IPv4地址216.58.201.228并发送回你的计算机。要了解有关站点IP地址的更多信息,可以从你的终端使用命令dig A site.com并用你正在查找的网站替换site.com。
第三步:建立一个TCP连接
因为使用http://访问网址,所以随后计算机会尝试与80端口上的IP地址建立TCP连接。TCP的细节并不重要,我们只需注意它是另一种定义“计算机如何相互通信”的协议即可。TCP提供了双向通信,这样消息接收者就可以验证对方是否接收到了信息,并且在传输过程中不会丢失任何内容。
你要向其发送请求的服务器可能正在运行多个服务(将服务视为计算机程序),因此它使用端口来标识接收请求的特定进程。你可以将端口视为服务器通向互联网的入口。如果没有端口,服务器端将不得不对发送到同一地点的信息进行竞争。这意味着我们需要另一个标准来定义服务如何相互协作,并确保一个服务的数据不会被另一个服务窃取。例如,端口80是发送和接收未加密的HTTP请求的标准端口。另一个公共端口是443,用于处理加密的HTTPS请求。虽然端口80是HTTP的标准端口,443是HTTPS的标准端口,但是TCP通信可以在任何端口上发生,这取决于管理员如何配置应用程序。
通过打开终端并运行nc <IP ADDRESS> 80,你可以在端口80上建立自己的TCP连接。这一命令行使用Netcat utility中的nc命令创建用于读写消息的网络连接。
第四步:发送一个HTTP请求
继续把http://www.google.com/作为例子,如果在第三步连接成功,你的浏览器应该准备并发送一个HTTP请求,如列表1-1所示。
列表1-1 发送一个HTTP请求
浏览器向路径/发出GET请求(❶),这是网站的根路径。网站的内容根据路径进行组织,就像计算机上的文件夹和文件一样。当进一步访问每个文件夹时,所走的路径都是通过记录每个文件夹的名称后面跟一个“/”来表示的。当你访问网站的第一页时,将访问根路径,它只是一个“/”。浏览器还指示它使用的是1.1版本的HTTP协议。GET请求只获取信息。我们之后会了解更多有关GET方法的知识。
Host头(❷)包含请求中发送的一部分附加信息。HTTP 1.1需要Host头来找到服务器的IP地址以确定请求应该发向哪里,因为IP地址可以对应多个域名。连接头(❸)表示请求与服务器的连接保持打开状态,以避免不断打开和关闭连接。
你可以在❹中知道预期的响应格式。在本例中,我们期望返回的格式是application/html,同时通过通配符(*/*)可以接受任何响应格式。目前有数百种可能的内容类型,但对我们来说,application/html、application/json、application/octet-stream、text/plain是比较常用的。最后,User-Agent(❺)表示发送请求的软件系统。
第五步:服务器响应
为了响应请求,服务器应该使用类似于列表1-2的内容进行响应。
列表1-2 服务器响应
这里,我们收到一个状态码为200的HTTP响应(❶)。状态码很重要,因为它代表了服务器的响应状态。同样由RFC定义,这些状态码通常是以2、3、4或5开头的三位数字。虽然没有严格要求服务器使用特定代码,但2xx通常表示请求成功。
由于这里服务器没有严格执行HTTP状态码的使用规则,因此即使HTTP报文解析存在应用程序错误,也可能会看到一些状态码是200的响应。HTTP报文是一段与请求或响应相关联的文本(❸)。在本例中,我们删除了内容并将其替换为“--snip--”,因为Google的响应体太大了。对于Web页面,响应内容通常是HTML,但对于应用程序编程接口(API),响应内容可能是JSON,而对于文件下载,响应内容则可能是一个文件,等等。
Content-Type头(❷)通知浏览器的媒体类型。媒体类型决定浏览器如何呈现内容。但浏览器并不总是使用从应用程序返回的值;相反,浏览器执行MIME sniffing,通过读取正文内容的第一位来确定自己的媒体类型。应用程序通过包含头文件X-Content-Type-Options:nosniff,可以禁止浏览器使用MIME sniffing这种方式。
其他以3开头的响应状态码表示重定向,指示浏览器发出附加请求。例如,理论上,如果Google需要永久性地将一个URL重定向到另一个URL,可以使用301响应。相反,302是临时重定向。
当收到3xx响应状态码时,浏览器应向Location头中指定的URL发出新的HTTP请求,如下所示:
以4开头的响应通常表示用户错误。例如,尽管提供了一个有效的HTTP请求,但请求不包括授权访问内容的正确标识时就会响应403。以5开头的响应表示某种类型的服务器错误,例如503表示服务器无法处理发送的请求。
第六步:呈现响应
因为服务器发送了一个状态码为200,内容类型为text/html的响应,浏览器将开始呈现它收到的内容。响应体会告诉浏览器应该向用户显示什么。
例如,这个响应体将包括页面结构的HTML,用于样式和布局的级联样式表(CSS),以及用于添加附加动态功能和媒体文件(如图像或视频)的JavaScript。服务器也能返回其他内容,例如XML,但是仍以本例的内容为准。第11章中更详细地讨论了XML。
因为网页可以引用外部文件,如CSS、JavaScript和媒体文件,所以浏览器可能会对网页的所有必需文件发出额外的HTTP请求。当浏览器请求这些附加文件时,它会继续解析响应并将正文作为网页呈现给你。在本例中,它将呈现Google的主页:www.google.com。
请注意,JavaScript是每种主流浏览器都支持的脚本语言。JavaScript允许网页具有动态功能,包括在不重新加载网页的情况下更新网页内容,(在某些网站上)检查密码是否足够强,等等。与其他编程语言一样,JavaScript具有内置函数,可以将值存储在变量中,并运行代码以响应Web页面上的事件。它还可以访问各种浏览器API。这些API使JavaScript能够与其他系统交互,其中最重要的可能是文档对象模型(Document Object Model,DOM)。
DOM允许JavaScript访问和操作网页的HTML和CSS。这一点很重要,因为如果攻击者可以在站点上执行自己的JavaScript,那么他们就可以访问DOM,并可以以目标用户的名义在站点上执行操作。第7章中会进一步探讨这一部分内容。