Python 的 httpx 包是一个用于 HTTP 交互的一个优秀且灵活的模块。Python 的 httpx 包是一个复杂的 Web 客户端。当你安装它后，你就可以用它来从网站上获取数据。像往常一样，安装它的最简单方法是使用 pip 工具：$ python -m pip install httpx --user 要使用它，把它导入到 Python 脚本中，然后使用 .get 函数从一个 web 地址获取数据：import httpx result = httpx.get("https://httpbin.org/get?hello=world") result.json()["args"] 下面是这个简单脚本的输出： {'hello': 'world'} HTTP 响应默认情况下，httpx 不会在非 200 状态下引发错误。试试这个代码：result = httpx.get("https://httpbin.org/status/404") result 结果是： <Response [404 NOT FOUND]> 可以明确地返回一个响应。添加这个异常处理：try: result.raise_for_status() except Exception as exc: print("woops", exc) 下面是结果： woops Client error '404 NOT FOUND' for url 'https://httpbin.org/status/404' For more information check: https://httpstatuses.com/404 自定义客户端除了最简单的脚本之外，使用一个自定义的客户端是有意义的。除了不错的性能改进，比如连接池，这也是一个配置客户端的好地方。例如, 你可以设置一个自定义的基本 URL：client = httpx.Client(base_url="https://httpbin.org") result = client.get("/get?source=custom-client") result.json()["args"] 输出示例： {'source': 'custom-client'} 这对用客户端与一个特定的服务器对话的典型场景很有用。例如，使用 base_url 和 auth，你可以为认证的客户端建立一个漂亮的抽象：client = httpx.Client( base_url="https://httpbin.org", auth=("good_person", "secret_password"), ) result = client.get("/basic-auth/good_person/secret_password") result.json() 输出： {'authenticated': True, 'user': 'good_person'} 你可以用它来做一件更棒的事情，就是在顶层的 “主” 函数中构建客户端，然后把它传递给其他函数。这可以让其他函数使用客户端，并让它们与连接到本地 WSGI 应用的客户端进行单元测试。def get_user_name(client): result = client.get("/basic-auth/good_person/secret_password") return result.json()["user"] get_user_name(client) 'good_person' def application(environ, start_response): start_response('200 OK', [('Content-Type', 'application/json')]) return [b'{"user": "pretty_good_person"}'] fake_client = httpx.Client(app=application, base_url="https://fake-server") get_user_name(fake_client) 输出： 'pretty_good_person' 尝试 httpx请访问 python-httpx.org 了解更多信息、文档和教程。我发现它是一个与 HTTP 交互的优秀而灵活的模块。试一试，看看它能为你做什么。via: https://opensource.com/article/22/3/python-httpx作者：Moshe Zadka 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
需要远程工作的看一下。使用这个有用的开源解决方案，从任何地方快速地连接和访问你的所有设备。随着对连接和远程工作的需求的增长，访问远程计算资源变得越来越重要。但是，提供对设备和硬件的外部访问的要求使此任务变得复杂而有风险。旨在减少此类麻烦的 ShellHub 是一个云服务器，允许从任何外部网络常规访问这些设备。ShellHub 是一个 Apache 2.0 许可的开源解决方案，它可满足所有这些需求，并允许用户通过一个帐户连接和管理多个设备。它的开发是为了方便开发者和程序员的工作，使得任何硬件架构的 Linux 设备的远程访问成为可能。仔细观察，ShellHub 方案使用 HTTP 传输层来封装 SSH 协议。这种传输层的选择可以在大多数网络上无缝使用，因为大多数公司的防火墙规则和策略通常都可以使用并接受它。下面这些示例使用 2020 年 6 月 10 日发布的 ShellHub 版本 0.3.2。使用 ShellHub要访问该平台，只需进入 shellhub.io 并注册一个自己的帐户。你的注册数据将帮助开发团队了解用户资料并提供有关如何改进平台的更多信息。图 1：shellhub.io 中的注册表格ShellHub 有直观、简洁的界面，这让所有的信息和功能都能以最快的方式呈现。注册后，你会看到一块仪表板，这时可以注册你的第一台设备。添加设备要启用通过 ShellHub 连接设备，你需要生成一个标识符，它用于在设备连接到服务器时对你的设备进行身份验证。此标识必须配置在代理（ShellHub 客户端）内部，该代理必须与镜像一起保存在设备中，或者必须添加为 Docker 容器。ShellHub 默认使用 Docker 运行代理，这非常方便，因为它在现有系统上提供了一种无痛的添加方式，支持 Docker 是唯一的要求。要添加设备，你需要粘贴命令行，它显示在 ShellHub Cloud 的对话框中（请参见图 2）。图 2：将设备添加到 ShellHub Cloud设备默认使用它的 MAC 地址作为其主机名。在内部，该设备由其密钥标识，这是在设备注册期间生成的，用于与服务器进行身份验证。访问设备要访问你的设备，只需进入仪表板中的“查看所有设备”，或单击左侧菜单上的“设备”。这将列出你所有已注册的设备。设备状态可在页面上轻松看到。在线设备旁边会显示一个绿色图标，可以单击终端图标进行连接。你接着输入密码，最后单击”连接“按钮，请参见（图 3）。图 3：使用网络上的终端访问设备另一种访问设备的方法是从类似 PuTTY、Termius 之类的 SSH 客户端，甚至 Linux 终端访问。我们可以使用称为 SSHID 的 ShellHub 标识作为连接的目的地址（例如 ssh username@SSHID）。图 4 说明了如何使用在终端中使用 Linux SSH 客户端连接到我们的计算机。图 4：使用 Linux 终端连接到设备无论你何时登录 ShellHub Cloud 平台，你都可以访问仪表板上的所有已注册设备，这样你可以随时随地访问它们。ShellHub 通过一个开源平台，以透明的方式为您与远程机器保持通信安全的过程增加了简单性。在 GitHub 上加入 ShellHub 社区，或随时通过 Gitter 或通过电子邮件 contato@ossystems.com.br 向开发团队发送你的建议或反馈。我们很乐意收到社区成员的贡献！via: https://opensource.com/article/20/7/linux-shellhub作者：Domarys 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
译注：本文来源于 2013 年出版的《High Performance Browser Networking》的第九章，因此有些信息略有过时。事实上，现在 HTTP/2 已经有相当的不是，而新的 HTTP/3 也在设计和标准制定当中。介绍 超文本传输协议 Hypertext Transfer Protocol （HTTP）是互联网上最普遍和广泛采用的应用程序协议之一。它是客户端和服务器之间的通用语言，支持现代 Web。从最初作为单个的关键字和文档路径开始，它已成为不仅仅是浏览器的首选协议，而且几乎是所有连接互联网硬件和软件应用程序的首选协议。在本文中，我们将简要回顾 HTTP 协议的发展历史。对 HTTP 不同语义的完整讨论超出了本文的范围，但理解 HTTP 的关键设计变更以及每个变更背后的动机将为我们讨论 HTTP 性能提供必要的背景，特别是在 HTTP/2 中即将进行的许多改进。HTTP 0.9: 单行协议 蒂姆·伯纳斯·李 Tim Berners-Lee 最初的 HTTP 提案在设计时考虑到了简单性，以帮助他采用他的另一个新想法： 万维网 World Wide Web 。这个策略看起来奏效了：注意，他是一个有抱负的协议设计者。1991 年，伯纳斯·李概述了这个新协议的动机，并列出了几个高级设计目标：文件传输功能、请求超文档存档索引搜索的能力，格式协商以及将客户端引用到另一个服务器的能力。为了证明该理论的实际应用，构建了一个简单原型，它实现了所提议功能的一小部分。客户端请求是一个 ASCII 字符串。客户端请求以回车符（CRLF）终止。服务器响应是 ASCII 字符流。服务器响应是一种超文本标记语言（HTML）。文档传输完成后连接终止。然而，即使这听起来也比实际复杂得多。这些规则支持的是一种非常简单的，对 Telnet 友好的协议，一些 Web 服务器至今仍然支持这种协议：$> telnet google.com 80 Connected to 74.125.xxx.xxx GET /about/ (hypertext response) (connection closed)请求包含这样一行：GET 方法和请求文档的路径。响应是一个超文本文档，没有标题或任何其他元数据，只有 HTML。真的是再简单不过了。此外，由于之前的交互是预期协议的子集，因此它获得了一个非官方的 HTTP 0.9 标签。其余的，就像他们所说的，都是历史。从 1991 年这些不起眼的开始，HTTP 就有了自己的生命，并在接下来几年里迅速发展。让我们快速回顾一下 HTTP 0.9 的特性：采用客户端-服务器架构，是一种请求-响应协议。采用 ASCII 协议，运行在 TCP/IP 链路上。旨在传输超文本文档（HTML）。每次请求后，服务器和客户端之间的连接都将关闭。流行的 Web 服务器，如 Apache 和 Nginx，仍然支持 HTTP 0.9 协议，部分原因是因为它没有太多功能！如果你感兴趣，打开 Telnet 会话并尝试通过 HTTP 0.9 访问 google.com 或你最喜欢的网站，并检查早期协议的行为和限制。HTTP/1.0: 快速增长和 Informational RFC1991 年至 1995 年期间，HTML 规范和一种称为 “web 浏览器”的新型软件快速发展，面向消费者的公共互联网基础设施也开始出现并快速增长。完美风暴：1990 年代初的互联网热潮基于蒂姆·伯纳斯·李最初的浏览器原型，美国国家超级计算机应用中心（NCSA）的一个团队决定实现他们自己的版本。就这样，第一个流行的浏览器诞生了：NCSA Mosaic。1994 年 10 月，NCSA 团队的一名程序员 Marc Andreessen 与 Jim Clark 合作创建了 Mosaic Communications，该公司后来改名为 Netscape（网景），并于 1994 年 12 月发布了 Netscape Navigator 1.0。从这一点来说，已经很清楚了，万维网已经不仅仅是学术上的好奇心了。实际上，同年在瑞士日内瓦组织了第一次万维网会议，这导致 万维网联盟 World Wide Web Consortium （W3C）的成立，以帮助指导 HTML 的发展。同样，在 IETF 内部建立了一个并行的 HTTP 工作组 HTTP Working Group （HTTP-WG），专注于改进 HTTP 协议。后来这两个团体一直对 Web 的发展起着重要作用。最后，完美风暴来临，CompuServe，AOL 和 Prodigy 在 1994-1995 年的同一时间开始向公众提供拨号上网服务。凭借这股迅速的浪潮，Netscape 在 1995 年 8 月 9 日凭借其成功的 IPO 创造了历史。这预示着互联网热潮已经到来，人人都想分一杯羹！不断增长的新 Web 所需功能及其在公共网站上的应用场景很快暴露了 HTTP 0.9 的许多基础限制：我们需要一种能够提供超文本文档、提供关于请求和响应的更丰富的元数据，支持内容协商等等的协议。相应地，新兴的 Web 开发人员社区通过一个特殊的过程生成了大量实验性的 HTTP 服务器和客户端实现来回应：实现，部署，并查看其他人是否采用它。从这些急速增长的实验开始，一系列最佳实践和常见模式开始出现。1996 年 5 月， HTTP 工作组 HTTP Working Group （HTTP-WG）发布了 RFC 1945，它记录了许多被广泛使用的 HTTP/1.0 实现的“常见用法”。请注意，这只是一个信息性 RFC：HTTP/1.0，如你所知的，它不是一个正式规范或 Internet 标准！话虽如此，HTTP/1.0 请求看起来应该是：$> telnet website.org 80 Connected to xxx.xxx.xxx.xxx GET /rfc/rfc1945.txt HTTP/1.0 ❶ User-Agent: CERN-LineMode/2.15 libwww/2.17b3 Accept: */* HTTP/1.0 200 OK ❷ Content-Type: text/plain Content-Length: 137582 Expires: Thu, 01 Dec 1997 16:00:00 GMT Last-Modified: Wed, 1 May 1996 12:45:26 GMT Server: Apache 0.84 (plain-text response) (connection closed)❶ 请求行有 HTTP 版本号，后面跟请求头❷ 响应状态，后跟响应头前面的交互并不是 HTTP/1.0 功能的详尽列表，但它确实说明了一些关键的协议更改：请求可能多个由换行符分隔的请求头字段组成。响应对象的前缀是响应状态行。响应对象有自己的一组由换行符分隔的响应头字段。响应对象不限于超文本。每次请求后，服务器和客户端之间的连接都将关闭。请求头和响应头都保留为 ASCII 编码，但响应对象本身可以是任何类型：HTML 文件、纯文本文件、图像或任何其他内容类型。因此，HTTP 的“超文本传输”部分在引入后不久就变成了用词不当。实际上，HTTP 已经迅速发展成为一种超媒体传输，但最初的名称没有改变。除了媒体类型协商之外，RFC 还记录了许多其他常用功能：内容编码、字符集支持、多部分类型、授权、缓存、代理行为、日期格式等。今天，几乎所有 Web 上的服务器都可以并且仍将使用 HTTP/1.0。不过，现在你应该更加清楚了！每个请求都需要一个新的 TCP 连接，这会对 HTTP/1.0 造成严重的性能损失。参见三次握手，接着会慢启动。HTTP/1.1: Internet 标准将 HTTP 转变为官方 IETF 互联网标准的工作与围绕 HTTP/1.0 的文档工作并行进行，并计划从 1995 年至 1999 年完成。事实上，第一个正式的 HTTP/1.1 标准定义于 RFC 2068，它在 HTTP/1.0 发布大约六个月后，即 1997 年 1 月正式发布。两年半后，即 1999 年 6 月，一些新的改进和更新被纳入标准，并作为 RFC 2616 发布。HTTP/1.1 标准解决了早期版本中发现的许多协议歧义，并引入了一些关键的性能优化：保持连接，分块编码传输，字节范围请求，附加缓存机制，传输编码和请求管道。有了这些功能，我们现在可以审视一下由任何现代 HTTP 浏览器和客户端执行的典型 HTTP/1.1 会话：$> telnet website.org 80 Connected to xxx.xxx.xxx.xxx GET /index.html HTTP/1.1 ❶ Host: website.org User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4)... (snip) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 Cookie: __qca=P0-800083390... (snip) HTTP/1.1 200 OK ❷ Server: nginx/1.0.11 Connection: keep-alive Content-Type: text/html; charset=utf-8 Via: HTTP/1.1 GWA Date: Wed, 25 Jul 2012 20:23:35 GMT Expires: Wed, 25 Jul 2012 20:23:35 GMT Cache-Control: max-age=0, no-cache Transfer-Encoding: chunked 100 ❸ (snip) 100 (snip) 0 ❹ GET /favicon.ico HTTP/1.1 ❺ Host: www.website.org User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4)... (snip) Accept: */* Referer: http://website.org/ Connection: close ❻ Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 Cookie: __qca=P0-800083390... (snip) HTTP/1.1 200 OK ❼ Server: nginx/1.0.11 Content-Type: image/x-icon Content-Length: 3638 Connection: close Last-Modified: Thu, 19 Jul 2012 17:51:44 GMT Cache-Control: max-age=315360000 Accept-Ranges: bytes Via: HTTP/1.1 GWA Date: Sat, 21 Jul 2012 21:35:22 GMT Expires: Thu, 31 Dec 2037 23:55:55 GMT Etag: W/PSA-GAu26oXbDi (icon data) (connection closed)❶ 请求的 HTML 文件，包括编、字符集和 cookie 元数据❷ 原始 HTML 请求的分块响应❸ 以 ASCII 十六进制数字（256 字节）表示块中的八位元的数量❹ 分块流响应结束❺ 在相同的 TCP 连接上请求一个图标文件❻ 通知服务器不再重用连接❼ 图标响应后，然后关闭连接哇，这里发生了很多事情！第一个也是最明显的区别是我们有两个对象请求，一个用于 HTML 页面，另一个用于图像，它们都通过一个连接完成。这就是保持连接的实际应用，它允许我们重用现有的 TCP 连接到同一个主机的多个请求，提供一个更快的最终用户体验。参见TCP 优化。要终止持久连接，注意第二个客户端请求通过 Connection 请求头向服务器发送显示的 close。类似地，一旦传输响应，服务器就可以通知客户端关闭当前 TCP 连接。从技术上讲，任何一方都可以在没有此类信号的情况下终止 TCP 连接，但客户端和服务器应尽可能提供此类信号，以便双方都启用更好的连接重用策略。HTTP/1.1 改变了 HTTP 协议的语义，默认情况下使用保持连接。这意味着，除非另有说明（通过 Connection:close 头），否则服务器应默认保持连接打开。但是，同样的功能也被反向移植到 HTTP/1.0 上，通过 Connection:keep-Alive 头启用。因此，如果你使用 HTTP/1.1，从技术上讲，你不需要 Connection:keep-Alive 头，但许多客户端仍然选择提供它。此外，HTTP/1.1 协议还添加了内容、编码、字符集，甚至语言协商、传输编码、缓存指令、客户端 cookie，以及可以针对每个请求协商的十几个其他功能。我们不打算详细讨论每个 HTTP/1.1 特性的语义。这个主题可以写一本专门的书了，已经有了很多很棒的书。相反，前面的示例很好地说明了 HTTP 的快速进展和演变，以及每个客户端-服务器交换的错综复杂的过程，里面发生了很多事情！要了解 HTTP 协议所有内部工作原理，参考 David Gourley 和 Brian Totty 共同撰写的权威指南： The Definitive Guide。HTTP/2: 提高传输性能RFC 2616 自发布以来，已经成为互联网空前增长的基础：数十亿各种形状和大小的设备，从台式电脑到我们口袋里的小型网络设备，每天都在使用 HTTP 来传送新闻，视频，在我们生活中的数百万的其他网络应用程序都在依靠它。一开始是一个简单的，用于检索超文本的简单协议，很快演变成了一种通用的超媒体传输，现在十年过去了，它几乎可以为你所能想象到的任何用例提供支持。可以使用协议的服务器无处不在，客户端也可以使用协议，这意味着现在许多应用程序都是专门在 HTTP 之上设计和部署的。需要一个协议来控制你的咖啡壶？RFC 2324 已经涵盖了超文本咖啡壶控制协议（HTCPCP/1.0）- 它原本是 IETF 在愚人节开的一个玩笑，但在我们这个超链接的新世界中，它不仅仅意味着一个玩笑。超文本传输协议（HTTP）是一个应用程序级的协议，用于分布式、协作、超媒体信息系统。它是一种通用的、无状态的协议，可以通过扩展请求方法、错误码和头，用于超出超文本之外的许多任务，比如名称服务器和分布式对象管理系统。HTTP 的一个特性是数据表示的类型和协商，允许独立于传输的数据构建系统。RFC 2616: HTTP/1.1, June 1999HTTP 协议的简单性是它最初被采用和快速增长的原因。事实上，现在使用 HTTP 作为主要控制和数据协议的嵌入式设备（传感器，执行器和咖啡壶）并不罕见。但在其自身成功的重压下，随着我们越来越多地继续将日常互动转移到网络 —— 社交、电子邮件、新闻和视频，以及越来越多的个人和工作空间，它也开始显示出压力的迹象。用户和 Web 开发人员现在都要求 HTTP/1.1 提供近乎实时的响应能力和协议 性能，如果不进行一些修改，就无法满足这些要求。为了应对这些新挑战，HTTP 必须继续发展，因此 HTTPbis 工作组在 2012 年初宣布了一项针对 HTTP/2 的新计划：已经有一个协议中出现了新的实现经验和兴趣，该协议保留了 HTTP 的语义，但是没有保留 HTTP/1.x 的消息框架和语法，这些问题已经被确定为妨碍性能和鼓励滥用底层传输。工作组将使用有序的双向流中生成 HTTP 当前语义的新表达式的规范。与 HTTP/1.x 一样，主要传输目标是 TCP，但是应该可以使用其他方式传输。HTTP/2 charter, January 2012HTTP/2 的主要重点是提高传输性能并支持更低的延迟和更高的吞吐量。主要的版本增量听起来像是一个很大的步骤，但就性能而言，它将是一个重大的步骤，但重要的是要注意，没有任何高级协议语义收到影响：所有的 HTTP 头，值和用例是相同的。任何现有的网站或应用程序都可以并且将通过 HTTP/2 传送而无需修改。你无需修改应用程序标记来利用 HTTP/2。HTTP 服务器将来一定会使用 HTTP/2，但这对大多数用户来说应该是透明的升级。如果工作组实现目标，唯一的区别应该是我们的应用程序以更低的延迟和更好的网络连接利用率来传送数据。话虽如此，但我们不要走的太远了。在讨论新的 HTTP/2 协议功能之前，有必要回顾一下我们现有的 HTTP/1.1 部署和性能最佳实践。HTTP/2 工作组正在新规范上取得快速的进展，但即使最终标准已经完成并准备就绪，在可预见的未来，我们仍然必须支持旧的 HTTP/1.1 客户端，实际上，这得十年或更长时间。via: https://hpbn.co/brief-history-of-http/#http-09-the-one-line-protocol作者：Ilya Grigorik 选题：lujun9972 译者：MjSeven 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
gorilla/mux 包以直观的 API 提供了 HTTP 请求路由、验证和其它服务。Go 网络库包括 http.ServeMux 结构类型，它支持 HTTP 请求多路复用（路由）：Web 服务器将托管资源的 HTTP 请求与诸如 /sales4today 之类的 URI 路由到代码处理程序；处理程序在发送 HTTP 响应（通常是 HTML 页面）之前执行适当的逻辑。 这是该体系的草图： +-----------+ +--------+ +---------+ HTTP 请求---->| web 服务器 |---->| 路由 |---->| 处理程序 | +-----------+ +--------+ +---------+调用 ListenAndServe 方法后启动 HTTP 服务器：http.ListenAndServe(":8888", nil) // args: port & router第二个参数 nil 意味着 DefaultServeMux 用于请求路由。gorilla/mux 库包含 mux.Router 类型，可替代 DefaultServeMux 或自定义请求多路复用器。 在 ListenAndServe 调用中，mux.Router 实例将代替 nil 作为第二个参数。 下面的示例代码很好的说明了为什么 mux.Router如此吸引人：1、一个简单的 CRUD web 应用程序crud web 应用程序（见下文）支持四种 CRUD（创建/读取/更新/删除）操作，它们分别对应四种 HTTP 请求方法：POST、GET、PUT 和 DELETE。 在这个 CRUD 应用程序中，所管理的资源是套话与反套话的列表，每个都是套话及其反面的的套话，例如这对：Out of sight, out of mind. Absence makes the heart grow fonder.可以添加新的套话对，可以编辑或删除现有的套话对。CRUD web 应用程序：package main import ( "gorilla/mux" "net/http" "fmt" "strconv" ) const GETALL string = "GETALL" const GETONE string = "GETONE" const POST string = "POST" const PUT string = "PUT" const DELETE string = "DELETE" type clichePair struct { Id int Cliche string Counter string } // Message sent to goroutine that accesses the requested resource. type crudRequest struct { verb string cp *clichePair id int cliche string counter string confirm chan string } var clichesList = []*clichePair{} var masterId = 1 var crudRequests chan *crudRequest // GET / // GET /cliches func ClichesAll(res http.ResponseWriter, req *http.Request) { cr := &crudRequest{verb: GETALL, confirm: make(chan string)} completeRequest(cr, res, "read all") } // GET /cliches/id func ClichesOne(res http.ResponseWriter, req *http.Request) { id := getIdFromRequest(req) cr := &crudRequest{verb: GETONE, id: id, confirm: make(chan string)} completeRequest(cr, res, "read one") } // POST /cliches func ClichesCreate(res http.ResponseWriter, req *http.Request) { cliche, counter := getDataFromRequest(req) cp := new(clichePair) cp.Cliche = cliche cp.Counter = counter cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)} completeRequest(cr, res, "create") } // PUT /cliches/id func ClichesEdit(res http.ResponseWriter, req *http.Request) { id := getIdFromRequest(req) cliche, counter := getDataFromRequest(req) cr := &crudRequest{verb: PUT, id: id, cliche: cliche, counter: counter, confirm: make(chan string)} completeRequest(cr, res, "edit") } // DELETE /cliches/id func ClichesDelete(res http.ResponseWriter, req *http.Request) { id := getIdFromRequest(req) cr := &crudRequest{verb: DELETE, id: id, confirm: make(chan string)} completeRequest(cr, res, "delete") } func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) { crudRequests<-cr msg := <-cr.confirm res.Write([]byte(msg)) logIt(logMsg) } func main() { populateClichesList() // From now on, this gorountine alone accesses the clichesList. crudRequests = make(chan *crudRequest, 8) go func() { // resource manager for { select { case req := <-crudRequests: if req.verb == GETALL { req.confirm<-readAll() } else if req.verb == GETONE { req.confirm<-readOne(req.id) } else if req.verb == POST { req.confirm<-addPair(req.cp) } else if req.verb == PUT { req.confirm<-editPair(req.id, req.cliche, req.counter) } else if req.verb == DELETE { req.confirm<-deletePair(req.id) } } }() startServer() } func startServer() { router := mux.NewRouter() // Dispatch map for CRUD operations. router.HandleFunc("/", ClichesAll).Methods("GET") router.HandleFunc("/cliches", ClichesAll).Methods("GET") router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET") router.HandleFunc("/cliches", ClichesCreate).Methods("POST") router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT") router.HandleFunc("/cliches/{id:[0-9]+}", ClichesDelete).Methods("DELETE") http.Handle("/", router) // enable the router // Start the server. port := ":8888" fmt.Println("\nListening on port " + port) http.ListenAndServe(port, router); // mux.Router now in play } // Return entire list to requester. func readAll() string { msg := "\n" for _, cliche := range clichesList { next := strconv.Itoa(cliche.Id) + ": " + cliche.Cliche + " " + cliche.Counter + "\n" msg += next } return msg } // Return specified clichePair to requester. func readOne(id int) string { msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n" index := findCliche(id) if index >= 0 { cliche := clichesList[index] msg = "\n" + strconv.Itoa(id) + ": " + cliche.Cliche + " " + cliche.Counter + "\n" } return msg } // Create a new clichePair and add to list func addPair(cp *clichePair) string { cp.Id = masterId masterId++ clichesList = append(clichesList, cp) return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n" } // Edit an existing clichePair func editPair(id int, cliche string, counter string) string { msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n" index := findCliche(id) if index >= 0 { clichesList[index].Cliche = cliche clichesList[index].Counter = counter msg = "\nCliche edited: " + cliche + " " + counter + "\n" } return msg } // Delete a clichePair func deletePair(id int) string { idStr := strconv.Itoa(id) msg := "\n" + "Bad Id: " + idStr + "\n" index := findCliche(id) if index >= 0 { clichesList = append(clichesList[:index], clichesList[index + 1:]...) msg = "\nCliche " + idStr + " deleted\n" } return msg } //*** utility functions func findCliche(id int) int { for i := 0; i < len(clichesList); i++ { if id == clichesList[i].Id { return i; } } return -1 // not found } func getIdFromRequest(req *http.Request) int { vars := mux.Vars(req) id, _ := strconv.Atoi(vars["id"]) return id } func getDataFromRequest(req *http.Request) (string, string) { // Extract the user-provided data for the new clichePair req.ParseForm() form := req.Form cliche := form["cliche"][0] // 1st and only member of a list counter := form["counter"][0] // ditto return cliche, counter } func logIt(msg string) { fmt.Println(msg) } func populateClichesList() { var cliches = []string { "Out of sight, out of mind.", "A penny saved is a penny earned.", "He who hesitates is lost.", } var counterCliches = []string { "Absence makes the heart grow fonder.", "Penny-wise and dollar-foolish.", "Look before you leap.", } for i := 0; i < len(cliches); i++ { cp := new(clichePair) cp.Id = masterId masterId++ cp.Cliche = cliches[i] cp.Counter = counterCliches[i] clichesList = append(clichesList, cp) } }为了专注于请求路由和验证，CRUD 应用程序不使用 HTML 页面作为请求响应。 相反，请求会产生明文响应消息：套话对的列表是对 GET 请求的响应，确认新的套话对已添加到列表中是对 POST 请求的响应，依此类推。 这种简化使得使用命令行实用程序（如 curl）可以轻松地测试应用程序，尤其是 gorilla/mux 组件。gorilla/mux 包可以从 GitHub 安装。 CRUD app 无限期运行；因此，应使用 Control-C 或同等命令终止。 CRUD 应用程序的代码，以及自述文件和简单的 curl 测试，可以在我的网站上找到。2、请求路由mux.Router 扩展了 REST 风格的路由，它赋给 HTTP 方法（例如，GET）和 URL 末尾的 URI 或路径（例如 /cliches）相同的权重。 URI 用作 HTTP 动词（方法）的名词。 例如，在HTTP请求中有一个起始行，例如：GET /cliches意味着得到所有的套话对，而一个起始线，如：POST /cliches意味着从 HTTP 正文中的数据创建一个套话对。在 CRUD web 应用程序中，有五个函数充当 HTTP 请求的五种变体的请求处理程序：ClichesAll(...) # GET: 获取所有的套话对 ClichesOne(...) # GET: 获取指定的套话对 ClichesCreate(...) # POST: 创建新的套话对 ClichesEdit(...) # PUT: 编辑现有的套话对 ClichesDelete(...) # DELETE: 删除指定的套话对每个函数都有两个参数：一个 http.ResponseWriter 用于向请求者发送一个响应，一个指向 http.Request 的指针，该指针封装了底层 HTTP 请求的信息。 使用 gorilla/mux 包可以轻松地将这些请求处理程序注册到Web服务器，并执行基于正则表达式的验证。CRUD 应用程序中的 startServer 函数注册请求处理程序。 考虑这对注册，router 作为 mux.Router 实例：router.HandleFunc("/", ClichesAll).Methods("GET") router.HandleFunc("/cliches", ClichesAll).Methods("GET")这些语句意味着对单斜线 / 或 /cliches 的 GET 请求应该路由到 ClichesAll 函数，然后处理请求。 例如，curl 请求（使用 ％ 作为命令行提示符）：% curl --request GET localhost:8888/会产生如下结果：1: Out of sight, out of mind. Absence makes the heart grow fonder. 2: A penny saved is a penny earned. Penny-wise and dollar-foolish. 3: He who hesitates is lost. Look before you leap.这三个套话对是 CRUD 应用程序中的初始数据。在这句注册语句中：router.HandleFunc("/cliches", ClichesAll).Methods("GET") router.HandleFunc("/cliches", ClichesCreate).Methods("POST")URI 是相同的（/cliches），但动词不同：第一种情况下为 GET 请求，第二种情况下为 POST 请求。 此注册举例说明了 REST 样式的路由，因为仅动词的不同就足以将请求分派给两个不同的处理程序。注册中允许多个 HTTP 方法，尽管这会影响 REST 风格路由的精髓：router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")除了动词和 URI 之外，还可以在功能上路由 HTTP 请求。 例如，注册router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")要求对 POST 请求进行 HTTPS 访问以创建新的套话对。以类似的方式，注册可能需要具有指定的 HTTP 头元素（例如，认证凭证）的请求。3、 Request validationgorilla/mux 包采用简单，直观的方法通过正则表达式进行请求验证。 考虑此请求处理程序以获取一个操作：router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")此注册排除了 HTTP 请求，例如：% curl --request GET localhost:8888/cliches/foo因为 foo 不是十进制数字。该请求导致熟悉的 404（未找到）状态码。 在此处理程序注册中包含正则表达式模式可确保仅在请求 URI 以十进制整数值结束时才调用 ClichesOne 函数来处理请求：% curl --request GET localhost:8888/cliches/3 # ok另一个例子，请求如下：% curl --request PUT --data "..." localhost:8888/cliches此请求导致状态代码为 405（错误方法），因为 /cliches URI 在 CRUD 应用程序中仅在 GET 和 POST 请求中注册。 像 GET 请求一样，PUT 请求必须在 URI 的末尾包含一个数字 id：router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")4、并发问题gorilla/mux 路由器作为单独的 Go 协程执行对已注册的请求处理程序的每次调用，这意味着并发性被内置于包中。 例如，如果有十个同时发出的请求，例如% curl --request POST --data "..." localhost:8888/cliches然后 mux.Router 启动十个 Go 协程来执行 ClichesCreate 处理程序。GET all、GET one、POST、PUT 和 DELETE 中的五个请求操作中，最后三个改变了所请求的资源，即包含套话对的共享 clichesList。 因此，CRUD app 需要通过协调对 clichesList 的访问来保证安全的并发性。 在不同但等效的术语中，CRUD app 必须防止 clichesList 上的竞争条件。 在生产环境中，可以使用数据库系统来存储诸如 clichesList 之类的资源，然后可以通过数据库事务来管理安全并发。CRUD 应用程序采用推荐的Go方法来实现安全并发：只有一个 Go 协程，资源管理器在 CRUD app startServer 函数中启动，一旦 Web 服务器开始侦听请求，就可以访问 clichesList。诸如 ClichesCreate 和 ClichesAll 之类的请求处理程序向 Go 通道发送（指向）crudRequest 实例（默认情况下是线程安全的），并且资源管理器单独从该通道读取。 然后，资源管理器对 clichesList 执行请求的操作。安全并发体系结构绘制如下： crudRequest 读/写 请求处理程序 -------------> 资源托管者 ------------> 套话列表 在这种架构中，不需要显式锁定 clichesList，因为一旦 CRUD 请求开始进入，只有一个 Go 协程（资源管理器）访问 clichesList。为了使 CRUD 应用程序尽可能保持并发，在一方请求处理程序与另一方的单一资源管理器之间进行有效的分工至关重要。 在这里，为了审查，是 ClichesCreate 请求处理程序：func ClichesCreate(res http.ResponseWriter, req *http.Request) { cliche, counter := getDataFromRequest(req) cp := new(clichePair) cp.Cliche = cliche cp.Counter = counter cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)} completeRequest(cr, res, "create") }ClichesCreate 调用实用函数 getDataFromRequest，它从 POST 请求中提取新的套话和反套话。 然后 ClichesCreate 函数创建一个新的 ClichePair，设置两个字段，并创建一个 crudRequest 发送给单个资源管理器。 此请求包括一个确认通道，资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成，因为尚未访问 clichesList。请求处理程序调用实用程序函数，该函数从 POST 请求中提取新的套话和反套话。 然后，该函数创建一个新的，设置两个字段，并创建一个 crudRequest 发送到单个资源管理器。 此请求包括一个确认通道，资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成，因为尚未访问它。completeRequest 实用程序函数在 ClichesCreate 函数和其他请求处理程序的末尾调用：completeRequest(cr, res, "create") // shown above通过将 crudRequest 放入 crudRequests 频道，使资源管理器发挥作用：func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) { crudRequests<-cr // 向资源托管者发送请求 msg := <-cr.confirm // 等待确认 res.Write([]byte(msg)) // 向请求方发送确认 logIt(logMsg) // 打印到标准输出 }对于 POST 请求，资源管理器调用实用程序函数 addPair，它会更改 clichesList 资源：func addPair(cp *clichePair) string { cp.Id = masterId // 分配一个唯一的 ID masterId++ // 更新 ID 计数器 clichesList = append(clichesList, cp) // 更新列表 return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n" }资源管理器为其他 CRUD 操作调用类似的实用程序函数。 值得重复的是，一旦 Web 服务器开始接受请求，资源管理器就是唯一可以读取或写入 clichesList 的 goroutine。对于任何类型的 Web 应用程序，gorilla/mux 包在简单直观的 API 中提供请求路由、请求验证和相关服务。 CRUD web 应用程序突出了软件包的主要功能。via: https://opensource.com/article/18/8/http-request-routing-validation-gorillamux作者：Marty Kalin 选题：lujun9972 译者：yongshouzhang 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
如何在 Linux 中快速地通过 HTTP 提供文件访问服务
如今，我有很多方法来通过 Web 浏览器为局域网中的其他系统提供单个文件或整个目录的访问。我在我的 Ubuntu 测试机上测试了这些方法，它们如下面描述的那样运行正常。如果你想知道如何在类 Unix 操作系统中通过 HTTP 轻松快速地提供文件和文件夹的访问服务，以下方法之一肯定会有所帮助。在 Linux 中通过 HTTP 访问文件和文件夹免责声明：此处给出的所有方法适用于安全的局域网。由于这些方法没有任何安全机制，因此不建议在生产环境中使用它们。你注意了！方法 1 - 使用 simpleHTTPserver（Python）我们写了一篇简要的指南来设置一个简单的 http 服务器，以便在以下链接中即时共享文件和目录。如果你有一个安装了 Python 的系统，这个方法非常方便。如何使用 simpleHTTPserver 设置一个简单的文件服务器方法 2 - 使用 Quickserve（Python）此方法针对 Arch Linux 及其衍生版。有关详细信息，请查看下面的链接。如何在 Arch Linux 中即时共享文件和文件夹方法 3 - 使用 Ruby在此方法中，我们使用 Ruby 在类 Unix 系统中通过 HTTP 提供文件和文件夹访问。按照以下链接中的说明安装 Ruby 和 Rails。在 CentOS 和 Ubuntu 中安装 Ruby on Rails安装 Ruby 后，进入要通过网络共享的目录，例如 ostechnix：$ cd ostechnix并运行以下命令：$ ruby -run -ehttpd . -p8000 [2018-08-10 16:02:55] INFO WEBrick 1.4.2 [2018-08-10 16:02:55] INFO ruby 2.5.1 (2018-03-29) [x86_64-linux] [2018-08-10 16:02:55] INFO WEBrick::HTTPServer#start: pid=5859 port=8000确保在路由器或防火墙中打开端口 8000。如果该端口已被其他一些服务使用，那么请使用不同的端口。现在你可以使用 URL 从任何远程系统访问此文件夹的内容 - http:// <ip-address>:8000。要停止共享，请按 CTRL+C。方法 4 - 使用 Http-server（NodeJS）Http-server 是一个用 NodeJS 编写的简单的可用于生产环境的命令行 http 服务器。它不需要配置，可用于通过 Web 浏览器即时共享文件和目录。按如下所述安装 NodeJS。如何在 Linux 上安装 NodeJS安装 NodeJS 后，运行以下命令安装 http-server。$ npm install -g http-server现在进入任何目录并通过 HTTP 共享其内容，如下所示。$ cd ostechnix $ http-server -p 8000 Starting up http-server, serving ./ Available on: http://127.0.0.1:8000 http://192.168.225.24:8000 http://192.168.225.20:8000 Hit CTRL-C to stop the server现在你可以使用 URL 从任何远程系统访问此文件夹的内容 - http:// <ip-address>:8000。要停止共享，请按 CTRL+C。方法 5 - 使用 Miniserve（Rust）Miniserve 是另一个命令行程序，它允许你通过 HTTP 快速访问文件。它是一个非常快速、易于使用的跨平台程序，它用 Rust 编程语言编写。与上面的程序/方法不同，它提供身份验证支持，因此你可以为共享设置用户名和密码。按下面的链接在 Linux 系统中安装 Rust。在 Linux 上安装 Rust 编程语言安装 Rust 后，运行以下命令安装 miniserve：$ cargo install miniserve或者，你可以在其发布页下载二进制文件并使其可执行。$ chmod +x miniserve-linux然后，你可以使用命令运行它（假设 miniserve 二进制文件下载到当前的工作目录中）：$ ./miniserve-linux <path-to-share>用法要提供目录访问：$ miniserve <path-to-directory>示例：$ miniserve /home/sk/ostechnix/ miniserve v0.2.0 Serving path /home/sk/ostechnix at http://[::]:8080, http://localhost:8080 Quit by pressing CTRL-C现在，你可以在本地系统使用 URL – http://localhost:8080 访问共享，或者在远程系统使用 URL – http://<ip-address>:8080 访问。要提供单个文件访问：$ miniserve <path-to-file>示例：$ miniserve ostechnix/file.txt带用户名和密码提供文件/文件夹访问：$ miniserve --auth joe:123 <path-to-share>绑定到多个接口：$ miniserve -i 192.168.225.1 -i 10.10.0.1 -i ::1 -- <path-to-share>如你所见，我只给出了 5 种方法。但是，本指南末尾附带的链接中还提供了几种方法。也去测试一下它们。此外，收藏并时不时重新访问它来检查将来是否有新的方法。今天就是这些。希望这篇文章有用。还有更多的好东西。敬请期待！干杯！资源单行静态 http 服务器大全via: https://www.ostechnix.com/how-to-quickly-serve-files-and-folders-over-http-in-linux/作者：SK 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
如何在流行而强大的 Apache Web 服务器上托管两个或多个站点。在我的上一篇文章中，我解释了如何为单个站点配置 Apache Web 服务器，事实证明这很容易。在这篇文章中，我将向你展示如何使用单个 Apache 实例来服务多个站点。注意：我写这篇文章的环境是 Fedora 27 虚拟机，配置了 Apache 2.4.29。如果你用另一个发行版或不同的 Fedora 版本，那么你使用的命令以及配置文件的位置和内容可能会有所不同。正如我之前的文章中提到的，Apache 的所有配置文件都位于 /etc/httpd/conf 和 /etc/httpd/conf.d。默认情况下，站点的数据位于 /var/www 中。对于多个站点，你需要提供多个位置，每个位置对应托管的站点。基于名称的虚拟主机使用基于名称的虚拟主机，你可以为多个站点使用一个 IP 地址。现代 Web 服务器，包括 Apache，使用指定 URL 的 hostname 部分来确定哪个虚拟 Web 主机响应页面请求。这仅仅需要比一个站点更多的配置。即使你只从单个站点开始，我也建议你将其设置为虚拟主机，这样可以在以后更轻松地添加更多站点。在本文中，我将从上一篇文章中我们停止的地方开始，因此你需要设置原来的站点，即基于名称的虚拟站点。准备原来的站点在设置第二个站点之前，你需要为现有网站提供基于名称的虚拟主机。如果你现在没有站点，请返回并立即创建一个。一旦你有了站点，将以下内容添加到 /etc/httpd/conf/httpd.conf 配置文件的底部（添加此内容是你需要对 httpd.conf 文件进行的唯一更改）：<VirtualHost 127.0.0.1:80> DocumentRoot /var/www/html ServerName www.site1.org </VirtualHost>这将是第一个虚拟主机配置节，它应该保持为第一个，以使其成为默认定义。这意味着通过 IP 地址或解析为此 IP 地址但没有特定命名主机配置节的其它名称对服务器的 HTTP 访问将定向到此虚拟主机。所有其它虚拟主机配置节都应跟在此节之后。你还需要使用 /etc/hosts 中的条目设置你的网站以提供名称解析。上次，我们只使用了 localhost 的 IP 地址。通常，这可以使用你使用的任何名称服务来完成，例如 Google 或 Godaddy。对于你的测试网站，通过在 /etc/hosts 中的 localhost 行添加一个新名称来完成此操作。添加两个网站的条目，方便你以后不需再次编辑此文件。结果如下：127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 www.site1.org www.site2.org让我们将 /var/www/html/index.html 文件改变得更加明显一点。它应该看起来像这样（带有一些额外的文本来识别这是站点 1）： Hello World Web site 1.重新启动 HTTPD 服务器，已启用对 httpd 配置的更改。然后，你可以从命令行使用 Lynx 文本模式查看网站。[root@testvm1 ~]# systemctl restart httpd [root@testvm1 ~]# lynx www.site1.org Hello World Web site 1. <snip> Commands: Use arrow keys to move, '?' for help, 'q' to quit, '<-' to go back. Arrow keys: Up and Down to move. Right to follow a link; Left to go back. H)elp O)ptions P)rint G)o M)ain screen Q)uit /=search [delete]=history list你可以看到原始网站的修改内容，没有明显的错误，先按下 Q 键，然后按 Y 退出 Lynx Web 浏览器。配置第二个站点现在你已经准备好建立第二个网站。使用以下命令创建新的网站目录结构：[root@testvm1 html]# mkdir -p /var/www/html2注意，第二个站点只是第二个 html 目录，与第一个站点位于同一 /var/www 目录下。现在创建一个新的索引文件 /var/www/html2/index.html，其中包含以下内容（此索引文件稍有不同，以区别于原来的网站）： Hello World -- Again Web site 2.在 httpd.conf 中为第二个站点创建一个新的配置节，并将其放在上一个虚拟主机配置节下面（这两个应该看起来非常相似）。此节告诉 Web 服务器在哪里可以找到第二个站点的 HTML 文件。<VirtualHost 127.0.0.1:80> DocumentRoot /var/www/html2 ServerName www.site2.org </VirtualHost>重启 HTTPD，并使用 Lynx 来查看结果。[root@testvm1 httpd]# systemctl restart httpd [root@testvm1 httpd]# lynx www.site2.org Hello World -- Again Web site 2. <snip> Commands: Use arrow keys to move, '?' for help, 'q' to quit, '<-' to go back. Arrow keys: Up and Down to move. Right to follow a link; Left to go back. H)elp O)ptions P)rint G)o M)ain screen Q)uit /=search [delete]=history list 在这里，我压缩了输出结果以适应这个空间。页面的差异表明这是第二个站点。要同时显示两个站点，请打开另一个终端会话并使用 Lynx Web 浏览器查看另一个站点。其他考虑这个简单的例子展示了如何使用 Apache HTTPD 服务器的单个实例来服务于两个站点。当考虑其他因素时，配置虚拟主机会变得有点复杂。例如，你可能希望为这些网站中的一个或全部使用一些 CGI 脚本。为此，你可能为 CGI 程序在 /var/www 目录下创建一些目录：/var/www/cgi-bin 和 /var/www/cgi-bin2，以与 HTML 目录命名一致。然后，你需要将配置指令添加到虚拟主机节，以指定 CGI 脚本的目录位置。每个站点可以有下载文件的目录。这还需要相应虚拟主机节中的条目。Apache 网站描述了管理多个站点的其他方法，以及从性能调优到安全性的配置选项。Apache 是一个强大的 Web 服务器，可以用来管理从简单到高度复杂的网站。尽管其总体市场份额在缩小，但它仍然是互联网上最常用的 HTTPD 服务器。via: https://opensource.com/article/18/3/configuring-multiple-web-sites-apache作者：David Both 译者：MjSeven 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
自从我写了上一篇博文之后，就再也找不到空闲时间写文章了。今天我终于可以抽出时间写一些关于 HTTP 的东西。我认为每一个 web 开发者都应该对这个支撑了整个 Web 世界的 HTTP 协议有所了解，这样才能帮助你更好的完成开发任务。在这篇文章中，我将讨论什么是 HTTP，它是怎么产生的，它的地位，以及我们应该怎么使用它。HTTP 是什么首先我们要明白 HTTP 是什么。HTTP 是一个基于 TCP/IP 的应用层通信协议，它是客户端和服务端在互联网互相通讯的标准。它定义了内容是如何通过互联网进行请求和传输的。HTTP 是在应用层中抽象出的一个标准，使得主机（客户端和服务端）之间的通信得以通过 TCP/IP 来进行请求和响应。TCP 默认使用的端口是 80，当然也可以使用其它端口，比如 HTTPS 使用的就是 443 端口。HTTP/0.9 - 单行协议 (1991)HTTP 最早的规范可以追溯到 1991 年，那时候的版本是 HTTP/0.9，该版本极其简单，只有一个叫做 GET的请求方式。如果客户端要访问服务端上的一个页面，只需要如下非常简单的请求：GET /index.html 服务端对应的返回类似如下：(response body) (connection closed) 就这么简单，服务端捕获到请求后立马返回 HTML 并且关闭连接，在这之中没有 头信息 （ headers ） 仅支持 GET 这一种请求方法必须返回 HTML如同你所看到的，当时的 HTTP 协议只是一块基础的垫脚石。HTTP/1.0 - 1996在 1996 年，新版本的 HTTP 对比之前的版本有了极大的改进，同时也被命名为 HTTP/1.0。与 HTTP/0.9 只能返回 HTML 不同的是，HTTP/1.0 支持处理多种返回的格式，比如图片、视频、文本或者其他格式的文件。它还增加了更多的请求方法（如 POST 和 HEAD），请求和响应的格式也相应做了改变，两者都增加了头信息；引入了状态码来定义返回的特征；引入了字符集支持；支持 多段类型 （ multi-part ） 、用户验证信息、缓存、内容编码格式等等。一个简单的 HTTP/1.0 请求大概是这样的：GET / HTTP/1.0 Host: kamranahmed.info User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) Accept: */* 正如你所看到的，在请求中附带了客户端中的一些个人信息、响应类型要求等内容。这些是在 HTTP/0.9 无法实现的，因为那时候没有头信息。一个对上述请求的响应例子如下所示：HTTP/1.0 200 OK Content-Type: text/plain Content-Length: 137582 Expires: Thu, 05 Dec 1997 16:00:00 GMT Last-Modified: Wed, 5 August 1996 15:55:28 GMT Server: Apache 0.84 (response body) (connection closed) 从 HTTP/1.0 （HTTP 后面跟的是版本号）早期开始，在状态码 200 之后就附带一个原因短语（你可以用来描述状态码）。在这个较新一点的版本中，请求和响应的头信息仍然必须是 ASCII 编码，但是响应的内容可以是任意类型，如图片、视频、HTML、文本或其他类型，服务器可以返回任意内容给客户端。所以这之后，HTTP 中的“ 超文本 （ Hyper Text ） ”成了名不副实。 HMTP （ 超媒体传输协议 （ Hypermedia transfer protocol ） ）可能会更有意义，但是我猜我们还是会一直沿用这个名字。HTTP/1.0 的一个主要缺点就是它不能在一个连接内拥有多个请求。这意味着，当客户端需要从服务器获取东西时，必须建立一个新的 TCP 连接，并且处理完单个请求后连接即被关闭。需要下一个东西时，你必须重新建立一个新的连接。这样的坏处在哪呢？假设你要访问一个有 10 张图片，5 个 样式表 （ stylesheet ） 和 5 个 JavaScript 的总计 20 个文件才能完整展示的一个页面。由于一个连接在处理完成一次请求后即被关闭，所以将有 20 个单独的连接，每一个文件都将通过各自对应的连接单独处理。当连接数量变得庞大的时候就会面临严重的性能问题，因为 TCP 启动需要经过三次握手，才能缓慢开始。三次握手三次握手是一个简单的模型，所有的 TCP 连接在传输应用数据之前都需要在三次握手中传输一系列数据包。SYN - 客户端选取一个随机数，我们称为 x，然后发送给服务器。SYN ACK - 服务器响应对应请求的 ACK 包中，包含了一个由服务器随机产生的数字，我们称为 y，并且把客户端发送的 x+1，一并返回给客户端。ACK - 客户端在从服务器接受到 y 之后把 y 加上 1 作为一个 ACK 包返回给服务器。一旦三次握手完成后，客户端和服务器之间就可以开始交换数据。值得注意的是，当客户端发出最后一个 ACK 数据包后，就可以立刻向服务器发送应用数据包，而服务器则需要等到收到这个 ACK 数据包后才能接受应用数据包。请注意，上图有点小问题，客户端发回的最后一个 ACK 包仅包含 y+1，上图应该是 ACK:y+1 而不是 ACK:x+1,y+1然而，某些 HTTP/1.0 的实现试图通过新引入一个称为 Connection: keep-alive 的头信息来克服这一问题，这个头信息意味着告诉服务器“嘿，服务器，请不要关闭此连接，我还要用它”。但是，这并没有得到广泛的支持，问题依然存在。除了无连接之外，HTTP 还是一个无状态的协议，即服务器不维护有关客户端的信息。因此每个请求必须给服务器必要的信息才能完成请求，每个请求都与之前的旧的请求无关。所以，这增加了推波助澜的作用，客户端除了需要新建大量连接之外，在每次连接中还需要发送许多重复的数据，这导致了带宽的大量浪费。HTTP/1.1 - 1999HTTP/1.0 经过仅仅 3 年，下一个版本，即 HTTP/1.1 就在 1999 年发布了，改进了它的前身很多问题，主要的改进包括：增加了许多 HTTP 请求方法，包括 PUT、PATCH、HEAD、OPTIONS、DELETE。主机标识符 Host 在 HTTP/1.0 并不是必须的，而在 HTTP/1.1 是必须的。如上所述的持久连接。在 HTTP/1.0 中每个连接只有一个请求并在该请求结束后被立即关闭，这导致了性能问题和增加了延迟。 HTTP/1.1 引入了持久连接，即连接在默认情况下是不关闭并保持开放的，这允许多个连续的请求使用这个连接。要关闭该连接只需要在头信息加入 Connection: close，客户通常在最后一个请求里发送这个头信息就能安全地关闭连接。新版本还引入了“ 管线化 （ pipelining ） ”的支持，客户端可以不用等待服务器返回响应，就能在同一个连接内发送多个请求给服务器，而服务器必须以接收到的请求相同的序列发送响应。但是你可能会问了，客户端如何知道哪里是第一个响应下载完成而下一个响应内容开始的地方呢？要解决这个问题，头信息必须有 Content-Length，客户可以使用它来确定哪些响应结束之后可以开始等待下一个响应。值得注意的是，为了从持久连接或管线化中受益， 头部信息必须包含 Content-Length，因为这会使客户端知道什么时候完成了传输，然后它可以发送下一个请求（持久连接中，以正常的依次顺序发送请求）或开始等待下一个响应（启用管线化时）。但是，使用这种方法仍然有一个问题。那就是，如果数据是动态的，服务器无法提前知道内容长度呢？那么在这种情况下，你就不能使用这种方法中获益了吗？为了解决这个问题，HTTP/1.1 引进了分块编码。在这种情况下，服务器可能会忽略 Content-Length 来支持分块编码（更常见一些）。但是，如果它们都不可用，那么连接必须在请求结束时关闭。在动态内容的情况下分块传输，当服务器在传输开始但无法得到 Content-Length 时，它可能会开始按块发送内容（一块接一块），并在传输时为每一个小块添加 Content-Length。当发送完所有的数据块后，即整个传输已经完成后，它发送一个空的小块，比如设置 Content-Length 为 0 ，以便客户端知道传输已完成。为了通知客户端块传输的信息，服务器在头信息中包含了 Transfer-Encoding: chunked。不像 HTTP/1.0 中只有 Basic 身份验证方式，HTTP/1.1 包括 摘要验证方式 （ digest authentication ） 和 代理验证方式 （ proxy authentication ） 。缓存。 范围请求 （ Byte Ranges ） 。字符集。 内容协商 （ Content Negotiation ） 。客户端 cookies。支持压缩。新的状态码。等等。我不打算在这里讨论所有 HTTP/1.1 的特性，因为你可以围绕这个话题找到很多关于这些的讨论。我建议你阅读 HTTP/1.0 和 HTTP/1.1 版本之间的主要差异，希望了解更多可以读原始的 RFC。HTTP/1.1 在 1999 年推出，到现在已经是多年前的标准。虽然，它比前一代改善了很多，但是网络日新月异，它已经垂垂老矣。相比之前，加载网页更是一个资源密集型任务，打开一个简单的网页已经需要建立超过 30 个连接。你或许会说，HTTP/1.1 具有持久连接，为什么还有这么多连接呢？其原因是，在任何时刻 HTTP/1.1 只能有一个未完成的连接。 HTTP/1.1 试图通过引入管线来解决这个问题，但它并没有完全地解决。因为一旦管线遇到了缓慢的请求或庞大的请求，后面的请求便被阻塞住，它们必须等待上一个请求完成。为了克服 HTTP/1.1 的这些缺点，开发人员开始实现一些解决方法，例如使用 spritesheets、在 CSS 中编码图像、单个巨型 CSS / JavaScript 文件、域名切分等。SPDY - 2009谷歌走在业界前列，为了使网络速度更快，提高网络安全，同时减少网页的等待时间，他们开始实验替代的协议。在 2009 年，他们宣布了 SPDY。SPDY 是谷歌的商标，而不是一个缩写。显而易见的是，如果我们继续增加带宽，网络性能开始的时候能够得到提升，但是到了某个阶段后带来的性能提升就很有限了。但是如果把这些优化放在等待时间上，比如减少等待时间，将会有持续的性能提升。这就是 SPDY 优化之前的协议的核心思想，减少等待时间来提升网络性能。对于那些不知道其中区别的人，等待时间就是延迟，即数据从源到达目的地需要多长时间（单位为毫秒），而带宽是每秒钟数据的传输量（比特每秒）。SPDY 的特点包括：复用、压缩、优先级、安全性等。我不打算展开 SPDY 的细节。在下一章节，当我们将介绍 HTTP/2，这些都会被提到，因为 HTTP/2 大多特性是从 SPDY 受启发的。SPDY 没有试图取代 HTTP，它是处于应用层的 HTTP 之上的一个传输层，它只是在请求被发送之前做了一些修改。它开始成为事实标准，大多数浏览器都开始支持了。2015年，谷歌不想有两个相互竞争的标准，所以他们决定将其合并到 HTTP 协议，这样就导致了 HTTP/2 的出现和 SPDY 的废弃。HTTP/2 - 2015现在想必你明白了为什么我们需要另一个版本的 HTTP 协议了。 HTTP/2 是专为了低延迟地内容传输而设计。主要特点和与 HTTP/1.1 的差异包括：使用二进制替代明文多路传输 - 多个异步 HTTP 请求可以使用单一连接报头使用 HPACK 压缩服务器推送 - 单个请求多个响应请求优先级安全性1. 二进制协议HTTP/2 通过使其成为一个二进制协议以解决 HTTP/1.x 中存在的延迟问题。作为一个二进制协议，它更容易解析，但可读性却不如 HTTP/1.x。 帧 （ frames ） 和 流 （ stream ） 的概念组成了 HTTP/2 的主要部分。帧和流现在 HTTP 消息是由一个或多个帧组成的。HEADERS 帧承载了 元数据 （ meta data ） ，DATA 帧则承载了内容。还有其他类型的帧（HEADERS、DATA、RST_STREAM、SETTINGS、PRIORITY 等等），这些你可以通过 HTTP/2 规范来了解。每个 HTTP/2 请求和响应都被赋予一个唯一的流 ID，并切分成帧。帧就是一小片二进制数据。帧的集合称为流，每个帧都有个标识了其所属流的流 ID，所以在同一个流下的每个帧具有共同的报头。值得注意的是，​除了流 ID 是唯一的之外，​由客户端发起的请求使用了奇数作为流 ID，从来自服务器的响应使用了偶数作为流 ID。除了 HEADERS 帧和 DATA 帧，另一个值得一提的帧是 RST_STREAM。这是一个特殊的帧类型，用来中止流，即客户可以发送此帧让服务器知道，我不再需要这个流了。在 HTTP/1.1 中让服务器停止给客户端发送响应的唯一方法是关闭连接，这样造成了延迟增加，因为之后要发送请求时，就要必须打开一个新的请求。而在 HTTP/2，客户端可以使用 RST_STREAM 来停止接收特定的数据流，而连接仍然打开着，可以被其他请求使用。2. 多路传输因为 HTTP/2 是一个二进制协议，而且如上所述它使用帧和流来传输请求与响应，一旦建立了 TCP 连接，相同连接内的所有流都可以同过这个 TCP 连接异步发送，而不用另外打开连接。反过来说，服务器也可以使用同样的异步方式返回响应，也就是说这些响应可以是无序的，客户端使用分配的流 ID 来识别数据包所属的流。这也解决了 HTTP/1.x 中请求管道被阻塞的问题，即客户端不必等待占用时间的请求而其他请求仍然可以被处理。3. HPACK 请求头部压缩RFC 花了一篇文档的篇幅来介绍针对发送的头信息的优化，它的本质是当我们在同一客户端上不断地访问服务器时，许多冗余数据在头部中被反复发送，有时候仅仅是 cookies 就能增加头信息的大小，这会占用许多宽带和增加传输延迟。为了解决这个问题，HTTP/2 引入了头信息压缩。不像请求和响应那样，头信息中的信息不会以 gzip 或者 compress 等格式压缩。而是采用一种不同的机制来压缩头信息，客户端和服务器同时维护一张头信息表，储存了使用了哈夫曼编码进行编码后的头信息的值，并且后续请求中若出现同样的字段则忽略重复值（例如 用户代理 （ user agent ） 等），只发送存在两边信息表中它的引用即可。我们说的头信息，它们同 HTTP/1.1 中一样，并在此基础上增加了一些伪头信息，如 :scheme，:host 和 :path。4. 服务器推送服务器推送是 HTTP/2 的另一个巨大的特点。对于服务器来说，当它知道客户端需要一定的资源后，它可以把数据推送到客户端，即使客户端没有请求它。例如，假设一个浏览器在加载一个网页时，它解析了整个页面，发现有一些内容必须要从服务端获取，然后发送相应的请求到服务器以获取这些内容。服务器推送减少了传输这些数据需要来回请求的次数。它是如何做到的呢？服务器通过发送一个名字为 PUSH_PROMISE 特殊的帧通知到客户端“嘿，我准备要发送这个资源给你了，不要再问我要了。”这个 PUSH_PROMISE 帧与要产生推送的流联系在一起，并包含了要推送的流 ID，也就是说这个流将会被服务器推送到客户端上。5. 请求优先级当流被打开的时候，客户端可以在 HEADERS 帧中包含优先级信息来为流指定优先级。在任何时候，客户端都可以发送 PRIORITY 帧来改变流的优先级。如果没有任何优先级信息，服务器将异步地无序地处理这些请求。如果流分配了优先级，服务器将基于这个优先级来决定需要分配多少资源来处理这个请求。6. 安全性在是否强制使用 TLS 来增加安全性的问题上产生了大范围的讨论，讨论的结果是不强制使用。然而大多数厂商只有在使用 TLS 时才能使用 HTTP/2。所以 HTTP/2 虽然规范上不要求加密，但是加密已经约定俗成了。这样，在 TLS 之上实现 HTTP/2 就有了一些强制要求，比如，TLS 的最低版本为 1.2，必须达到某种级别的最低限度的密钥大小，需要布署 ephemeral 密钥等等。到现在 HTTP/2 已经完全超越了 SPDY，并且还在不断成长，HTTP/2 有很多关系性能的提升，我们应该开始布署它了。如果你想更深入的了解细节，请访问该规范的链接和 HTTP/2 性能提升演示的链接。请在留言板写下你的疑问或者评论，最后如果你发现有错误，请同样留言指出。这就是全部了，我们之后再见~via: http://kamranahmed.info/blog/2016/08/13/http-in-depth/作者：Kamran Ahmed 译者：NearTan 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
这是一个针对 PHP、Go、Python 等语言的 CGI 应用的漏洞。httpoxy 是一系列影响到以 CGI 或类 CGI 方式运行的应用的漏洞名称。简单的来说，它就是一个名字空间的冲突问题。RFC 3875 （CGI）中定义了从 HTTP 请求的 Proxy 头部直接填充到环境变量 HTTP_PROXY 的方式HTTP_PROXY 是一个常用于配置外发代理的环境变量这个缺陷会导致远程攻击。如果你正在运行着 PHP 或 CGI 程序，你应该马上封挡 Proxy 头部！马上！ 具体做法参见下面。httpoxy 是一个服务器端 web 应用漏洞，如果你没有在服务器端部署这些代码，则不用担心。如果我的 Web 应用存在这种漏洞会怎么样？当一个利用了此漏洞的 HTTP 客户端发起请求时，它可以做到：通过你的 Web 应用去代理请求别的 URL直接让你的服务器打开指定的远程地址及端口浪费服务器的资源，替攻击者访问指定的资源httpoxy 漏洞非常容易利用。希望安全人员尽快扫描该漏洞并快速修复。哪些受到影响？以下情况会存在安全漏洞：代码运行在 CGI 上下文中，这样 HTTP_PROXY 就会变成一个真实的或模拟的环境变量一个信任 HTTP_PROXY的 HTTP 客户端，并且支持代理功能该客户端会在请求内部发起一个 HTTP（或 HTTPS）请求下列情形是已经发现存在该缺陷的环境：语言环境HTTP 客户端PHPphp-fpm mod\_phpGuzzle 4+ ArtaxPythonwsgiref.handlers.CGIHandler twisted.web.twcgi.CGIScriptrequestsGonet/http/cginet/http肯定还有很多我们没有确定是否存在缺陷的语言和环境。PHP是否存在缺陷依赖于你的应用代码和 PHP 库，但是影响面看起来似乎非常广泛只要在处理用户请求的过程中使用了一个带有该缺陷的库，就可能被利用如果你使用了有该缺陷的库，该缺陷会影响任意 PHP 版本甚至会影响到替代的 PHP 运行环境，比如部署在 FastCGI 模式下的 HHVM确认影响 Guzzle、Artax 等库，可能还有很多很多的库也受影响Guzzle 4.0.0rc2 及其以后版本受影响，Guzzle 3 及更低版本不受影响其它的例子还有 Composer 的 StreamContextBuilder 工具类举个例子说，如果你在 Drupal 中使用 Guzzle 6 模块发起外发请求（比如请求一个天气 API），该模块发起的请求就存在这个 httpoxy 缺陷。PythonPython 代码只有部署在 CGI 模式下才存在缺陷，一般来说，存在缺陷的代码会使用类似 wsgiref.handlers.CGIHandler 的 CGI 控制器正常方式部署的 Python web 应用不受影响（大多数人使用 WSGI 或 FastCGI，这两个不受影响），所以受到影响的 Python 应用要比 PHP 少得多wsgi 不受影响，因为 os.environ 不会受到 CGI 数据污染存在缺陷的 requests 库必须信任和使用 os.environ['HTTP_PROXY']，并且不做内容检查GoGo 代码必须部署在 CGI 下才受影响。一般来说受到影响的代码会使用 net/http/cgi 包像 Python 一样，这并不是部署 Go 为一个 Web 应用的通常方式。所以受到影响的情形很少相较而言，Go 的 net/http/fcgi 包并不设置实际的环境变量，所以不受影响存在缺陷的 net/http 版本需要在外发请求中信任并使用 HTTP_PROXY ，并不做内容检查马上修复最好的修复方式是在他们攻击你的应用之前尽早封挡 Proxy 请求头部。这很简单，也很安全。说它安全是因为 IETF 没有定义 Proxy 请求头部，也没有列在 IANA 的消息头部注册中。这表明对该头部的使用是非标准的，甚至也不会临时用到符合标准的 HTTP 客户端和服务器绝不应该读取和发送这个头部你可以从请求中去掉这个头部或者干脆整个封挡使用它的请求你可以在上游没有发布补丁时自己来解决这个问题当 HTTP 请求进来时就检查它，这样可以一次性修复好许多存在缺陷的应用在反向代理和应用防火墙之后的应用剔除 Proxy 请求头部是安全的如何封挡 Proxy 请求头部依赖于你的配置。最容易的办法是在你的 Web 应用防火墙上封挡该头部，或者直接在 Apache 和 Nginx 上做也行。以下是一些如何做的指导：Nginx/FastCGI使用如下语句封挡传递给 PHP-FPM、PHP-PM 的请求头，这个语句可以放在 fastcgi.conf 或 fastcgi\_param 中（视你使用了哪个配置文件）：fastcgi_param HTTP_PROXY ""; 在 FastCGI 模式下，PHP 存在缺陷（但是大多数使用 Nginx FastCGI 的其它语言则不受影响）。Apache对于 Apache 受影响的具体程度，以及其它的 Apache 软件项目，比如 Tomcat ，推荐参考 Apache 软件基金会的官方公告。 以下是一些主要信息：如果你在 Apache HTTP 服务器中使用 mod_cgi来运行 Go 或 Python 写的脚本，那么它们会受到影响（这里 HTTP_PROXY 环境变量是“真实的”）。而 mod_php 由于用于 PHP 脚本，也存在该缺陷。如果你使用 mod\_headers 模块，你可以通过下述配置在进一步处理请求前就 unset 掉 Proxy 请求头部：RequestHeader unset Proxy early 如果你使用 mod\_security 模块，你可以使用一个 SecRule 规则来拒绝带有 Proxy 请求头部的请求。下面是一个例子，要确保 SecRuleEngine 打开了。你可以根据自己的情况调整。SecRule &REQUEST_HEADERS:Proxy "@gt 0" "id:1000005,log,deny,msg:'httpoxy denied'" 最后，如果你使用 Apache Traffic Server 的话，它本身不受影响。不过你可以用它来剔除掉 Proxy 请求头部，以保护其后面的其它服务。具体可以参考 ASF 指导。HAProxy通过下述配置剔除该请求头部：http-request del-header Proxy Varnish通过下述语句取消该头部，请将它放到已有的 vcl\_recv 小节里面：sub vcl_recv { [...] unset req.http.proxy; [...] } OpenBSD relayd使用如下语句移除该头部。把它放到已有的过滤器里面：http protocol httpfilter { match request header remove "Proxy" } lighttpd （<= 1.4.40）弹回包含 Proxy 头部的请求。创建一个 /path/to/deny-proxy.lua文件，让它对于 lighttpd 只读，内容如下：if (lighty.request["Proxy"] == nil) then return 0 else return 403 end 修改 lighttpd.conf 以加载 mod_magnet 模块，并运行如上 lua 代码：server.modules += ( "mod_magnet" ) magnet.attract-raw-url-to = ( "/path/to/deny-proxy.lua" ) lighttpd2 (开发中)从请求中剔除 Proxy 头部。加入如下语句到 lighttpd.conf中：req_header.remove "Proxy";用户端的 PHP 修复没有作用用户端的修复不能解决该缺陷，所以不必费劲：使用 unset($_SERVER['HTTP_PROXY']) 并不会影响到 getenv() 返回的值，所以无用使用 putenv('HTTP_PROXY=') 也没效果（putenv 只能影响到来自实际环境变量的值，而不是来自请求头部的）httpoxy 的历史该漏洞首次发现与15年前。2001 年 3 月Randal L. Schwartz 在 libwww-perl 发现该缺陷并修复。2001 年 4 月Cris Bailiff 在 curl 中发现该缺陷并修复。2012 年 7 月在Net::HTTP 的 HTTP_PROXY 实现中， Ruby 团队的 Akira Tanaka 发现了该缺陷2013 年 11 月在 nginx 邮件列表中提到了该缺陷。发现者 Jonathan Matthews 对此不太有把握，不过事实证明他是对的。2015 年 2 月Stefan Fritsch 在 Apache httpd-dev 邮件列表中提到了它。2016 年 7 月Vend 安全团队的 Scott Geary 发现了对该缺陷，并且它影响到了 PHP 等许多现代的编程语言和库。所以，这个缺陷已经潜伏了许多年，许多人都在不同方面发现了它的存在，但是没有考虑到它对其它语言和库的影响。安全研究人员为此专门建立了一个网站： https://httpoxy.org/ ，可以在此发现更多内容。
上周五，IESG（ 互联网工程指导委员会 （ Internet Engineering Steering Group ） ）批准了一个新的互联网标准，为 HTTP 增加了一个新状态码：451 Unavailable For Legal Reasons。还需要一点点工作就会发布为正式的 RFC ，不过现在已经可以用了。缘起几年前，英国政府要求 ISP 们对海盗湾的内容进行封挡，Terence Eden 就这个事情写了一个帖子，建议应该有一个不同的状态码来区分禁止访问的原因。这样的话，ISP 们就可以向他们的用户说明为什么这些资源不能访问。有人提议使用数字 451 作为状态码，也有各种其它的建议。谷歌的 Tim Bray 受此启发，于前几年给 HTTP 工作组 HTTP Working Group 提交了一份提案，他（及很多人）认为应该将由于技术原因的不可见与非技术原因的不可见区分开来：403 状态码，用于描述由于技术原因禁止访问451 状态码，则用于描述由于国家法律所要求而禁止访问据说，451 这个数字来源于 Ray Bradbury 的一篇小说《 华氏 451 （ Fahrenheit 451 ） 》。发展最初，对于这份提案，IETF HTTP 工作组的主席 Mark Nottingham 是拒绝的。因为 HTTP 状态码是有限的，虽然说从 400 到 499 有足足一百个位置，但是谁也不知道将来会有什么需求。而当时也没有任何需要让程序/机器来区分禁止访问的这两种情形，所以不应该浪费代码——最多，在 HTTP 首部或页面上呈现具体原因就可以了。而 Tim 依旧坚持他的提案，并偶尔更新一下提案内容，和关心这件事的人谈论。有一些站点开始实验性的使用这个状态码，不过这并不足以让 IESG 同意增加新的状态码。但是，随着网络上的审查的越来越多，比如欧盟政府要求 ISP 禁止对盗版内容的访问，而韩国、俄罗斯和其它一些国家会限制某些内容的访问等等，IESG 意识到网站需要能够区分这其中的不同。此外，还有一些人希望能够自动找到和分类哪些内容是被审查的。这就需要一种机器可读的机制来区分 403 和 451 所代表的不同意义。451 能做什么和不能做什么虽然 451 状态码的原意是用于标示出哪些内容是被法律禁止访问的，比如可以用在网络设备上（比如防火墙）或 Web 服务器上。就目前已知的， Github、 Twitter、 Facebook 和 Google 都已经开始使用这个状态码来应对各个国家地区的审查要求了。但是显然，451 状态码并不能标示出所有的被审查的内容，也许有些国家（比如英国）的法律要求不允许使用这个状态码。本文内容编译自 Mark Nottingham 的博客、维基百科、Solidot 等来源。
假设由于某种原因，你需要嗅探HTTP站点的流量（如HTTP请求与响应）。举个例子，你可能在测试一个web服务器的实验性功能，或者你在为某个web应用或RESTful服务排错，又或者你正在为PAC(proxy auto config)排错或寻找某个站点下载的恶意软件。不论什么原因，在这些情况下，进行HTTP流量嗅探对于系统管理、开发者、甚至最终用户来说都是很有帮助的。数据包嗅工具tcpdump被广泛用于实时数据包的导出，但是你需要设置过滤规则来捕获HTTP流量，甚至它的原始输出通常不能方便的停在HTTP协议层。实时web服务器日志解析器如ngxtop可以提供可读的实时web流量跟踪痕迹，但这仅适用于可完全访问live web服务器日志的情况。要是有一个仅用于抓取HTTP流量的类似tcpdump的数据包嗅探工具就非常好了。事实上，httpry就是:HTTP包嗅探工具。httpry捕获HTTP数据包，并且将HTTP协议层的数据内容以可读形式列举出来。通过这篇指文章，让我们了解如何使用httpry工具嗅探HTTP流量。在Linux上安装httpry基于Debian系统（Ubuntu 或 LinuxMint），基础仓库中没有httpry安装包(译者注：本人ubuntu14.04,仓库中已有包，可直接安装)。所以我们需要通过源码安装:$ sudo apt-get install gcc make git libpcap0.8-dev $ git clone https://github.com/jbittel/httpry.git $ cd httpry $ make $ sudo make install 在Fedora，CentOS 或 RHEL系统，可以使用如下yum命令安装httpry。在CentOS/RHEL系统上，运行yum之前使其能够访问EPEL repo。$ sudo yum install httpry 如果你仍想通过基于RPM系统的源码来安装httpry的话，你可以通过这几个步骤实现：$ sudo yum install gcc make git libpcap-devel $ git clone https://github.com/jbittel/httpry.git $ cd httpry $ make $ sudo make install httpry的基本用法以下是httpry的基本用法$ sudo httpry -i <network-interface> httpry就会监听指定的网络接口，并且实时的显示捕获到的HTTP请求/相应。在大多数情况下，由于发送与接到的数据包过多导致刷屏很快，难以分析。这时候你肯定想将捕获到的数据包保存下来以便离线分析。可以使用'b'或'-o'选项保存数据包。'-b'选项将数据包以二进制文件的形式保存下来，这样可以使用httpry软件打开文件以浏览。另一方面，'-o'选项将数据以可读的字符文件形式保存下来。以二进制形式保存文件：$ sudo httpry -i eth0 -b output.dump 浏览所保存的HTTP数据包文件：$ httpry -r output.dump 注意，不需要根用户权限就可以使用'-r'选项读取数据文件。将httpry数据以字符文件保存：$ sudo httpry -i eth0 -o output.txt httpry 的高级应用如果你想监视指定的HTTP方法（如：GET，POST，PUT，HEAD，CONNECT等），使用'-m'选项：$ sudo httpry -i eth0 -m get,head 如果你下载了httpry的源码，你会发现源码下有一些Perl脚本，这些脚本用于分析httpry输出。脚本位于目录httpry/scripts/plugins。如果你想写一个定制的httpry输出分析器，则这些脚可以作为很好的例子。其中一些有如下的功能：hostnames: 显示唯一主机名列表。find\_proxies: 探测web代理。search\_terms: 查找及统计在搜索服务里面的搜索词。content\_analysis: 查找含有指定关键的URL。xml\_output: 将输出转换为XML形式。log\_summary: 生成日志汇总。db\_dump: 将日志文件数据保存数据库。在使用这些脚本之前，首先使用'-o'选项运行httpry。当获取到输出文件后，立即使用如下命令执行脚本：$ cd httpry/scripts $ perl parse_log.pl -d ./plugins <httpry-output-file> 你可能在使用插件的时候遇到警告。比如，如果你没有安装带有DBI接口的MySQL数据库，那么使用db\_dump插件时可能会失败。如果一个插件初始化失败的话，那么只是这个插件不能使用，所以你可以忽略那些警告。当parse\_log.pl完成后，你将在httpry/scripts 目录下看到数个分析结果。例如，log\_summary.txt 与如下内容类似。总的来说，当你要分析HTTP数据包的时候，httpry非常有用。它可能并不被大多Linux使用者所熟知，但会用总是有好处的。你觉得这个工具怎么样呢？via: http://xmodulo.com/2014/08/sniff-http-traffic-command-line-linux.html作者：Dan Nanni 译者：DoubleC 校对：wxy本文由 LCTT 原创翻译，Linux中国 荣誉推出