标签 http 下的文章

Python 的 httpx 包是一个用于 HTTP 交互的一个优秀且灵活的模块。

 title=

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_urlauth,你可以为认证的客户端建立一个漂亮的抽象:

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:使用网络上的终端访问设备

另一种访问设备的方法是从类似 PuTTYTermius 之类的 SSH 客户端,甚至 Linux 终端访问。我们可以使用称为 SSHID 的 ShellHub 标识作为连接的目的地址(例如 ssh username@SSHID)。图 4 说明了如何使用在终端中使用 Linux SSH 客户端连接到我们的计算机。

图 4:使用 Linux 终端连接到设备

无论你何时登录 ShellHub Cloud 平台,你都可以访问仪表板上的所有已注册设备,这样你可以随时随地访问它们。ShellHub 通过一个开源平台,以透明的方式为您与远程机器保持通信安全的过程增加了简单性。

GitHub 上加入 ShellHub 社区,或随时通过 Gitter 或通过电子邮件 [email protected] 向开发团队发送你的建议或反馈。我们很乐意收到社区成员的贡献!


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 RFC

1991 年至 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 ❸
<!doctype html>
(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 1999

HTTP 协议的简单性是它最初被采用和快速增长的原因。事实上,现在使用 HTTP 作为主要控制和数据协议的嵌入式设备(传感器,执行器和咖啡壶)并不罕见。但在其自身成功的重压下,随着我们越来越多地继续将日常互动转移到网络 —— 社交、电子邮件、新闻和视频,以及越来越多的个人和工作空间,它也开始显示出压力的迹象。用户和 Web 开发人员现在都要求 HTTP/1.1 提供近乎实时的响应能力和协议 性能,如果不进行一些修改,就无法满足这些要求。

为了应对这些新挑战,HTTP 必须继续发展,因此 HTTPbis 工作组在 2012 年初宣布了一项针对 HTTP/2 的新计划:

已经有一个协议中出现了新的实现经验和兴趣,该协议保留了 HTTP 的语义,但是没有保留 HTTP/1.x 的消息框架和语法,这些问题已经被确定为妨碍性能和鼓励滥用底层传输。

工作组将使用有序的双向流中生成 HTTP 当前语义的新表达式的规范。与 HTTP/1.x 一样,主要传输目标是 TCP,但是应该可以使用其他方式传输。

HTTP/2 charter, January 2012

HTTP/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 validation

gorilla/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
  • 诸如 ClichesCreateClichesAll 之类的请求处理程序向 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中国 荣誉推出

如今,我有很多方法来通过 Web 浏览器为局域网中的其他系统提供单个文件或整个目录的访问。我在我的 Ubuntu 测试机上测试了这些方法,它们如下面描述的那样运行正常。如果你想知道如何在类 Unix 操作系统中通过 HTTP 轻松快速地提供文件和文件夹的访问服务,以下方法之一肯定会有所帮助。

在 Linux 中通过 HTTP 访问文件和文件夹

免责声明:此处给出的所有方法适用于安全的局域网。由于这些方法没有任何安全机制,因此不建议在生产环境中使用它们。你注意了!

方法 1 - 使用 simpleHTTPserver(Python)

我们写了一篇简要的指南来设置一个简单的 http 服务器,以便在以下链接中即时共享文件和目录。如果你有一个安装了 Python 的系统,这个方法非常方便。

方法 2 - 使用 Quickserve(Python)

此方法针对 Arch Linux 及其衍生版。有关详细信息,请查看下面的链接。

方法 3 - 使用 Ruby

在此方法中,我们使用 Ruby 在类 Unix 系统中通过 HTTP 提供文件和文件夹访问。按照以下链接中的说明安装 Ruby 和 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。

安装 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。

安装 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 种方法。但是,本指南末尾附带的链接中还提供了几种方法。也去测试一下它们。此外,收藏并时不时重新访问它来检查将来是否有新的方法。

今天就是这些。希望这篇文章有用。还有更多的好东西。敬请期待!

干杯!

资源


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):

<h1>Hello World</h1>

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,其中包含以下内容(此索引文件稍有不同,以区别于原来的网站):

<h1>Hello World -- Again</h1>

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中国 荣誉推出