一起写一个 Web 服务器
2016-07-26 19:16
295 查看
http://my.oschina.net/leejun2005/blog/486771
2015/06/06 · 实践项目 · 9
评论 · Web服务器
分享到:8
本文由 伯乐在线 - 高世界 翻译,艾凌风 校稿。未经许可,禁止转载!
英文出处:ruslan spivak。欢迎加入翻译组。
还记得吗?在本系列第一部分我问过你:“怎样在你的刚完成的WEB服务器下运行 Django 应用、Flask 应用和 Pyramid 应用?在不单独修改服务器来适应这些不同的WEB框架的情况下。”往下看,来找出答案。
过去,你所选择的一个Python Web框架会限制你选择可用的Web服务器,反之亦然。如果框架和服务器设计的是可以一起工作的,那就很好:
但是,当你试着结合没有设计成可以一起工作的服务器和框架时,你可能要面对(可能你已经面对了)下面这种问题:
基本上,你只能用可以在一起工作的部分,而不是你想用的部分。
那么,怎样确保在不修改Web服务器和Web框架下,用你的Web服务器运行不同的Web框架?答案就是Python Web服务器网关接口(或者缩写为WSGI,读作“wizgy”)。
WSGI允许开发者把框架的选择和服务器的选择分开。现在你可以真正地混合、匹配Web服务器和Web框架了。例如,你可以在Gunicorn或者Nginx/uWSGI或者Waitress上面运行Django,Flask,或Pyramid。真正的混合和匹配哟,感谢WSGI服务器和框架两者都支持:
就这样,WSGI成了我在本系列第一部分和本文开头重复问的问题的答案。你的Web服务器必须实现WSGI接口的服务器端,所有的现代Python Web框架已经实现 了WSGI接口的框架端了,这就让你可以不用修改服务器代码,适应某个框架。
现在你了解了Web服务器和WEb框架支持的WSGI允许你选择一对儿合适的(服务器和框架),它对服务器和框架的开发者也有益,因为他们可以专注于他们特定的领域,而不是越俎代庖。其他语言也有相似的接口:例如,Java有Servlet API,Ruby有Rack。
一切都还不错,但我打赌你会说:“秀代码给我看!” 好吧,看看这个漂亮且简约的WSGI服务器实现
#
Tested with 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):
#
Create a listening socket
self.listen_socket
=
listen_socket
=
socket.socket(
self.address_family,
self.socket_type
)
#
Allow to reuse the same address
listen_socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR,
1)
#
Bind
listen_socket.bind(server_address)
#
Activate
listen_socket.listen(self.request_queue_size)
#
Get server host name and port
host,
port
=
self.listen_socket.getsockname()[:2]
self.server_name
=
socket.getfqdn(host)
self.server_port
=
port
#
Return headers set by Web framework/Web application
self.headers_set
=
[]
def
set_app(self,
application):
self.application
=
application
def
serve_forever(self):
listen_socket
=
self.listen_socket
while
True:
#
New client connection
self.client_connection,
client_address
=
listen_socket.accept()
#
Handle one request and close the client connection. Then
#
loop over to wait for another client connection
self.handle_one_request()
def
handle_one_request(self):
self.request_data
=
request_data
=
self.client_connection.recv(1024)
#
Print formatted request data a la 'curl -v'
print(''.join(
'<
{line}\n'.format(line=line)
for
line
in
request_data.splitlines()
))
self.parse_request(request_data)
#
Construct environment dictionary using request data
env
=
self.get_environ()
#
It's time to call our application callable and get
#
back a result that will become HTTP response body
result
=
self.application(env,
self.start_response)
#
Construct a response and send it back to the client
self.finish_response(result)
def
parse_request(self,
text):
request_line
=
text.splitlines()[0]
request_line
=
request_line.rstrip('\r\n')
#
Break down the request line into components
(self.request_method, #
GET
self.path, #
/hello
self.request_version #
HTTP/1.1
)
=
request_line.split()
def
get_environ(self):
env
=
{}
#
The following code snippet does not follow PEP8 conventions
#
but it's formatted the way it is for demonstration purposes
#
to emphasize the required variables and their values
#
#
Required WSGI variables
env['wsgi.version'] =
(1,
0)
env['wsgi.url_scheme']
=
'http'
env['wsgi.input'] =
StringIO.StringIO(self.request_data)
env['wsgi.errors']
=
sys.stderr
env['wsgi.multithread'] =
False
env['wsgi.multiprocess']
=
False
env['wsgi.run_once']
=
False
#
Required CGI variables
env['REQUEST_METHOD'] =
self.request_method #
GET
env['PATH_INFO']
=
self.path #
/hello
env['SERVER_NAME']
=
self.server_name
#
localhost
env['SERVER_PORT']
=
str(self.server_port) #
8888
return
env
def
start_response(self,
status,
response_headers,
exc_info=None):
#
Add necessary server headers
server_headers
=
[
('Date',
'Tue, 31 Mar 2015 12:54:48
GMT'),
('Server',
'WSGIServer 0.2'),
]
self.headers_set
=
[status,
response_headers
+
server_headers]
#
To adhere to WSGI specification the start_response must return
#
a 'write' callable. We simplicity's sake we'll ignore that detail
#
for now.
#
return self.finish_response
def
finish_response(self,
result):
try:
status,
response_headers
=
self.headers_set
response
=
'HTTP/1.1 {status}\r\n'.format(status=status)
for
header
in
response_headers:
response
+=
'{0}: {1}\r\n'.format(*header)
response
+=
'\r\n'
for
data
in
result:
response
+=
data
#
Print formatted response data a la 'curl -v'
print(''.join(
'>
{line}\n'.format(line=line)
for
line
in
response.splitlines()
))
self.client_connection.sendall(response)
finally:
self.client_connection.close()
SERVER_ADDRESS
=
(HOST,
PORT)
=
'',
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('WSGIServer:
Serving HTTP on port {port} ...\n'.format(port=PORT))
httpd.serve_forever()
它明显比本系列第一部分中的服务器代码大,但为了方便你理解,而不陷入具体细节,它也足够小了(只有150行不到)。上面的服务器还做了别的事 – 它可以运行你喜欢的Web框架写的基本的Web应用,可以是Pyramid,Flask,Django,或者其他的Python WSGI框架。
不信?自己试试看。把上面的代码保存成webserver2.py或者直接从Github上下载。如果你不带参数地直接运行它,它就会报怨然后退出。
Python
它真的想给Web框架提供服务,从这开始有趣起来。要运行服务器你唯一需要做的是安装Python。但是要运行使用Pyramid,Flask,和Django写的应用,你得先安装这些框架。一起安装这三个吧。我比较喜欢使用virtualenv。跟着以下步骤来创建和激活一个虚拟环境,然后安装这三个Web框架。Python
此时你需要创建一个Web应用。我们先拿Pyramid开始吧。保存以下代码到保存webserver2.py时相同的目录。命名为pyramidapp.py。或者直接从Github上下载:
Python
现在你已经准备好用完全属于自己的Web服务器来运行Pyramid应用了:Python
刚才你告诉你的服务器从python模块‘pyramidapp’中加载可调用的‘app’,现在你的服务器准备好了接受请求然后转发它们给你的Pyramid应用。目前应用只处理一个路由:/hello 路由。在浏览器里输入http://localhost:8888/hello地址,按回车键,观察结果:
你也可以在命令行下使用‘curl’工具来测试服务器:
Python
检查服务器和curl输出了什么到标准输出。现在弄Flask。按照相同的步骤。Python
保存以上代码为flaskapp.py或者从Github上下载它。然后像这样运行服务器:
Python
现在在浏览器里输入http://localhost:8888/hello然后按回车:
再一次,试试‘curl’,看看服务器返回了一条Flask应用产生的消息:Python
服务器也能处理Django应用吗?试试吧!尽管这有点复杂,但我还是推荐克隆整个仓库,然后使用djangoapp.py,它是GitHub仓库的一部分。以下的源码,简单地把Django ‘helloworld’ 工程(使用Django的django-admin.py启动项目预创建的)添加到当前Python路径,然后导入了工程的WSGI应用。
Python
把以上代码保存为djangoapp.py,然后用你的Web服务器运行Django应用:Python
输入下面的地址,然后按回车键:
虽然你已经做过两次啦,你还是可以再在命令行测试一下,确认一下,这次是Django应用处理了请求。
Python
你试了吧?你确定服务器可以和这三个框架一起工作吧?如果没试,请试一下。阅读挺重要,但这个系列是关于重建的,也就是说,你要自己动手。去动手试试吧。别担心,我等你哟。你必须试下,最好呢,你亲自输入所有的东西,确保它工作起来像你期望的那样。很好,你已经体验到了WSGI的强大:它可以让你把Web服务器和Web框架结合起来。WSGI提供了Python Web服务器和Python Web框架之间的一个最小接口。它非常简单,在服务器和框架端都可以轻易实现。下面的代码片段展示了(WSGI)接口的服务器和框架端:Python
以下是它如何工作的:
1.框架提供一个可调用的’应用’(WSGI规格并没有要求如何实现)
2.服务器每次接收到HTTP客户端请求后,执行可调用的’应用’。服务器把一个包含了WSGI/CGI变量的字典和一个可调用的’start_response’做为参数给可调用的’application’。
3.框架/应用生成HTTP状态和HTTP响应头,然后把它们传给可调用的’start_response’,让服务器保存它们。框架/应用也返回一个响应体。
4.服务器把状态,响应头,响应体合并到HTTP响应里,然后传给(HTTP)客户端(这步不是(WSGI)规格里的一部分,但它是后面流程中的一步,为了解释清楚我加上了这步)
以下是接口的视觉描述:
目前为止,你已经了解了Pyramid,Flask,和Django Web应用,你还了解了实现了WSGI规范服务器端的服务器代码。你甚至已经知道了不使用任何框架的基本的WSGI应用代码片段。
问题就在于,当你使用这些框架中的一个来写Web应用时,你站在一个比较高的层次,并不直接和WSGI打交道,但我知道你对WSGI接口的框架端好奇,因为你在读本文。所以,咱们一起写个极简的WSGI Web应用/Web框架吧,不用Pyramid,Flask,或者Django,然后用你的服务器运行它:
Python
再次,保存以上代码到wsgiapp.py文件,或者直接从GitHub上下载,然后像下面这样使用你的Web服务器运行应用:Python
输入下面地址,敲回车。你应该就看到下面结果了:
在你学习怎样写一个Web服务器时,你刚刚写了一个你自己的极简的WSGI Web框架!棒极啦。
现在,让我们回头看看服务器传输了什么给客户端。以下就是使用HTTP客户端调用Pyramid应用时生成的HTTP响应:
这个响应跟你在本系列第一部分看到的有一些相近的部分,但也有一些新东西。例如,你以前没见过的4个HTTP头:Content-Type, Content-Length, Date, 和Servedr。这些头是Web服务器生成的响应应该有的。虽然他们并不是必须的。头的目的传输HTTP请求/响应的额外信息。
现在你对WSGI接口了解的更多啦,同样,以下是带有更多信息的HTTP响应,这些信息表示了哪些部件产生的它(响应):
我还没有介绍’environ’字典呢,但它基本上就是一个Python字典,必须包含WSGI规范规定的必要的WSGI和CGI变量。服务器在解析请求后,从HTTP请求拿到了字典的值,字典的内容看起来像下面这样:
Web框架使用字典里的信息来决定使用哪个视图,基于指定的路由,请求方法等,从哪里读请求体,错误写到哪里去,如果有的话。
现在你已经创建了你自己的WSGI Web服务器,使用不同的Web框架写Web应用。还有,你还顺手写了个简单的Web应用/Web框架。真是段难忘的旅程。咱们简要重述下WSGI Web服务器必须做哪些工作才能处理发给WSGI应用的请求吧:
首先,服务器启动并加载一个由Web框架/应用提供的可调用的’application’
然后,服务器读取请求
然后,服务器解析它
然后,服务器使用请求的数据创建了一个’environ’字典
然后,服务器使用’environ’字典和’start_response’做为参数调用’application’,并拿到返回的响应体。
然后,服务器使用调用’application’返回的数据,由’start_response’设置的状态和响应头,来构造HTTP响应。
最终,服务器把HTTP响应传回给户端。
这就是全部啦。现在你有了一个可工作的WSGI服务器,它可以处理使用像Django,Flask,Pyramid或者 你自己的WSGI框架这样的兼容WSGI的Web框架写的基本的Web应用。最优秀的地方是,服务器可以在不修改代码的情况下,使用不同的Web框架。
在你离开之前,还有个问题请你想一下,“该怎么做才能让服务器同一时间处理多个请求呢?”
保持关注,我会在本系列第三部分秀给你看实现它的一种方式。欢呼!
顺便说下,我在写一本书《一起构建WEB服务器:第一步》,它解释了从零开始写一个基本的WEB服务器,还更详细地讲解了我上面提到的话题。订阅邮件组来获取关于书籍和发布时间和最近更新。
一起写一个 Web 服务器(2)
2015/06/06 · 实践项目 · 9评论 · Web服务器
分享到:8
本文由 伯乐在线 - 高世界 翻译,艾凌风 校稿。未经许可,禁止转载!
英文出处:ruslan spivak。欢迎加入翻译组。
还记得吗?在本系列第一部分我问过你:“怎样在你的刚完成的WEB服务器下运行 Django 应用、Flask 应用和 Pyramid 应用?在不单独修改服务器来适应这些不同的WEB框架的情况下。”往下看,来找出答案。
过去,你所选择的一个Python Web框架会限制你选择可用的Web服务器,反之亦然。如果框架和服务器设计的是可以一起工作的,那就很好:
但是,当你试着结合没有设计成可以一起工作的服务器和框架时,你可能要面对(可能你已经面对了)下面这种问题:
基本上,你只能用可以在一起工作的部分,而不是你想用的部分。
那么,怎样确保在不修改Web服务器和Web框架下,用你的Web服务器运行不同的Web框架?答案就是Python Web服务器网关接口(或者缩写为WSGI,读作“wizgy”)。
WSGI允许开发者把框架的选择和服务器的选择分开。现在你可以真正地混合、匹配Web服务器和Web框架了。例如,你可以在Gunicorn或者Nginx/uWSGI或者Waitress上面运行Django,Flask,或Pyramid。真正的混合和匹配哟,感谢WSGI服务器和框架两者都支持:
就这样,WSGI成了我在本系列第一部分和本文开头重复问的问题的答案。你的Web服务器必须实现WSGI接口的服务器端,所有的现代Python Web框架已经实现 了WSGI接口的框架端了,这就让你可以不用修改服务器代码,适应某个框架。
现在你了解了Web服务器和WEb框架支持的WSGI允许你选择一对儿合适的(服务器和框架),它对服务器和框架的开发者也有益,因为他们可以专注于他们特定的领域,而不是越俎代庖。其他语言也有相似的接口:例如,Java有Servlet API,Ruby有Rack。
一切都还不错,但我打赌你会说:“秀代码给我看!” 好吧,看看这个漂亮且简约的WSGI服务器实现
#
Tested with 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):
#
Create a listening socket
self.listen_socket
=
listen_socket
=
socket.socket(
self.address_family,
self.socket_type
)
#
Allow to reuse the same address
listen_socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR,
1)
#
Bind
listen_socket.bind(server_address)
#
Activate
listen_socket.listen(self.request_queue_size)
#
Get server host name and port
host,
port
=
self.listen_socket.getsockname()[:2]
self.server_name
=
socket.getfqdn(host)
self.server_port
=
port
#
Return headers set by Web framework/Web application
self.headers_set
=
[]
def
set_app(self,
application):
self.application
=
application
def
serve_forever(self):
listen_socket
=
self.listen_socket
while
True:
#
New client connection
self.client_connection,
client_address
=
listen_socket.accept()
#
Handle one request and close the client connection. Then
#
loop over to wait for another client connection
self.handle_one_request()
def
handle_one_request(self):
self.request_data
=
request_data
=
self.client_connection.recv(1024)
#
Print formatted request data a la 'curl -v'
print(''.join(
'<
{line}\n'.format(line=line)
for
line
in
request_data.splitlines()
))
self.parse_request(request_data)
#
Construct environment dictionary using request data
env
=
self.get_environ()
#
It's time to call our application callable and get
#
back a result that will become HTTP response body
result
=
self.application(env,
self.start_response)
#
Construct a response and send it back to the client
self.finish_response(result)
def
parse_request(self,
text):
request_line
=
text.splitlines()[0]
request_line
=
request_line.rstrip('\r\n')
#
Break down the request line into components
(self.request_method, #
GET
self.path, #
/hello
self.request_version #
HTTP/1.1
)
=
request_line.split()
def
get_environ(self):
env
=
{}
#
The following code snippet does not follow PEP8 conventions
#
but it's formatted the way it is for demonstration purposes
#
to emphasize the required variables and their values
#
#
Required WSGI variables
env['wsgi.version'] =
(1,
0)
env['wsgi.url_scheme']
=
'http'
env['wsgi.input'] =
StringIO.StringIO(self.request_data)
env['wsgi.errors']
=
sys.stderr
env['wsgi.multithread'] =
False
env['wsgi.multiprocess']
=
False
env['wsgi.run_once']
=
False
#
Required CGI variables
env['REQUEST_METHOD'] =
self.request_method #
GET
env['PATH_INFO']
=
self.path #
/hello
env['SERVER_NAME']
=
self.server_name
#
localhost
env['SERVER_PORT']
=
str(self.server_port) #
8888
return
env
def
start_response(self,
status,
response_headers,
exc_info=None):
#
Add necessary server headers
server_headers
=
[
('Date',
'Tue, 31 Mar 2015 12:54:48
GMT'),
('Server',
'WSGIServer 0.2'),
]
self.headers_set
=
[status,
response_headers
+
server_headers]
#
To adhere to WSGI specification the start_response must return
#
a 'write' callable. We simplicity's sake we'll ignore that detail
#
for now.
#
return self.finish_response
def
finish_response(self,
result):
try:
status,
response_headers
=
self.headers_set
response
=
'HTTP/1.1 {status}\r\n'.format(status=status)
for
header
in
response_headers:
response
+=
'{0}: {1}\r\n'.format(*header)
response
+=
'\r\n'
for
data
in
result:
response
+=
data
#
Print formatted response data a la 'curl -v'
print(''.join(
'>
{line}\n'.format(line=line)
for
line
in
response.splitlines()
))
self.client_connection.sendall(response)
finally:
self.client_connection.close()
SERVER_ADDRESS
=
(HOST,
PORT)
=
'',
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('WSGIServer:
Serving HTTP on port {port} ...\n'.format(port=PORT))
httpd.serve_forever()
它明显比本系列第一部分中的服务器代码大,但为了方便你理解,而不陷入具体细节,它也足够小了(只有150行不到)。上面的服务器还做了别的事 – 它可以运行你喜欢的Web框架写的基本的Web应用,可以是Pyramid,Flask,Django,或者其他的Python WSGI框架。
不信?自己试试看。把上面的代码保存成webserver2.py或者直接从Github上下载。如果你不带参数地直接运行它,它就会报怨然后退出。
Python
12 | $pythonwebserver2.pyProvideaWSGIapplicationobjectasmodule:callable |
1 2 3 4 5 6 7 8 9 10 | $ [sudo] pip install virtualenv $ mkdir ~/envs $ virtualenv ~/envs/lsbaws/ $ cd ~/envs/lsbaws/ $ ls bin include lib $ source bin/activate (lsbaws) $ pip install pyramid (lsbaws) $ pip install flask (lsbaws) $ pip install django |
Python
1234567891011121314 | frompyramid.configimportConfiguratorfrompyramid.responseimportResponse defhello_world(request): returnResponse( '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() |
1 2 | (lsbaws) $ python webserver2.py pyramidapp:app WSGIServer: Serving HTTP on port 8888 ... |
你也可以在命令行下使用‘curl’工具来测试服务器:
Python
12 | $curl-vhttp://localhost:8888/hello... |
1 2 3 4 5 6 7 8 9 10 11 12 13 | from flask import Flask from flask import Response flask_app = Flask('flaskapp') @flask_app.route('/hello') def hello_world(): return Response( 'Hello world from Flask!\n', mimetype='text/plain' ) app = flask_app.wsgi_app |
Python
12 | (lsbaws)$pythonwebserver2.pyflaskapp:appWSGIServer:ServingHTTPonport8888... |
再一次,试试‘curl’,看看服务器返回了一条Flask应用产生的消息:Python
1 2 | $ curl -v http://localhost:8888/hello ... |
Python
12345 | importsyssys.path.insert(0,'./helloworld')fromhelloworldimportwsgi app=wsgi.application |
1 2 | (lsbaws) $ python webserver2.py djangoapp:app WSGIServer: Serving HTTP on port 8888 ... |
虽然你已经做过两次啦,你还是可以再在命令行测试一下,确认一下,这次是Django应用处理了请求。
Python
12 | $curl-vhttp://localhost:8888/hello... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def run_application(application): """Server code.""" # This is where an application/framework stores # an HTTP status and HTTP response headers for the server # to transmit to the client headers_set = [] # Environment dictionary with WSGI/CGI variables environ = {} def start_response(status, response_headers, exc_info=None): headers_set[:] = [status, response_headers] # Server invokes the ‘application' callable and gets back the # response body result = application(environ, start_response) # Server builds an HTTP response and transmits it to the client … def app(environ, start_response): """A barebones WSGI app.""" start_response('200 OK', [('Content-Type', 'text/plain')]) return ['Hello world!'] run_application(app) |
1.框架提供一个可调用的’应用’(WSGI规格并没有要求如何实现)
2.服务器每次接收到HTTP客户端请求后,执行可调用的’应用’。服务器把一个包含了WSGI/CGI变量的字典和一个可调用的’start_response’做为参数给可调用的’application’。
3.框架/应用生成HTTP状态和HTTP响应头,然后把它们传给可调用的’start_response’,让服务器保存它们。框架/应用也返回一个响应体。
4.服务器把状态,响应头,响应体合并到HTTP响应里,然后传给(HTTP)客户端(这步不是(WSGI)规格里的一部分,但它是后面流程中的一步,为了解释清楚我加上了这步)
以下是接口的视觉描述:
目前为止,你已经了解了Pyramid,Flask,和Django Web应用,你还了解了实现了WSGI规范服务器端的服务器代码。你甚至已经知道了不使用任何框架的基本的WSGI应用代码片段。
问题就在于,当你使用这些框架中的一个来写Web应用时,你站在一个比较高的层次,并不直接和WSGI打交道,但我知道你对WSGI接口的框架端好奇,因为你在读本文。所以,咱们一起写个极简的WSGI Web应用/Web框架吧,不用Pyramid,Flask,或者Django,然后用你的服务器运行它:
Python
123456789 | defapp(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['Hello world from a simple WSGI application!\n'] |
1 2 | (lsbaws)$pythonwebserver2.pywsgiapp:app WSGIServer:ServingHTTPonport8888... |
在你学习怎样写一个Web服务器时,你刚刚写了一个你自己的极简的WSGI Web框架!棒极啦。
现在,让我们回头看看服务器传输了什么给客户端。以下就是使用HTTP客户端调用Pyramid应用时生成的HTTP响应:
这个响应跟你在本系列第一部分看到的有一些相近的部分,但也有一些新东西。例如,你以前没见过的4个HTTP头:Content-Type, Content-Length, Date, 和Servedr。这些头是Web服务器生成的响应应该有的。虽然他们并不是必须的。头的目的传输HTTP请求/响应的额外信息。
现在你对WSGI接口了解的更多啦,同样,以下是带有更多信息的HTTP响应,这些信息表示了哪些部件产生的它(响应):
我还没有介绍’environ’字典呢,但它基本上就是一个Python字典,必须包含WSGI规范规定的必要的WSGI和CGI变量。服务器在解析请求后,从HTTP请求拿到了字典的值,字典的内容看起来像下面这样:
Web框架使用字典里的信息来决定使用哪个视图,基于指定的路由,请求方法等,从哪里读请求体,错误写到哪里去,如果有的话。
现在你已经创建了你自己的WSGI Web服务器,使用不同的Web框架写Web应用。还有,你还顺手写了个简单的Web应用/Web框架。真是段难忘的旅程。咱们简要重述下WSGI Web服务器必须做哪些工作才能处理发给WSGI应用的请求吧:
首先,服务器启动并加载一个由Web框架/应用提供的可调用的’application’
然后,服务器读取请求
然后,服务器解析它
然后,服务器使用请求的数据创建了一个’environ’字典
然后,服务器使用’environ’字典和’start_response’做为参数调用’application’,并拿到返回的响应体。
然后,服务器使用调用’application’返回的数据,由’start_response’设置的状态和响应头,来构造HTTP响应。
最终,服务器把HTTP响应传回给户端。
这就是全部啦。现在你有了一个可工作的WSGI服务器,它可以处理使用像Django,Flask,Pyramid或者 你自己的WSGI框架这样的兼容WSGI的Web框架写的基本的Web应用。最优秀的地方是,服务器可以在不修改代码的情况下,使用不同的Web框架。
在你离开之前,还有个问题请你想一下,“该怎么做才能让服务器同一时间处理多个请求呢?”
保持关注,我会在本系列第三部分秀给你看实现它的一种方式。欢呼!
顺便说下,我在写一本书《一起构建WEB服务器:第一步》,它解释了从零开始写一个基本的WEB服务器,还更详细地讲解了我上面提到的话题。订阅邮件组来获取关于书籍和发布时间和最近更新。
相关文章推荐
- Linux学习篇之Vim编辑器
- list to csv
- rem布局实现自适应
- Android Content Providers基础
- 通用分页 (基于jquery、bootstrap)
- HDU 3363 Ice-sugar Gourd (贪心)
- Mac上安装go环境
- 解决iSlider的一些问题(滑动组件)
- python学习
- POJ3258-River Hopscotch二分
- 【数组5】最小的K个数
- tomcat6+redis 的session共享
- SQL 左外连接查询 将右表中的多行变为左表的一列或多列
- poj 2632 Crashing Robots
- 1022. D进制的A+B (20)--做题记录
- 【数组4】数组中出现次数超过一半的数字
- 阅读笔记(五)
- 2016 Multi-University Training Contest 3
- POJ3368 Frequent Values [RMQ] [线段树]
- 2015 Multi-University Training Contest 1