我在 GitHub 上开源了这个系列文章的翻译版(还在跟进原作者的更新)欢迎 PR。
📖 📖 📖
原文:《Let’s Build A Web Server. Part 1.》
一个女人外出散步,路过一个工地,看到三个人在工作。她问第一个人,“你在做什么呀?”,第一个人不耐烦地冲她吼:“你没看到我在垒砖啊?!” 她不满足于这个回答,又问了第二个人他在做什么。第二个人回答道:“我在砌一堵墙。” 第二个人转向第一个人,说道:“嘿!你砌过头了,赶紧把最后一块儿转拿掉。” 女人还是对这个答案不满意,她问了第三个人同样的问题。第三个人仰望天空,说:“我在盖一座迄今为止世界上最大的教堂。” 他站在这儿仰望着天空,背后那两个人却还在为那块儿不合时宜地砖争论不休。他对第一个人说,“哥们,不用担心那块砖了,那是内墙,最后会涂满涂料,不会有人看见那块儿砖。开始垒另一层好了。”
这个故事的寓意是,当你了解整个系统并了解不同的部分如何组合在一起(砖块,墙壁,大教堂)时,你可以更快地识别和修复问题(放错的砖块)。
这与从头创建一个 Web 服务器有什么关联?
我相信想要成为一个更优秀的开发者,你必须对每天在使用的底层软件系统有更好的了解,包括编程语言、编译器和解释器、数据库和操作系统、Web 服务器和框架等。 而且,为了更好、更深入地理解这些系统,你必须从头开始,一步一个脚印地重新构建它们。
子曰:
“我听到的会忘记”
“我看到的我能记住”
“我做过的我就能理解”
我希望在这一点上你相信通过重新造轮子来了解一个系统的工作原理是个好主意。
在这个由三部分组成的系列文章中,我将向你展示如何构建自己的基础 Web 服务器。让我们开始吧。
首先,什么是 Web 服务器?
简而言之,它是一个位于物理服务器(oops,服务器上的服务器)上的网络服务器,它等待客户端发送请求。当它收到一个请求后,会生成响应并发送回客户端。客户端和服务器之间的通信使用 HTTP 协议
进行。 客户端可以是浏览器或任何其他使用 HTTP 协议的软件。
Web 服务器的非常简单的实现是什么样的? 以下是我的示例。示例是在 Python 中(在 Python3.7 +上测试过),但即使你不了解 Python(它是一种非常容易学习的语言,不妨试试!)你仍然应该能够从下面的代码和解释中理解概念:
# Python3.7+
import socket
HOST, PORT = '', 8888
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
print(f'Serving HTTP on port {PORT} ...')
while True:
client_connection, client_address = listen_socket.accept()
request_data = client_connection.recv(1024)
print(request_data.decode('utf-8'))
http_response = b"""\
HTTP/1.1 200 OK
Hello, World!
"""
client_connection.sendall(http_response)
client_connection.close()
将上面的代码保存为 webserver1.py
或直接从 GitHub 下载并在命令行上运行,如下所示:
$ python webserver1.py
Serving HTTP on port 8888 …
现在,在 Web 浏览器的地址栏中键入以下 URL:http://localhost:8888/hello
,按 Enter 键,然后看到魔法生效。你应该在浏览器中看到“Hello,World”,如下所示:
Just do it!
Are you OK?现在让我们讨论它到底是如何运作的。
首先从我们输入的网址开始。它被称为 URL
,这是它的基本结构:
这就是你告诉浏览器如何去查找和连接 Web 服务器的地址、以及要为你提取的服务器上的页面(路径)的方式。 在你的浏览器发送 HTTP 请求之前,它首先需要与 Web 服务器建立 TCP 连接
。 然后它通过 TCP 连接向服务器发送 HTTP 请求,并等待服务器发回 HTTP 响应。 当您的浏览器收到响应时就会显示服务器的返回,在这个示例中它返回了:“Hello,World!”
让我们更详细地探讨客户端和服务器在发送 HTTP 请求和响应之前如何建立 TCP 连接。为实现连接,它们都使用所谓的 套接字(socket)
。你不要直接使用浏览器,而是在命令行上使用 telnet 手动模拟浏览器。
在运行 Web 服务器的同一台计算机上,在新的命令行上启动 telnet 会话,指定连接到 localhost 的主机和 8888 端口,然后按 Enter 键:
$ telnet localhost 8888
正在连接 localhost …
Connected to localhost.
此时,你已与本地主机上运行的 Web 服务器建立 TCP 连接,并准备发送和接收 HTTP 消息。 在下图中,你可以看到服务器接受新的 TCP 连接必须经历的标准过程:
在同一个 telnet 会话中键入 GET /hello HTTP/1.1
然后 Enter 键:
$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.
GET /hello HTTP/1.1
HTTP/1.1 200 OK
Hello, World!
译者注:在 Win10 的命令行中似乎无法完成,因为在你输入第一个字母的时候连接就断开了,但是依然能收到正确的响应。
打开本地回显提示:连接成功后终端是黑的,此时也可以输入命令,但不回显,所以可以先键入
Ctrl + ]
打开本地回显功能,再次回车,就能进入到回显模式输入命令。
你刚刚已经手动模拟了你的浏览器!你发送了 HTTP 请求并得到了 HTTP 响应。 这是 HTTP 请求的基本结构:
HTTP 请求包含指明 HTTP 方法的行(GET,因为我们要求我们的服务器返回一些东西),路径 /hello
指示我们想要的服务器上的“页”和协议版本。
为简单起见,我们此例中的 Web 服务器此时完全忽略了上述请求行。你也可以输入任何废话而不是 GET /hello HTTP / 1.1
,你仍然可以获得 “Hello,World!”响应。
一旦你键入请求行并按下 Enter 键,客户端将请求发送到服务器,服务器将读取请求行,把它打印出来并返回相应的 HTTP 响应。
以下是服务器发送回客户端的 HTTP 响应(在本例中为 telnet):
让我们剖析它看看。响应包括状态行 HTTP / 1.1 200 OK
,后跟所需的空行,然后是 HTTP 响应主体。
响应状态行 HTTP / 1.1 200 OK 包含 HTTP 版本,HTTP 状态代码和 HTTP 状态代码原因短语 OK。当浏览器获得响应时,它会显示响应的主体,这就是你在浏览器中看到 “Hello,World!” 的原因。
这就是 Web 服务器工作原理的基本模型。总结一下: Web 服务器创建一个监听套接字并通过循环接受新的连接。客户端启动 TCP 连接,并在成功建立 TCP 连接后,客户端向服务器发送 HTTP 请求,服务器响应 HTTP 响应,并显示给客户端。 要建立TCP连接,客户端和服务器都要使用套接字。
现在你有了一个非常基本的可运行的Web服务器,你可以使用浏览器或其他 HTTP 客户端进行测试。正如你所见并希望尝试的那样,你也可以通过使用 telnet 并手动输入 HTTP 请求来成为人工 HTTP 客户端。
这里有一个问题:“如何在新创建的 Web 服务器下运行 Django 应用程序,Flask 应用程序和Pyramid 应用程序,而无需对服务器进行单一更改以适应所有这些不同的 Web 框架?”
我将在本系列的 Part 2 中向您展示如何去做。敬请关注。
用于准备本文的资源(链接是代理链接):
- Unix Network Programming, Volume 1: The Sockets Networking API (3rd Edition)
- Advanced Programming in the UNIX Environment, 3rd Edition
- The Linux Programming Interface: A Linux and UNIX System Programming Handbook
- Lead with a Story
UPDATE: Sat, July 13, 2019
- Updated the server code to run under Python 3.7+
- Added resources used in preparation for the article
此系列的所有文章(已翻译):
- 【从零开始】用 Python 搭建一个 Web 服务器 - Part 1(HTTP请求)
- 【从零开始】用 Python 搭建一个 Web 服务器 - Part 2(WSGI)
- 【从零开始】用 Python 搭建一个 Web 服务器 - Part 3(并发)(上)【未完待续…】
- 【从零开始】用 Python 搭建一个 Web 服务器 - Part 3(并发)(下)
看得出来 Ruslan 很喜欢中国谚语,我们可以去他的博客 Intro: About-Ruslan’s Blog 认识他:
“I hear and I forget. I see and I remember. I do and I understand.” —— Confucius
荀子非常重视实践的作用,他认为:“不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之;学至于行之而止矣。”