我在
GitHub
上开源了这个系列文章的翻译版(还在跟进原作者的更新)欢迎 PR。
📖 📖 📖
原文:《
Let’s Build A Web Server. Part 2.
》
还记得,在
Part 1
中,我问了一个问题:“如何在新创建的 Web 服务器下运行 Django、Flask 和 Pyramid 这些 Web 框架,而无需对我们的 Web 服务器进行对应的更改?”, 继续阅读,找出答案。
很久以前,我们选择的 Python Web 框架会限制我们对 Web 服务器的选择,反之亦然。 如果框架和服务器本就相互兼容,那么没有任何问题:
但是当我们将服务器和并非兼容的框架组合在一起时,可能会遇到以下问题(也许你曾经遇到过):
所以,以前基本上我们必须使用相互兼容的服务器和框架,而不是我们想要使用就能用的。
那么,我们如何确保可以运行具有多个 Web 框架的 Web 服务器,而无需对 Web 服务器或 Web 框架进行代码更改?而这个问题的答案就在于 Python
Web 服务器网关接口
(简称
WSGI
,发音为 \wizgy)。
WSGI 允许开发人员将 Web 框架的选择与 Web 服务器的选择分开。现在,我们可以放心地随便选择 Web 服务器和 Web 框架。例如,可以使用 Gunicorn 或 Nginx / uWSGI 或 Waitress 来运行 Django,Flask 或 Pyramid。我们全都能要,得益于在服务器和框架之间的 WSGI 支持:
因此,WSGI 是在
Part 1
末尾及本文开头提出的问题的答案。 你的 Web 服务器必须实现 WSGI 接口的服务器部分,并且所有现代 Python Web 框架都已实现了 WSGI 接口的框架端,这就允许我们将这些框架与 Web 服务器一起使用,而无需修改服务器的代码去适应特定的 Web 框架。
这对服务器和框架开发人员是有益的,因为他们可以专注于他们擅长的专业领域而不是彼此手忙脚乱。其他语言也有类似的接口:例如,Java 有 Servlet API,Ruby 有 Rack。
现在自我感觉良好,但我打赌你会说:“Show me the code!”。好的,看看这个非常简约的 WSGI 服务器实现:
import io
import socket
import sys
class WSGIServer:
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
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):
request_data = self.client_connection.recv(1024)
self.request_data = request_data = request_data.decode('utf-8')
print(''.join(
f'< {line}\n' for line in request_data.splitlines()
self.parse_request(request_data)
env = self.get_environ()
result = self.application(env, self.start_response)
self.finish_response(result)
def parse_request(self, text):
request_line = text.splitlines()[0]
request_line = request_line.rstrip('\r\n')
(self.request_method,
self.path,
self.request_version
) = request_line.split()
def get_environ(self):
env = {}
env['wsgi.version'] = (1, 0)
env['wsgi.url_scheme'] = 'http'
env['wsgi.input'] = io.StringIO(self.request_data)
env['wsgi.errors'] = sys.stderr
env['wsgi.multithread'] = False
env['wsgi.multiprocess'] = False
env['wsgi.run_once'] = False
env['REQUEST_METHOD'] = self.request_method
env['PATH_INFO'] = self.path
env['SERVER_NAME'] = self.server_name
env['SERVER_PORT'] = str(self.server_port)
return env
def start_response(self, status, response_headers, exc_info=None):
server_headers = [
('Date', 'Mon, 15 Jul 2019 5:54:48 GMT'),
('Server', 'WSGIServer 0.2'),
self.headers_set = [status, response_headers + server_headers]
return self.finish_response
def finish_response(self, result):
try:
status, response_headers = self.headers_set
response = f'HTTP/1.1 {status}\r\n'
for header in response_headers:
response += '{0}: {1}\r\n'.format(*header)
response += '\r\n'
for data in result:
response += data.decode('utf-8')
print(''.join(
f'> {line}\n' for line in response.splitlines()
response_bytes = response.encode()
self.client_connection.sendall(response_bytes)
finally:
self.client_connection.close()
SERVER_ADDRESS = (HOST, PORT) = '127.0.0.1', 8888
def make_server(server_address, application):
server = WSGIServer(server_address)
server.set_app(application)
return server
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit('Provide a WSGI application object as module:callable')
app_path = sys.argv[1]
module, application = app_path.split(':')
module = __import__(module)
application = getattr(module, application)
httpd = make_server(SERVER_ADDRESS, application)
print(f'WSGIServer: Serving HTTP on port {PORT} ...\n')
httpd.serve_forever()
这肯定比 Part 1 中的服务器代码更大,但也足够小(不到150行),你可以理解而不会被细节困扰。上面的服务器也能做到更多——它可以运行用你心爱的 Web 框架编写的基本 Web 应用程序,无论是 Pyramid,Flask,Django 还是其他一些 Python WSGI 框架。
不相信我?试一试,亲身体会吧。 将上述代码保存为 webserver2.py
或直接从 GitHub 下载。 如果你试图在没有任何参数的情况下运行它,它会报错并退出。
$ python webserver2.py
Provide a WSGI application object as module:callable
这个程序需要为你的 Web 应用程序提供服务,而不是直接运行自己,这样才会变得有趣。要运行服务器,你唯一需要安装的就是 Python(确切地说是Python 3.7+)。 但要运行使用 Pyramid,Flask 和 Django 编写的应用程序,你需要先安装这些框架。让我们安装所有这三个。我首选的方法是使用 venv(默认情况下在Python 3.3及更高版本中可用)。只需按照以下步骤创建并激活虚拟环境,然后安装这三个 Web 框架。
译者注:先创建项目目录,再在目录中创建虚拟环境
$ mkdir part2
$ cd part2
译者注:请注意,在一些操作系统中,你可能需要在上面的命令中使用 python 而不是 python3 。
$ python -m venv lsbaws
译者注:不管你用什么方法创建虚拟环境,创建完毕之后还需要激活才能够进入这个虚拟环境。 要激活你的全新虚拟环境,需使用以下命令:(以下是 Microsoft Windows 命令提示符窗口)
$ lsbaws\Scripts\activate
(lsbaws) $ _
译者注:激活一个虚拟环境,终端会话的环境配置就会被修改,之后你键入 python
的时候,实际上是调用的虚拟环境中的 Python 解释器。 此外,终端提示符也被修改成包含被激活的虚拟环境的名称的格式。这种激活是临时的和私有的,因此在关闭终端窗口时它们将不会保留,也不会影响其他的会话。 那么,当你需要同时打开多个终端窗口来调试不同的应用时,每个终端窗口都可以激活不同的虚拟环境而不会相互影响。
成功创建和激活了虚拟环境之后,你可以安装那三个框架了,命令如下:
(lsbaws) $ pip install -U pip
(lsbaws) $ pip install pyramid
(lsbaws) $ pip install flask
(lsbaws) $ pip install django
译者注:我在 Windows10 命令行安装 pip 时遇到文件权限限制,修改整个 anaconda3 的文件权限无果,
便直接在命令行中加入--user
:pip install --user -U pip
成功安装 pip-19。
原文作者电脑系统是 MacOS,创建虚拟环境、激活虚拟环境以及安装的过程如下:
$ python3 -m venv lsbaws
$ ls lsbaws
bin include lib pyvenv.cfg
$ source lsbaws/bin/activate
(lsbaws) $ pip install -U pip
(lsbaws) $ pip install pyramid
(lsbaws) $ pip install flask
(lsbaws) $ pip install django
此时,你需要创建一个 Web 应用程序。 让我们先从 Pyramid 开始吧。 将以下代码保存为 pyramidapp.py
,放到你保存 webserver2.py
的同一目录下或直接从 GitHub 下载该文件:
译者注:这些文件最好都放在上面创建的目录中,即刚刚创建的虚拟环境 lsbaws
的同级目录下,否则要在命令行运行这些文件时就要带上它们的绝对路径才行。
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
return Response(
'Hello world from Pyramid!\n',
content_type='text/plain',
config = Configurator()
config.add_route('hello', '/hello')
config.add_view(hello_world, route_name='hello')
app = config.make_wsgi_app()
现在,我们已准备好使用自己的 Web 服务器为 Pyramid 应用程序提供服务:
(lsbaws) $ python webserver2.py pyramidapp:app
WSGIServer: Serving HTTP on port 8888 ...
译者注:可以注意到,要在激活的虚拟环境中运行上面的命令
我们刚刚告诉我们的服务器从 python 模块 ‘pyramidapp’ 加载可调用的 ‘app’ 。你的服务器现在已准备好接收请求并将它们转发到你的 Pyramid 应用程序。应用程序现在只处理一个路径:/hello
路由。在浏览器中键入 http://localhost:8888/hello
地址,按 Enter 键,然后观察结果:
你还可以使用 ‘curl’ 工具程序在另一个命令窗口上测试这个服务器:
$ curl -v http://localhost:8888/hello
查看服务器和 curl -v
打印到标准输出的内容。
译者注:这是我的运行结果:
将上面的代码保存为 flaskapp.py
或从 GitHub下载,然后运行服务器如下:
(lsbaws) $ python webserver2.py flaskapp:app
WSGIServer: Serving HTTP on port 8888 ...
现在在浏览器中键入 http://localhost:8888/hello
,然后按 Enter 键:
再次尝试 ‘curl’ 并亲自看看服务器返回 Flask 应用程序生成的消息:
$ curl -v http://localhost:8888/hello
译者注:这是我的运行结果:
服务器还可以处理 Django 应用程序吗? 试试看!但是,它有点复杂,我建议克隆整个 repo 并使用其中的 djangoapp.py
,它是 GitHub repository 的一部分。这是源代码,它基本上将Django ‘helloworld’ 项目(使用 Django 的 django-admin.py startproject
命令预先创建)添加到当前的 Python 路径,然后导入项目的 WSGI 应用程序。
import sys
sys.path.insert(0, './helloworld')
from helloworld import wsgi
app = wsgi.application
将上面的代码保存为 djangoapp.py
并使用 Web 服务器运行这个 Django 应用程序:
(lsbaws) $ python webserver2.py djangoapp:app
WSGIServer: Serving HTTP on port 8888 ...
输入以下网址,然后按 Enter 键:
就像我们之前已经做过的那几次,你也可以在命令行上进行测试,并确认这次是处理你的请求的 Django 应用程序:
$ curl -v http://localhost:8888/hello
译者注:服务器已经成功在 Windows10 下的命令行终端中挂起,但在 chrome 浏览器中访问 http://localhost:8888/hello
时出现 DisallowedHost at / Invalid HTTP_HOST header: 'DESKTOP-E61P8KD:8888'. You may need to add 'desktop-e61p8kd' to ALLOWED_HOSTS.
这样的错误。暂且搁置,待日后重提。
我希望你已经尝试过,并且确认我们的 WSGI 服务器能和这三个框架一起工作了。如果没有动手敲代码,那么请试一试。 阅读很重要,但这个系列是关于造轮子的,这意味着我们需要亲力亲为。去尝试吧,别担心。
至此,我们已经体验过 WSGI 的强大功能:它允许我们混合搭配 Web 服务器和 Web 框架。 WSGI 在 Web 服务器和框架之间提供了一个最小的接口。它非常简单,并且很容易在服务器端和框架端实现。以下代码段显示了接口的服务器端和框架端:
def run_application(application):
"""Server code."""
headers_set = []
environ = {}
def start_response(status, response_headers, exc_info=None):
headers_set[:] = [status, response_headers]
result = application(environ, start_response)
...
def app(environ, start_response):
"""A barebones WSGI app."""
start_response('200 OK', [('Content-Type', 'text/plain')])
return [b'Hello world!']
run_application(app)
以下是它的运行方式:
- 框架提供了一个可调用的 “application”(WSGI 规范没有规定应该如何实现)
- 服务器为从 HTTP 客户端接收的每个请求调用 ‘application’ 。 它传递一个包含 WSGI / CGI 变量的字典 ‘environ’ 和一个可调用 ‘start_response’ 作为 ‘application’ 的参数。
- 框架 / 应用程序生成 HTTP 状态和 HTTP 响应标头,并将它们传递给可调用的 “start_response”,以便服务器存储它们。框架 / 应用程序还返回响应主体。
- 服务器将状态、响应头和响应主体组合成 HTTP 响应并将其发送到客户端(此步骤不是规范的一部分,但它是流程中的下一个逻辑步骤,为了清楚起见我添加进来)
以下是这个接口的直观展示:
到目前为止,你已经看过 Pyramid、Flask 和 Django Web 应用程序,并且你已经看到了实现服务器端 WSGI 规范的服务器代码。你甚至已经看到了不使用任何框架的非正式 WSGI 应用程序代码片段。
问题在于,当你使用其中一个框架编写 Web 应用程序时,你在更高级别的层次工作而不是直接使用 WSGI,但我知道你对 WSGI 接口的框架方面也很好奇,因为你在读这篇文章。因此,让我们创建一个简约的 WSGI Web 应用程序 / Web 框架,而不使用 Pyramid、Flask 或 Django,并在你的服务器上运行它:
def app(environ, start_response):
"""A barebones WSGI application.
This is a starting point for your own Web framework :)
status = '200 OK'
response_headers = [('Content-Type', 'text/plain')]
start_response(status, response_headers)
return [b'Hello world from a simple WSGI application!\n']
再次,将上述代码保存成 wsgiapp.py
文件或直接从 GitHub 下载并在 Web 服务器下运行应用程序:
(lsbaws) $ python webserver2.py wsgiapp:app
WSGIServer: Serving HTTP on port 8888 ...
输入以下网址,然后按 Enter 键。 你应该看到这样的结果:
在学习如何创建 Web 服务器的同时,你刚刚编写了自己的简约 WSGI Web 框架!不得了啊。
现在,让我们回到服务器传输给客户端的内容。 以下是使用 HTTP 客户端调用 Pyramid 应用程序时服务器生成的 HTTP 响应:
响应中有一些你在 Part 1 中看到的熟悉部分,但它也有一些新的东西。例如,它有四个你以前没有见过的 HTTP 标头:Content-Type,Content-Length,Date 和 Server。 这些是来自 Web 服务器的响应通常应该具有的标头。但是,没有一个是严格要求的。标头的目的是传输有关 HTTP 请求 / 响应的其他信息。
现在你已经了解了有关 WSGI 接口的更多信息,以下是同样的 HTTP 响应,其中包含有关生成它的部件的更多信息:
我还没有提到过 ‘environ’ 字典,但基本上它是一个 Python 字典,必须包含 WSGI 规范规定的某些 WSGI 和 CGI 参数变量。解析请求后,服务器从 HTTP 请求中获取字典的值。这就是字典的内容:
Web 框架使用来自该字典的信息来决定指定的路由、请求方法等使用哪个视图,从哪里读取请求主体以及在何处写入错误(如果有的话)。
到目前为止,我们已经创建了自己的 WSGI Web 服务器,并且已经使用不同的 Web 框架编写了 Web 应用程序。而且还在此过程中创建了我们的准系统 Web 应用程序 / Web 框架。这是一段艰难的旅程。让我们回顾一下 WSGI Web 服务器必须做什么来提供针对 WSGI 应用程序的请求:
- 首先,服务器启动并加载由 Web 框架 / 应用程序提供的可调用 “application”
- 然后,服务器读取一个请求
- 接着,服务器解析这个请求
- 再者,然后,服务器使用请求数据构建 “environ” 字典
- 然后,服务器调用可调用的 “application”、“environ” 字典和可调用的 “start_response” 作为参数并返回响应主体。
- 接着,服务器使用可调用 ‘application’ 对象返回的数据以及 ‘start_response’ 设置的状态和响应头来构造 HTTP 响应。
- 最后,服务器将这个 HTTP 响应返回给客户端
这就是 WSGI 的全部内容。你现在拥有一个可用的 WSGI 服务器,可以为使用 WSGI 兼容的 Web 框架(如 Django,Flask,Pyramid 或你自己的 WSGI 框架)编写的基本 Web 应用程序提供服务。最好的一点就是服务器可以与多个 Web 框架一起使用,而无需对服务器代码库进行任何更改。简直漂亮。
在继续之前,这是另一个你可以考虑一下的问题:“你如何让你的服务器一次处理多个请求(并发)?”
请继续关注,我将在 Part 3 中向你展示完成并发的一种方法。🍻!
UPDATE: Mon, July 15, 2019
- Updated the server code to run under Python 3.7+
- Added resources used in preparation for the article
此系列的所有文章(已翻译):
原文链接:https://ruslanspivak.com/lsbaws-part2/
交互式命令行界面(Cisco风格)的仅跨平台标头的C ++ 14库
重要说明:重大的API更改cli 2.0版进行了重大更改,以添加更多功能。 要将您的应用程序迁移到新的cli版本,请参见此文件的“异步编程和调度程序”部分,或该库随附的示例。
跨平台(Linux和Windows)
菜单和子菜单
远程会话(远程登录)
永久历史记录(使用箭头键导航)
自动补全(使用TAB键)
如何获取CLI库
如果您不需要远程会话,则库没有依赖项。
该库取决于ASIO(无论是单机版或升压版本)只提供telnet服务器(即远程会话)。
该库是仅标头的:它完全由包含模板和内联函数的标头文件组成,并且在链接时不需要单独编译的库二进制文件或特殊处理。
将档案解压缩到所需的任何位置。
现在,您必须仅记住在编译源代码时指定cli(以
1.WSGI接口了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是:浏览器发送一个HTTP请求;服务器收到请求,生成一个HTML文档;服务器把HTML文档作为HTTP响应的Body发送给浏览器;浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取H...
出差几日,终于回来继续学习。
话说Python好用,主要在于其自带的模块都很实用,犹如一把瑞士军刀,轻便、锋利又功能齐全。一直想找一个框架来实现我的命令行调试小工具,发现还真有,而且居然是Python的标准模块,这就是cmd模块。
这是一种通用的C++命令行UI系统,包括了页面管理与UI管理系统。可以简单地在框架内使用最少4个用户接口函数即可绘制C++命令行UI,并进行函数的调用机制。
对于懒人和新手写简单的交互程序而言,这再方便不过了。
参考FrmSrc与使用例,复制Frm
1 WSGI
WSGI是 Web Server Gateway Interface 的缩写,它是 Python应用程序(application)或框架(如 Django)和 Web服务器之间的一种接口,已经被广泛接受。
它是一种协议,一种规范,其是在 PEP 333提出的,并在 PEP 3333 进行补充(主要是为了支持 Python3.x)。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件。WSG