点击
上方蓝字
,快速关注我们)
来源:RussellLuo
www.cnblogs.com/russellluo/p/3338616.html
如有好文章投稿,请点击 → 这里了解详情
-
一、概述
-
二、从demo_app开始
-
三、WSGI中的application
-
四、区分URL
-
五、重构
-
六、参考
一、
概述
在Python中,WSGI(Web Server Gateway Interface)定义了Web服务器与Web应用(或Web框架)之间的标准接口。在WSGI的规范下,各种各样的Web服务器和Web框架都可以很好的交互。
由于WSGI的存在,用Python写一个简单的Web框架也变得非常容易。然而,同很多其他的强大软件一样,要实现一个功能丰富、健壮高效的Web框架并非易事;如果您打算这么做,可能使用一个现成的Web框架(如 Django、Tornado、web.py 等)会是更合适的选择。
本文尝试写一个类似web.py的Web框架。好吧,我承认我夸大其辞了:首先,web.py并不简单;其次,本文只重点实现了 URL调度(URL dispatch)部分。
二、从demo_app开始
首先,作为一个初步体验,我们可以借助 wsgiref.simple_server 来搭建一个简单无比(trivial)的Web应用:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from
wsgiref.simple_server
import
make_server
,
demo_app
httpd
=
make_server
(
''
,
8086
,
demo_app
)
sa
=
httpd
.
socket
.
getsockname
()
print
'http://{0}:{1}/'
.
format
(
*
sa
)
# Respond to requests until process is killed
httpd
.
serve_forever
()
运行脚本:
$
python
code
.py
http
://
0.0.0.0
:
8086
/
打开浏览器,输入http://0.0.0.0:8086/后可以看到:一行”Hello world!” 和 众多环境变量值。
三、WSGI中的application
WSGI中规定:application是一个 可调用对象(callable object),它接受 environ 和 start_response 两个参数,并返回一个 字符串迭代对象。
其中,可调用对象 包括 函数、方法、类 或者 具有__call__方法的 实例;environ 是一个字典对象,包括CGI风格的环境变量(CGI-style environment variables)和 WSGI必需的变量(WSGI-required variables);start_response 是一个可调用对象,它接受两个 常规参数(status,response_headers)和 一个 默认参数(exc_info);字符串迭代对象 可以是 字符串列表、生成器函数 或者 具有__iter__方法的可迭代实例。更多细节参考 Specification Details。
The Application/Framework Side 中给出了一个典型的application实现:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
def
simple_app
(
environ
,
start_response
)
:
"""Simplest possible application object"""
status
=
'200 OK'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
start_response
(
status
,
response_headers
)
return
[
'Hello world!\n'
]
现在用simple_app来替换demo_app:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""code.py"""
from
wsgiref.simple_server
import
make_server
from
application
import
simple_app
as
app
if
__name__
==
'__main__'
:
httpd
=
make_server
(
''
,
8086
,
app
)
sa
=
httpd
.
socket
.
getsockname
()
print
'http://{0}:{1}/'
.
format
(
*
sa
)
# Respond to requests until process is killed
httpd
.
serve_forever
()
运行脚本code.py后,访问http://0.0.0.0:8086/就可以看到那行熟悉的句子:Hello world!
四、区分URL
倒腾了一阵子后,您会发现不管如何改变URL中的path部分,得到的响应都是一样的。因为simple_app只识别host+port部分。
为了对URL中的path部分进行区分处理,需要修改application.py的实现。
首先,改用 类 来实现application:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
class
my_app
:
def
__init__
(
self
,
environ
,
start_response
)
:
self
.
environ
=
environ
self
.
start
=
start_response
def
__iter__
(
self
)
:
status
=
'200 OK'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
self
.
start
(
status
,
response_headers
)
yield
"Hello world!\n"
然后,增加对URL中path部分的区分处理:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
class
my_app
:
def
__init__
(
self
,
environ
,
start_response
)
:
self
.
environ
=
environ
self
.
start
=
start_response
def
__iter__
(
self
)
:
path
=
self
.
environ
[
'PATH_INFO'
]
if
path
==
"/"
:
return
self
.
GET_index
()
elif
path
==
"/hello"
:
return
self
.
GET_hello
()
else
:
return
self
.
notfound
()
def
GET_index
(
self
)
:
status
=
'200 OK'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
self
.
start
(
status
,
response_headers
)
yield
"Welcome!\n"
def
GET_hello
(
self
)
:
status
=
'200 OK'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
self
.
start
(
status
,
response_headers
)
yield
"Hello world!\n"
def
notfound
(
self
)
:
status
=
'404 Not Found'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
self
.
start
(
status
,
response_headers
)
yield
"Not Found\n"
修改code.py中的from application import simple_app as app,用my_app来替换simple_app后即可体验效果。
五、重构
上面的代码虽然奏效,但是在编码风格和灵活性方面有很多问题,下面逐步对其进行重构。
1、正则匹配URL
消除URL硬编码,增加URL调度的灵活性:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
import
re
##########修改点
class
my_app
:
urls
=
(
(
"/"
,
"index"
),
(
"/hello/(.*)"
,
"hello"
),
)
##########修改点
def
__init__
(
self
,
environ
,
start_response
)
:
self
.
environ
=
environ
self
.
start
=
start_response
def
__iter__
(
self
)
:
##########修改点
path
=
self
.
environ
[
'PATH_INFO'
]
method
=
self
.
environ
[
'REQUEST_METHOD'
]
for
pattern
,
name
in
self
.
urls
:
m
=
re
.
match
(
'^'
+
pattern
+
'$'
,
path
)
if
m
:
# pass the matched groups as arguments to the function
args
=
m
.
groups
()
funcname
=
method
.
upper
()
+
'_'
+
name
if
hasattr
(
self
,
funcname
)
:
func
=
getattr
(
self
,
funcname
)
return
func
(
*
args
)
return
self
.
notfound
()
def
GET_index
(
self
)
:
status
=
'200 OK'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
self
.
start
(
status
,
response_headers
)
yield
"Welcome!\n"
def
GET_hello
(
self
,
name
)
:
##########修改点
status
=
'200 OK'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
self
.
start
(
status
,
response_headers
)
yield
"Hello %s!\n"
%
name
def
notfound
(
self
)
:
status
=
'404 Not Found'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
self
.
start
(
status
,
response_headers
)
yield
"Not Found\n"
2、DRY
消除GET_*方法中的重复代码,并且允许它们返回字符串:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
import
re
class
my_app
:
urls
=
(
(
"/"
,
"index"
),
(
"/hello/(.*)"
,
"hello"
),
)
def
__init__
(
self
,
environ
,
start_response
)
:
##########修改点
self
.
environ
=
environ
self
.
start
=
start_response
self
.
status
=
'200 OK'
self
.
_headers
=
[]
def
__iter__
(
self
)
:
##########修改点
result
=
self
.
delegate
()
self
.
start
(
self
.
status
,
self
.
_headers
)
# 将返回值result(字符串 或者 字符串列表)转换为迭代对象
if
isinstance
(
result
,
basestring
)
:
return
iter
([
result
])
else
:
return
iter
(
result
)
def
delegate
(
self
)
:
##########修改点
path
=
self
.
environ
[
'PATH_INFO'
]
method
=
self
.
environ
[
'REQUEST_METHOD'
]
for
pattern
,
name
in
self
.
urls
:
m
=
re
.
match
(
'^'
+
pattern
+
'$'
,
path
)
if
m
:
# pass the matched groups as arguments to the function
args
=
m
.
groups
()
funcname
=
method
.
upper
()
+
'_'
+
name
if
hasattr
(
self
,
funcname
)
:
func
=
getattr
(
self
,
funcname
)
return
func
(
*
args
)
return
self
.
notfound
()
def
header
(
self
,
name
,
value
)
:
##########修改点
self
.
_headers
.
append
((
name
,
value
))
def
GET_index
(
self
)
:
##########修改点
self
.
header
(
'Content-type'
,
'text/plain'
)
return
"Welcome!\n"
def
GET_hello
(
self
,
name
)
:
##########修改点
self
.
header
(
'Content-type'
,
'text/plain'
)
return
"Hello %s!\n"
%
name
def
notfound
(
self
)
:
##########修改点
self
.
status
=
'404 Not Found'
self
.
header
(
'Content-type'
,
'text/plain'
)
return
"Not Found\n"
3、抽象出框架
为了将类my_app抽象成一个独立的框架,需要作出以下修改:
-
剥离出其中的具体处理细节:urls配置 和 GET_*方法(改成在多个类中实现相应的GET方法)
-
把方法header实现为类方法(classmethod),以方便外部作为功能函数调用
-
改用 具有__call__方法的 实例 来实现application
修改后的application.py(最终版本):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
import
re
class
my_app
:
"""my simple web framework"""
headers
=
[]
def
__init__
(
self
,
urls
=
(),
fvars
=
{})
:
self
.
_urls
=
urls
self
.
_fvars
=
fvars
def
__call__
(
self
,
environ
,
start_response
)
:
self
.
_status
=
'200 OK'
# 默认状态OK
del
self
.
headers
[
:
]
# 清空上一次的headers