# 一、资源

也许网络中最熟悉的部分是 HTTP 地址。当我想找到一个以西兰花为特色的菜肴的食谱时，这几乎是不可能的，然后我可能会打开我的网络浏览器，在地址栏中输入`http://food.com`，进入 food.com 网站并搜索食谱。我的网络浏览器理解这个语法，知道它需要向一个名为 food.com 的服务器发出一个 HTTP 请求。我们稍后将讨论“发出 HTTP 请求”的含义以及涉及的所有网络细节。目前，我们只想关注地址:`http://food.com`。

## 资源定位器

地址`http://food.com`就是我们所说的 URL——统一资源定位符。它代表网络上的特定资源。在这种情况下，资源是 food.com 网站的主页。资源是我想在网上互动的东西。图像、页面、文件和视频都是资源。

互联网上有数十亿，如果不是数万亿的话——换句话说，有数万亿的资源。每个资源都有一个我可以用来找到它的网址。`http://news.google.com`和`http://news.yahoo.com`是不同的地方。这是两个不同的名字，两个不同的公司，两个不同的网站，因此是两个不同的网址。当然，同一网站内部也会有不同的网址。`http://food.com/recipe/broccoli-salad-10733/`是西兰花沙拉食谱页面的网址，而`http://food.com/recipe/grilled-cauliflower-19710/`仍在 food.com，但它是描述花椰菜食谱的不同资源。

我们可以将最后一个网址分成三部分:

1.  `http`, the part before the `://`, is what we call the **URL scheme**. The scheme describes *how* to access a particular resource, and in this case it tells the browser to use the hypertext transfer protocol. Later we'll also look at a different scheme, HTTPS, which is the secure HTTP protocol. You might run into other schemes too, like FTP for the file transfer protocol, and mailto for email addresses.

    `://`之后的一切都将针对特定的方案。因此，合法的 HTTP 网址可能不是合法的 mailto 网址——这两个网址实际上是不可互换的(这很有意义，因为它们描述了不同类型的资源)。

2.  `food.com`是**的主人**。该主机名告诉浏览器托管资源的计算机的名称。计算机将使用域名系统(DNS)将`food.com`转换为网络地址，然后它将准确知道向哪里发送资源请求。您也可以使用 IP 地址指定 URL 的主机部分。
3.  `/recipe/grilled-cauliflower-19710/`是**的 URL 路径**。food.com 主机应该识别此路径请求的特定资源，并做出适当的响应。

有时，网址会指向主机文件系统或硬盘上的文件。例如，网址`http://food.com/logo.jpg`可能指向 food.com 服务器上确实存在的图片。然而，资源也可以是动态的。网址`http://food.com/recipes/brocolli`可能不是指 food.com 服务器上的真实文件。相反，某种应用正在 food.com 主机上运行，它将接受该请求，并使用数据库中的内容构建资源。该应用可能使用 ASP.NET、PHP、Perl、Ruby on Rails 或其他一些网络技术构建，这些技术知道如何通过创建供浏览器显示的 HTML 来响应传入的请求。

事实上，这些天许多网站试图避免在他们的网址中有任何真实的文件名。首先，文件名通常与特定的技术相关联，比如。微软 ASP.NET 技术。许多网址将比托管和服务它们的技术更长久。其次，许多网站希望将关键词放入一个网址中(比如西兰花食谱的网址中有`/recipe/broccoli/`)。在网址中有这些关键词是搜索引擎优化的一种形式，它将在搜索引擎结果中对资源进行更高的排名。如今，描述性关键字，而不是文件名，对网址很重要。

一些资源也会导致浏览器下载额外的资源。food.com 的主页将包括图像、JavaScript 文件、CSS 和其他资源，这些资源将共同呈现 food.com 的“主页”。

![](img/img001.png)

图 1:food.com 主页

## 端口、查询字符串和片段

既然我们已经了解了 URL 方案、主机和路径，那么我们也来看看带有端口号的 URL:

```cs
  http://food.com:80/recipes/broccoli/

```

数字 80 代表主机用来监听 HTTP 请求的**端口号**。HTTP 的默认端口号是端口 80，所以您通常会看到这个端口号从网址中省略了。如果服务器监听的端口不是端口 80，您只需要指定一个端口号，这通常只发生在测试、调试或开发环境中。让我们看看另一个网址。

```cs
  http://www.bing.com/search?q=broccoli

```

