蚊章汇 - Ruslan
type: channel
link: https://linuxcn.bwsl.wang/author/373/
description: Ruslan
items: 搭个 Web 服务器（三）
items: 搭个 Web 服务器（二）
items: 搭个 Web 服务器（一）

搭个 Web 服务器（三）
type: item
date: 2016-10-09T22:42:00+08:00
description: “只有在创造中才能够学到更多。” ——皮亚杰在本系列的第二部分中，你创造了一个可以处理基本 HTTP GET 请求的、朴素的 WSGI 服务器。当时我问了一个问题：“你该如何让你的服务器在同一时间处理多个请求呢？”在这篇文章中，你会找到答案。系好安全带，我们要认真起来，全速前进了！你将会体验到一段非常快速的旅程。准备好你的 Linux、Mac OS X（或者其他 *nix 系统），还有你的 Python。本文中所有源代码均可在 GitHub 上找到。服务器的基本结构及如何处理请求首先，我们来回顾一下 Web 服务器的基本结构，以及服务器处理来自客户端的请求时，所需的必要步骤。你在第一部分及第二部分中创建的轮询服务器只能够一次处理一个请求。在处理完当前请求之前，它不能够接受新的客户端连接。所有请求为了等待服务都需要排队，在服务繁忙时，这个队伍可能会排的很长，一些客户端可能会感到不开心。这是轮询服务器 webserver3a.py 的代码：##################################################################### # 轮询服务器 - webserver3a.py # # # # 使用 Python 2.7.9 或 3.4 # # 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过 # ##################################################################### import socket SERVER_ADDRESS = (HOST, PORT) = '', 8888 REQUEST_QUEUE_SIZE = 5 def handle_request(client_connection): request = client_connection.recv(1024) print(request.decode()) http_response = b"""\ HTTP/1.1 200 OK Hello, World! """ client_connection.sendall(http_response) def serve_forever(): listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) listen_socket.bind(SERVER_ADDRESS) listen_socket.listen(REQUEST_QUEUE_SIZE) print('Serving HTTP on port {port} ...'.format(port=PORT)) while True: client_connection, client_address = listen_socket.accept() handle_request(client_connection) client_connection.close() if __name__ == '__main__': serve_forever() 为了观察到你的服务器在同一时间只能处理一个请求的行为，我们对服务器的代码做一点点修改：在将响应发送至客户端之后，将程序阻塞 60 秒。这个修改只需要一行代码，来告诉服务器进程暂停 60 秒钟。这是我们更改后的代码，包含暂停语句的服务器 webserver3b.py：###################################################################### # 轮询服务器 - webserver3b.py # # # # 使用 Python 2.7.9 或 3.4 # # 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过 # # # # - 服务器向客户端发送响应之后，会阻塞 60 秒 # ###################################################################### import socket import time SERVER_ADDRESS = (HOST, PORT) = '', 8888 REQUEST_QUEUE_SIZE = 5 def handle_request(client_connection): request = client_connection.recv(1024) print(request.decode()) http_…

搭个 Web 服务器（二）
type: item
date: 2016-08-16T17:33:00+08:00
description: 在第一部分中，我提出了一个问题：“如何在你刚刚搭建起来的 Web 服务器上适配 Django, Flask 或 Pyramid 应用，而不用单独对 Web 服务器做做出改动以适应各种不同的 Web 框架呢？”我们可以从这一篇中找到答案。曾几何时，你所选择的 Python Web 框架会限制你所可选择的 Web 服务器，反之亦然。如果某个框架及服务器设计用来协同工作的，那么一切正常：但你可能正面对着（或者曾经面对过）尝试将一对无法适配的框架和服务器搭配在一起的问题：基本上，你需要选择那些能够一起工作的框架和服务器，而不能选择你想用的那些。所以，你该如何确保在不对 Web 服务器或框架的代码做任何更改的情况下，让你的 Web 服务器和多个不同的 Web 框架一同工作呢？这个问题的答案，就是 Python Web 服务器网关接口 （ Web Server Gateway Interface ） （缩写为 WSGI，念做“wizgy”）。WSGI 允许开发者互不干扰地选择 Web 框架及 Web 服务器的类型。现在，你可以真正将 Web 服务器及框架任意搭配，然后选出你最中意的那对组合。比如，你可以使用 Django，Flask 或者 Pyramid，与 Gunicorn，Nginx/uWSGI 或 Waitress 进行结合。感谢 WSGI 同时对服务器与框架的支持，我们可以真正随意选择它们的搭配了。所以，WSGI 就是我在第一部分中提出，又在本文开头重复了一遍的那个问题的答案。你的 Web 服务器必须实现 WSGI 接口的服务器部分，而现代的 Python Web 框架均已实现了 WSGI 接口的框架部分，这使得你可以直接在 Web 服务器中使用任意框架，而不需要更改任何服务器代码，以对特定的 Web 框架实现兼容。现在，你已经知道 Web 服务器及 Web 框架对 WSGI 的支持使得你可以选择最合适的一对来使用，而且它也有利于服务器和框架的开发者，这样他们只需专注于其擅长的部分来进行开发，而不需要触及另一部分的代码。其它语言也拥有类似的接口，比如：Java 拥有 Servlet API，而 Ruby 拥有 Rack。这些理论都不错，但是我打赌你在说：“Show me the code!” 那好，我们来看看下面这个很小的 WSGI 服务器实现：### 使用 Python 2.7.9，在 Linux 及 Mac OS X 下测试通过 import socket import StringIO import sys class WSGIServer(object): address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 1 def __init__(self, server_address): ### 创建一个监听的套接字 self.listen_socket = listen_socket = socket.socket( self.address_family, self.socket_type ) ### 允许复用同一地址 listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ### 绑定地址 listen_socket.bind(server_address) ### 激活套接字 listen_socket.listen(self.request_queue_size) ### 获取主机的名称及端口 host, port = self.listen_socket.getsockname()[:2] self.server_name = socket.getfqdn(host) self.server_port = port ### 返回由 Web 框架/应用设定的响应头部字段 self.headers_set = [] def set_app(self, application): self.application = application def serve_forever(self): listen_socket = self.listen_socket while True: ### 获取新的客户端连接 self.client_connection, client_address = listen_socket.accept() ### 处理一条请求后关闭连接，然后循环等待另一个连接建立 self.handle_one_request() def handle_one_request(self): self.reques…

