65.9K
CodeProject 正在变化。 阅读更多。
Home

HTTP 上的对话

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (16投票s)

2018 年 1 月 2 日

CPOL

11分钟阅读

viewsIcon

15563

downloadIcon

228

HTTP 入门指南

引言

作为日常工作,您启动一个网页浏览器,在浏览器的地址栏中输入类似 http://peterleowblog.com 的文本,然后等待网页加载到浏览器中。一个典型的网页由文本、图像和指向其他网页的链接组成。然后,您可以通过点击其中一个链接导航到另一个网页。对于普通用户来说,这就是他们所关心的一切。然而,对于网页开发人员来说,这种司空见惯的做法背后却隐藏着更多——浏览器和网页服务器之间因每个用户页面请求而触发的看不见的会话。

管理浏览器和网页服务器之间通信的协议正是众所周知的 HTTP——超文本传输协议。HTTP 是一种基于文本的无状态协议,不记忆先前的通信。一个典型的 HTTP 会话始于客户端(通常是网页浏览器)建立与网页服务器的连接,接着是一系列请求-响应循环,简而言之是:

步骤 1。客户端通过 URL(例如 http://www.example.com)向网页服务器发送格式化为 HTTP 请求消息的请求,并等待响应。

步骤 2。收到请求后,URL 处的网页服务器处理请求,并将其答案格式化为 HTTP 响应消息发送回客户端。

步骤 3。后续请求重复步骤 1。

除了提供静态 HTML 文件,网页服务器还可以借助 PHP 引擎和 MySQL 等其他软件,通过服务器端脚本解析和数据库操作,动态生成内容。

请求和响应消息共享相似的结构——每个都由一系列文本指令组成,通过 CRLF(回车符,后跟换行符)分隔,并组织成三个部分:开头的起始行部分、中间包含一些头部字段和空行的头部部分,以及消息末尾包含任何有效载荷的数据部分。

眼见为实

让我们来看一个例子:在您本地网页服务器的文档根目录中,创建一个名为 testsite 的目录,其中包含一个名为 index.html 的 HTML 文件和一个名为 ball.png 的图像文件。该 HTML 文件包含以下 HTML 标记:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HTTP Headers</title>
</head>
<body>
<h1>HTTP Headers</h1>
<img src="ball.png">
</body>
</html>

在浏览器中,此 HTML 文件将呈现为一个网页,如图 1 所示:

Figure 1: index.html

图 1:index.html

要获得图 1 所示的页面,请启动网页服务器,然后在浏览器地址栏中输入以下 URL:

https:///testsite/index.html

通过这种方式,您无法看到通过 HTTP 进行的原始会话。让我们跳过浏览器部分,改用文本终端(例如 Windows 的命令提示符)通过 telnet 会话访问 index.html。跟我来...

通过 Telnet 进行 HTTP

在 Windows 的命令提示符中,输入:

telnet localhost 80

然后按 Enter 键打开到网页服务器端口 80 的连接。接下来,将以下文本(包括表示头部部分结束的必需空行)复制并粘贴到终端,然后按 Enter 键:

GET /testsite/index.html HTTP/1.1
Host: localhost
Accept: text/html 

您刚刚无意中编写并向网页服务器提交了一个 HTTP 请求消息(通常由浏览器完成)。

收到 index.html 文件的 HTTP 请求消息后,网页服务器定位 index.html 并将其内容嵌入到 HTTP 响应消息中,该消息可能在终端中显示为以下文本,返回给客户端:

HTTP/1.1 200 OK
Date: Thu, 16 Nov 2017 16:40:10 GMT
Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2h PHP/5.6.28
Last-Modified: Thu, 16 Nov 2017 16:28:27 GMT
ETag: "a4-55e1c1c1e1486"
Accept-Ranges: bytes
Content-Length: 164
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HTTP Headers</title>
</head>
<body>
<h1>HTTP Headers</h1>
<img src="ball.png">
</body>
</html>

查看图 2 中动画显示的整个过程:

图 2:通过 Telnet 进行 HTTP

注意

HTTP 请求消息以请求行开始,请求行包含应用于资源的方法(GET)、资源的标识符(/testsite/index.html)和正在使用的 HTTP 协议版本(HTTP/1.1)。其后是一些请求头,以 headerFieldName=value 对的形式(Host: localhostAccept: text/html)包含有关请求的附加信息到网页服务器。请注意,请求头部分以一个空行结束,如果需要向服务器发送任何有效负载,则数据部分应在此之后,本例中没有。我们将很快探讨另一个带有有效负载的 HTTP 请求消息示例。

查看以下链接中的不同请求方法和请求头:

注意

HTTP 响应消息以状态行开始,状态行包含正在使用的 HTTP 协议版本(HTTP/1.1)、一个 3 位数字状态码(200)以及与状态码关联的原因短语(OK)。其后是一些响应头,以 headerFieldName=value 对的形式(Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2h PHP/5.6.28Content-Length: 164 等)包含有关响应的附加信息。有效载荷,即所请求资源(index.html)的内容,在表示响应头部分结束的空行(仅包含一个 CRLF)之后。查看以下链接中的不同状态码和响应头:

通过网页进行 HTTP

除了繁琐的终端,您实际上可以使用现代浏览器提供的开发者工具查看原始的 HTTP 会话。访问开发者工具的快速方法是:在 Chrome 或 Firefox 中打开一个网页,在 Windows / Linux 上按 Ctrl+Shift+I,或在 Mac 上按 Command+Option+I,您将看到开发者工具窗口在浏览器底部打开,如图 3 在 Chrome 中所示。

图 3:开发者工具

开发者工具包含一系列功能工具,允许开发人员(除其他事项外)检查 DOM、编辑 CSS、调试脚本和分析网页。它们可以通过开发者工具窗口工具栏中的选项卡列表访问。点击选项卡会打开相应的面板,您可以在其中执行该选项卡工具提供的特定任务。在这里,我们只对网络选项卡感兴趣。网络选项卡下的网络面板提供有关网页上发生的网络活动的信息,包括 HTTP 头、响应、cookie 等。

网络面板打开并选择全部筛选选项的情况下,在浏览器地址栏中输入 https:///testsite/index.html 并按 Enter 键,您应该会看到类似图 4 的屏幕:

图 4:index.html 的 HTTP 请求和响应

图 4 中的屏幕显示了头部选项卡下与 index.html 相关的 HTTP 请求和响应消息,如 Name 面板中所示。响应的有效载荷在 Response 选项卡下单独显示,如图 5 所示:

图 5:index.html 的响应有效载荷

等等,故事还没结束。您是否注意到 ball.png 出现在 Name 面板中 index.html 的下方?如果您好奇,点击它;您是否在头部选项卡下看到了另一组 HTTP 请求和响应消息,看起来像图 6 所示的那些?

图 6:ball.png 的 HTTP 请求和响应

当浏览器解析 index.html 并遇到图像标记,即 <img src="ball.png"> 时,它将启动一个新的请求-响应循环与网页服务器,以请求该图像资源。网页的地址(本例中为 index.html),即所请求图像(本例中为 ball.png)的链接源自何处,可以在名为 Referer 的请求头字段中找到,如下所示:

Referer: https:///testsite/index.html

任何在网页中指定的外部资源,如音频、视频、CSS 文件、JavaScript 文件、插件等,也同样如此。换句话说,一个网页的完整下载可能需要几个请求-响应周期,具体取决于指定的外部资源的数量。这在图 7 中有所说明:

图 7:HTTP 请求-响应循环

模仿现实世界中的 HTTP 会话

如果网页浏览器和网页服务器是真实的人类,HTTP 会话将如何以自然语言进行?试试这个:

浏览器:你好,我是 Mozilla (User-Agent: Mozilla/5.0),你能给我发送 https:///testsite/index.html 处的 HTML 文件 (Accept: text/html) 吗 (GET /testsite/index.html HTTP/1.1)?

服务器:你好,我是 Apache (Server: Apache/2.4.23)。我已成功找到该文件 (HTTP/1.1 200 OK)。它是 HTML 文本 (Content-Type: text/html)。内容是……(有效载荷)。

浏览器:你好,我是 Mozilla (User-Agent: Mozilla/5.0),我能获取在 https:///testsite/index.html 中引用的 https:///testsite/ball.png 处的图像文件 (Accept: image/*) 吗 (GET /testsite/ball.png HTTP/1.1) (Referer: https:///testsite/index.html)?

服务器:你好,我是 Apache (Server: Apache/2.4.23)。我已成功找到该文件 (HTTP/1.1 200 OK)。它是一个图像 (Content-Type: image/png)。

在上面的模拟对话中,部分句子用括号中的相应 HTTP 头部进行了注释。计算机通常不擅长处理非结构化的自然语言。为了克服这个问题,HTTP 请求和响应消息被组织和结构化成不同的头部字段,每个字段在整个 HTTP 过程中都扮演着预定义的角色和意义。

带有效载荷的 HTTP 请求

到目前为止,您已经看到了一个没有有效载荷的 HTTP 请求示例,让我们来看一个带有效载荷的。带有效载荷的请求通常由用户通过 HTML 表单提交数据发起。在 testsite 目录中,添加一个名为 enquiry.html 的 HTML 文件,其中包含以下 HTML 标记:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Books Enquiry</title>
</head>
<body>
<h1>Books Enquiry</h1>
<form action="response.php" method="get">
  My Name:<br>
  <input type="text" name="name">
  <br><br>
  Title of Book:<br>
  <input type="text" name="booktitle">
  <br><br>
  <input type="submit" value="Submit">
</form> 
</body>
</html>

在浏览器中,此 HTML 文件将呈现为一个网页,其中包含两个文本字段和一个提交按钮,如图 8 所示。

图 8:enquiry.html

现在,在相应的文本字段中输入一个姓名和一本书名,例如 Peter LeowHands-on with PHP,然后点击提交按钮。这将使用 GET 方法(在 <form> 标签的 method 属性中指定)发起一个 HTTP 请求,将输入的姓名和书名作为有效载荷发送到网页服务器,由 response.php(在 <form> 标签的 action 属性中指定)接收,该文件包含以下脚本:

// This is a very rudimental code for demo only
$name = $_REQUEST["name"];
$booktitle = $_REQUEST["booktitle"];

// Assuming there is code to search database and found that book
echo "Dear $name<br><br>The book titled \"$booktitle\" is currently on loan.";
?>

收到请求后,response.php 将读取收到的书名,假设在数据库中搜索它,然后根据搜索结果生成回复。response.php 的输出示例如图 9 所示。

图 9:response.php

如图 9 所示,通过 GET 方法发送的有效载荷数据以 name=value 对的形式表示(name=Peter+Leow&booktitle=Hands-on+with+PHP),用 & 符号分隔,并进行 URL 编码(将空格替换为 +)。它们作为查询字符串参数明显地附加到 URL。它们在 HTTP 请求消息的请求行中显示如下:

GET /testsite/response.php?name=Peter+Leow&booktitle=Hands-on+with+PHP HTTP/1.1

除了 enquiry.html,您还可以通过 telnet 会话调用 response.php 并传递数据,就像您之前所做的那样。

复制并粘贴以下文本(包括表示头部部分结束的必需空行)到 telnet 控制台,然后按 Enter 键:

GET /testsite/response.php?name=Peter+Leow&booktitle=Hands-on+with+PHP HTTP/1.1
Host: localhost
Accept: text/html 

您应该会收到 response.php 的以下响应:

HTTP/1.1 200 OK
Date: Thu, 16 Nov 2017 17:40:10 GMT
Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2h PHP/5.6.28
X-Powered-By: PHP/5.6.28
Content-Length: 80
Content_Type: text/html; charset=UTF-8

Dear Peter Leow<br><br>The book titled "Hands-on with PHP" is currently on loan.

或者,您可以使用 POST 方法将数据发送到网页服务器。以下 HTTP 请求消息使用 POST 方法通过 telnet 会话将数据作为有效载荷发送到 response.php

POST /testsite/response.php HTTP/1.1
Host: localhost
Content-Length: 43
Content-Type: application/x-www-form-urlencoded

name=Peter+Leow&booktitle=Hands-on+with+PHP

与其 GET 对应项类似,通过 POST 方法发送的有效载荷数据以 name=value 对的形式表示(name=Peter+Leow&booktitle=Hands-on+with+PHP),用 & 符号分隔,并进行 URL 编码(将空格替换为 +)。然而,与 GET 对应项不同的是,GET 方法的数据会明显地附加到 URL,而通过 POST 方法发送的有效载荷数据则嵌入在请求头部部分的结束空行之后,因此肉眼不可见。

如果您要在现实世界中模仿带有效载荷的 HTTP 请求,它可能会是这样:

enquiry.html:你好,我是 Mozilla (User-Agent: Mozilla/5.0),我的名字是 Peter Leow (name=Peter+Leow),我正在找这本名为“Hands-on with PHP”的书 (booktitle=Hands-on+with+PHP)。你们图书馆有这本书吗 (GET /testsite/response.php?name=Peter+Leow&booktitle=Hands-on+with+PHP HTTP/1.1)?

(收到 enquiry.html 的请求后,response.php 查询了这本书,发现它已被借出。)

response.php:亲爱的 Peter Leow,我是来自 Apache 的 response.php (HTTP/1.1 200 OK) (Server: Apache/2.4.23)。您正在寻找的名为“Hands-on with PHP”的书目前已被借出 (Dear Peter Leow<br><br>The book titled "Hands-on with PHP" is currently on loan.)。

对话结束

用户通常不关注通过 HTTP 进行的原始会话是如何发生的,因为它在浏览器和网页服务器之间自动发生并正常工作。然而,对于开发人员来说,理解 HTTP 的机制使他们能够,除其他事项外:

  • 通过服务器端脚本设置响应头的值,以实现某些否则无法实现的有用的功能。其中一些功能包括将浏览器重定向到特定 URL,以及将资源作为文件下载而不是在屏幕上显示。
  • 根据服务器收到的请求头中收集的数据执行分析。

历史

  • 2018年1月2日:初始版本
© . All rights reserved.