`?`之后的一切(问号)被称为**查询**。该查询也称为**查询字符串**，包含目标网站要使用或解释的信息。查询字符串的外观没有正式的标准，因为技术上是由应用来解释它找到的值，但是您会看到大多数用于传递名称-值对的查询字符串都是以`name1=value1&name2=value2`的形式出现的。

例如:

```cs
  http://foo.com?first=Scott&last=Allen

```

本例中有两个名称-值对。第一对名为“first”，值为“Scott”。第二对名为“last”，值为“Allen”。在我们之前的 URL ( `http://www.bing.com/search?q=broccoli`)中，Bing 搜索引擎会看到与值“花椰菜”相关联的名称“q”。事实证明，Bing 引擎寻找一个“q”值作为搜索词。我们可以将该网址视为代表西兰花必应搜索结果的资源的网址。

最后，还有一个网址:

```cs
  http://server.com?recipe=broccoli#ingredients

```

#符号后的部分称为**片段**。该片段不同于我们到目前为止看到的其他片段，因为与 URL 路径和查询字符串不同，该片段不由服务器处理。该片段仅在客户端使用，它标识资源的特定部分。具体来说，片段通常用于通过元素的标识来标识页面中的特定 HTML 元素。

网络浏览器通常会对齐网页的初始显示，使得片段所标识的元素的顶部位于屏幕的顶部。举个例子，URL `http://odetocode.com/Blogs/scott/archive/2011/11/29/programming-windows-8-the-sublime-to-the-strange.aspx#feedback`有片段值“反馈”。如果你按照网址，你的网络浏览器应该向下滚动页面，显示我博客上某个特定博文的反馈部分。您的浏览器检索了整个资源(博客文章)，但是将您的注意力集中在一个特定的区域——反馈部分。您可以想象博客文章的 HTML 如下所示(省略了所有文本内容):

```cs
  <div id="post">
      ...
  </div>
  <div id="feedback">
      ...
  </div>

```

客户端确保带有“反馈”标识的元素位于顶部。

如果我们把到目前为止学到的所有东西放在一起，我们知道一个网址被分成以下几部分:

```cs
  <scheme>://<host>:<port>/<path>?<query>#<fragment>

```

## 网址编码

所有从事网络工作的软件开发人员都应该知道网址的字符编码问题。描述网址的官方文件竭尽全力使网址尽可能有用和互操作。网址应该像打印在保险杠贴纸上并粘贴在 2001 年福特 Windstar 上一样容易通过电子邮件进行交流。为此，互联网标准为 URL 定义了**不安全字符**。例如，空格字符被认为是不安全的，因为当网址以打印形式出现时，空格字符可能会错误地出现或消失(你的名片上是一个空格还是两个空格？).