搭个 Web 服务器（一）
type: item
date: 2016-08-09T04:51:00+08:00
description: 一天，有一个正在散步的妇人恰好路过一个建筑工地，看到三个正在工作的工人。她问第一个人：“你在做什么？”第一个人没好气地喊道：“你没看到我在砌砖吗？”妇人对这个答案不满意，于是问第二个人：“你在做什么？”第二个人回答说：“我在建一堵砖墙。”说完，他转向第一个人，跟他说：“嗨，你把墙砌过头了。去把刚刚那块砖弄下来！”然而，妇人对这个答案依然不满意，于是又问了第三个人相同的问题。第三个人仰头看着天，对她说：“我在建造世界上最大的教堂。”当他回答时，第一个人和第二个人在为刚刚砌错的砖而争吵。他转向那两个人，说：“不用管那块砖了。这堵墙在室内，它会被水泥填平，没人会看见它的。去砌下一层吧。”这个故事告诉我们：如果你能够理解整个系统的构造，了解系统的各个部件如何相互结合（如砖、墙还有整个教堂），你就能够更快地定位及修复问题（那块砌错的砖）。如果你想从头开始创造一个 Web 服务器，那么你需要做些什么呢？我相信，如果你想成为一个更好的开发者，你必须对日常使用的软件系统的内部结构有更深的理解，包括编程语言、编译器与解释器、数据库及操作系统、Web 服务器及 Web 框架。而且，为了更好更深入地理解这些系统，你必须从头开始，用一砖一瓦来重新构建这个系统。荀子曾经用这几句话来表达这种思想：“ 不闻不若闻之。 （ I hear and I forget. ） ”“ 闻之不若见之。 （ I see and I remember. ） ”“ 知之不若行之。 （ I do and I understand. ） ”我希望你现在能够意识到，重新建造一个软件系统来了解它的工作方式是一个好主意。在这个由三篇文章组成的系列中，我将会教你构建你自己的 Web 服务器。我们开始吧~先说首要问题：Web 服务器是什么？简而言之，它是一个运行在一个物理服务器上的网络服务器（啊呀，服务器套服务器），等待客户端向其发送请求。当它接收请求后，会生成一个响应，并回送至客户端。客户端和服务端之间通过 HTTP 协议来实现相互交流。客户端可以是你的浏览器，也可以是使用 HTTP 协议的其它任何软件。最简单的 Web 服务器实现应该是什么样的呢？这里我给出我的实现。这个例子由 Python 写成，即使你没听说过 Python（它是一门超级容易上手的语言，快去试试看！），你也应该能够从代码及注释中理解其中的理念：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 'Serving HTTP on port %s ...' % PORT while True: client_connection, client_address = listen_socket.accept() request = client_connection.recv(1024) print request http_response = """\ 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 … 现在，在你的网页浏览器的地址栏中输入 URL：http://localhost:8888/hello ，敲一下回车，然后来见证奇迹。你应该看到“Hello, World!”显示在你的浏览器中，就像下图那样：说真的，快去试一试。你做实验的时候，我会等着你的。完成了？不错！现在我们来讨论一下它实际上是怎么工作的。首先我们从你刚刚输入的 Web 地址开始。它叫 URL，这是它的基本结构：URL 是一个 Web 服务器的地址，浏览器用这个地址来寻找并连接 Web 服务器，并将上面的内容返回给你。在你的浏览器能够发送 HTTP 请求之前，它需要与 Web 服务器建立一个 TCP 连接。然后会在 TCP 连接中发送 HTTP 请求，并等待服务器返回 HTTP 响应。当你的浏览器收到响应后，就会显示其内容，在上面的例子中，它显示了“Hello, World…