其他不安全的字符包括数字符号(#)，因为它用于分隔片段，以及插入符号(^)，因为它并不总是通过所有网络设备正确传输。事实上，RFC 3986(网址的“定律”)将网址的安全字符定义为美国 ASCII 中的字母数字字符，加上一些特殊字符，如冒号(:)和斜杠(/)。

幸运的是，您仍然可以在一个 URL 中传输不安全的字符，但是所有不安全的字符都必须是百分比编码的(也就是 URL 编码的)。`%20`是空格字符的编码(其中 20 是 US-ASCII 空格字符的十六进制值)。

例如，假设您想为 someserver.com 的一个名为“^my 简历. txt”的文件创建 URL。合法的编码网址如下所示:

```cs
  http://someserver.com/%5Emy%20resume.txt

```

^字符和空格字符都经过了百分比编码。大多数网络应用框架将提供一个简单的网址编码应用编程接口。在服务器端，您应该通过编码应用编程接口运行动态创建的网址，以防网址中出现不安全的字符。

## 资源和媒体类型

到目前为止，我们专注于网址，并简化了其他一切。但是，当我们在浏览器中输入一个网址时，这意味着什么？通常这意味着我们想要检索或查看一些资源。网络上有大量的资料可以查看，稍后我们还将看到 HTTP 如何使我们能够创建、删除和更新资源。目前，我们将继续关注检索。

对于我们想要检索的资源类型，我们还不是很明确。网络上有成千上万种不同的资源类型——图像、超文本文档、XML 文档、视频、音频、可执行应用、微软 Word 文档等等。

为了让主机正确地为资源服务，为了让客户端正确地显示资源，所涉及的各方必须对资源的类型具体而精确。资源是图像吗？资源是电影吗？我们不希望我们的网络浏览器尝试将 PNG 图像渲染为文本，也不希望他们尝试将超文本解释为图像。

当主机响应 HTTP 请求时，它会返回一个资源，并指定该资源的**内容类型**(也称为媒体类型)。我们将在下一章看到内容类型如何出现在 HTTP 消息中的细节。

为了指定内容类型，HTTP 依赖于多用途互联网邮件扩展(MIME)标准。尽管 MIME 最初是为电子邮件通信而设计的，但 HTTP 出于同样的目的使用 MIME 标准，即以客户端知道内容包含什么的方式来标记内容。

例如，当客户端请求一个 HTML 网页时，主机可以用它标记为“`text/html`”的一些 HTML 来响应 HTTP 请求。“`text`”部分是主要媒体类型，“`html`”是媒体子类型。当响应对图像的请求时，主机将使用内容类型为“`image/jpeg`”的 JPG 文件、“`image/gif`”的 GIF 文件或“`image/png`”的 PNG 文件来标记资源。这些内容类型是标准的 MIME 类型，实际上就是将出现在 HTTP 响应中的内容。

## 关于文件扩展名的快速说明

您可能认为浏览器会依赖文件扩展名来确定传入资源的内容类型。例如，如果我的浏览器请求“frog.jpg”，它应该将资源视为 jpg 文件，但将“frog.gif”视为 gif 文件。然而，对于大多数浏览器来说，文件扩展名是决定实际内容类型的最后一个地方。

文件扩展名可能会引起误解，仅仅因为我们请求了 JPG 文件，并不意味着服务器必须以 JPG 格式编码的数据进行响应。Microsoft 将 Internet Explorer (IE)记录为首先查看主机指定的内容类型标记。如果主机没有提供内容类型，IE 将扫描响应的前 200 个字节，试图猜测内容类型。最后，如果 IE 找不到内容类型并且无法猜测内容类型，它将依赖于资源请求中使用的文件扩展名。这是内容类型标签重要的一个原因，但远不是唯一的原因。

## 内容类型协商

虽然我们倾向于把 HTTP 看作是用来服务网页的东西，但事实证明，HTTP 规范描述了一种灵活的、通用的协议，用于移动高保真的信息。移动信息的部分工作是确保所有相关方知道如何解释信息，这就是为什么媒体类型设置很重要。

然而，媒体类型并不仅仅针对主机。通过参与内容类型协商，客户端可以在主机返回的媒体类型中发挥作用。

由单个网址标识的资源可以有多个表示。以我们之前提到的西兰花食谱为例。单个配方可能有不同语言(英语、法语和德语)的表示。食谱甚至可以有不同格式的表示(HTML、PDF 和纯文本)。都是同样的资源，同样的配方，但是表现方式不同。

显而易见的问题是:服务器应该选择哪种表示？答案在 HTTP 规范描述的内容协商机制中。当客户端向 URL 发出 HTTP 请求时，客户端可以指定它将接受的媒体类型。媒体类型不仅供主机标记传出资源，还供客户端指定它们要使用的媒体类型。

客户端指定它将在传出请求消息中接受什么。同样，我们将在[第 2 章](2.html#heading_id_20)中看到该消息的详细信息，但是想象一下请求`http://food.com/`说它将接受德语表示。由服务器来尝试满足请求。主持人可能会发送仍然是英语的文本资源，这可能会让说德语的用户失望，但这就是为什么我们称之为内容协商，而不是内容最后通牒。

网络浏览器是复杂的软件，可以处理许多不同类型的资源表示。内容协商是用户可能永远不会关心的事情，但是对于软件开发人员(尤其是 web 服务开发人员)来说，内容协商是 HTTP 伟大之处的一部分。用 JavaScript 编写的一段代码可以向服务器发出请求，并请求 JSON 表示。用 C++编写的一段代码可以向服务器发出请求，并请求一个 XML 表示。在这两种情况下，如果主机能够满足请求，信息将以解析和消费的理想格式到达客户端。

## 我们在哪里？

在这一点上，我们已经尽我们所能，没有深入到 HTTP 消息看起来像什么的细节。我们已经了解了网址、网址编码和内容类型。是时候看看这些内容类型规范是什么样子了。