深入了解wsgi协议及用webob模块处理web请求和响应的方法
和相应内容给web服务器。

在上述操作中,所采用的开发服务器与程序间交互的通信协议被称作WSGI,这一协议与CGI相似,它也将接收到的请求信息封装成键值对的形式。
仅仅是因为CGI是通过环境变量将信息传递给CGI应用程序的,而WSGI则是直接利用Python的字典对象进行信息传递。
hello_app的第一个参数environ代表一个包含请求信息的字典,第二个参数则是一个函数,该函数负责web应用程序输出响应内容。
前需要先调用它来输出状态码和响应头。
在处理网络请求与反馈的过程中,我们选用webob模块进行操作。为了确保模块的正常运行,必须进行安装。安装过程中,首先需要安装setuptools模块,这是一款包管理工具。它能够自动下载所需的软件包,其功能与Ubuntu系统中的app-get类似。以下是安装后的操作指引:完成安装后,您可在命令行界面键入“easy_install webob”,系统将自动执行下载并完成安装。
简单使用:
>>> # 导入 Request 对象
>>> from webob import Request
>>> environ = {}
利用 Request 对 environ 字典进行封装处理。
>>> req = Request(environ)
通过创建一个名为Request的类来封装environ,我们得以借助Request对象的属性与函数来访问environ。鉴于仅在web环境中方可获取到真实的environ字典,为了便于大家于shell环境下进行测试,webob提供了一种模拟简单web请求的手段:
也可以通过req查找其它有用的信息
同时,我们还可以利用webob模块中的Response实体来对响应内容进行封装。
下面使用webob模块重写之前的hello_app
# -*- coding: utf-8 -*- 导入wsgiref模块中的simple_server子模块。 导入webob模块中的Request和Response类。 我们额外添加了一项功能,该功能能够根据用户在网址末尾提供的参数进行操作。 # 显示相应的内容 def hello_app(request): content = [] # 获取 get 请求的参数 content中添加了字符串'Hello',紧随其后的是从request.GET字典中获取的'name'键对应的值。 # 输出所有 environ 变量 遍历请求的环境变量,对每个键值对进行操作:key, value := request.environ.items().each() content中添加了键值对,具体为:%s对应%s。 response = Response(内容以换行符连接后形成的字符串作为正文) response的headers属性中,将content-type的值设置为text/plain。 return response # 对请求和响应进行包装 定义函数wsgi_wrapper,接收两个参数:环境变量environ和启动响应函数start_response。 request = Request(environ) response = hello_app(request) response 对象同样具备与 wsgi 服务器进行通信的能力,并遵循相应的协议规范。 # 所以可以帮我们处理与web服务器之间的交互。 # 这一句比较奇怪,对象使用括号是什么意思。。。。 执行返回操作,向响应函数传递环境变量和启动响应参数。 创建了一个服务器实例,该实例由simple_server模块生成,监听本地主机的8080端口,并使用wsgi_wrapper作为其包装器。 server.serve_forever()
为了提升wsgi_wrapper的适用性,我们可将其构思为一种装饰器模式。
# -*- coding: utf-8 -*-
from wsgiref import simple_server
from webob import Request, Response
# 写成装饰器的 wsgi_wrapper
def wsgi_wrapper(func):
定义一个名为new_func的函数,该函数接收两个参数:环境变量environ和响应启动函数start_response。
request = Request(environ)
response = func(request)
执行返回操作,向响应函数传递环境变量和启动响应。
new_func的名称与func的名称相同,即:new_func.__name__ 等于 func.__name__。
new_func的文档字符串被设置为func的文档字符串。
return new_func
# 应用程序
@wsgi_wrapper
def hello_app(request):
content = []
content.append('Hello %s'%request.GET['name'])
for key, value in request.environ.items():
content.append('%s : %s' % (key, value))
response = Response(body='\n'.join(content))
response.headers['content-type'] = 'text/plain'
return response
创建了一个服务器实例,该实例由simple_server模块生成,并指定了本地地址为localhost和端口号8080,同时将hello_app作为应用对象传入。
server.serve_forever()
三、模板
果不其然,我们确实需要借助模板,不能总是直接在响应内容中输入一长串的HTML代码。
Python领域常用的模板引擎包括mako、genshi以及jinjia等。
Mako的显著优势在于其模板中能够便捷地嵌入Python代码,并且其执行效能十分卓越。
genshi的特色在于其基于XML的简洁易学的模板语法,这对于那些热衷于xhtml的朋友来说,无疑是一个理想的选择。
同时也可以嵌入Python 代码,实现一些复杂的展现逻辑;
Jinja与Genshi相似,都具备简洁的模板语法,而且它们并不要求遵循XML的规范,因此同样非常适合设计人员直接进行模板的构建。
同时也可以嵌入Python 代码实现一些复杂的展现逻辑。
在此情况下,我们采用Mako工具,通过指定具体地址,执行下载python的setup.py安装命令来完成安装过程。
简单的模块例子:
## -*- coding: utf-8 -*-简单mako模板 Hello ${name}!
-
遍历data字典中的每个键值对,对每个键和对应的值进行处理:for key, value in data.items():
- ${key} - ${value}
- % endfor
将文件命名为simple.html,接着向模板对象提供data和name两个参数,之后执行渲染操作,即可输入HTML内容。
# -*- coding: utf-8 -*- # 导入模板对象 引用自mako模板库中的模板类 # 使用模板文件名构造模板对象 创建模板对象时,指定文件名为'./simple.html',并设定输出编码为'utf-8'。 # 构造一个简单的字典填充模板,并print出来 执行模板渲染操作,其中模板名称设定为'python',并传入包含键值对的数据字典,字典内包含'a'键对应值为1和'b'键对应值为2。
将文件命名为test_template.py,执行后即可输入所需内容。
$ python test_template.py
简单mako模板 Hello python!
- a - 1
- b - 2
下面对hello_app程序进行重构:
将 wsgi_wrapper 独立提取至通用模块,命名为 utils.py。
# -*- coding: utf-8 -*- from webob import Request def wsgi_wrapper(func): def new_func(environ, start_response): request = Request(environ) response = func(request) return response(environ, start_response) new_func.__name__ = func.__name__ new_func.__doc__ = func.__doc__ return new_func
将“hello_app”完全分离,构建为一个独立的模块,命名为“controller.py”。
# -*- coding: utf-8 -*- 引入模块,使用wsgi_wrapper工具。 from webob import Response from mako import Template # 整合了模板功能的 hello_app @wsgi_wrapper def hello_app(request): 创建模板对象时,指定文件名为'./simple.html',并设置输出编码为'utf-8'。 content = tmpl执行渲染操作,其中name参数为request.GET字典中获取的'name'键对应的值,data参数则为request.environ环境变量。 return Response(body=content)
3. 这样 main.py 就变成这样了:
# -*- coding: utf-8 -*-
from wsgiref import simple_server
导入控制器模块中的hello_app应用。
server = simple_server.make_server('localhost', 8080, hello_app)
server.serve_forever()
四、对象关系映射(ORM,即Object Relation Mapping)
这一阶段终于到来,对于web应用而言,与数据库的协作是必不可少的。
此处采用sqlalchemy库,该库属于对象-关系映射(ORM)类别,负责实现Python对象与关系型数据库间的数据映射。与Django的models模块类似,它也实现了这一功能。
其用法颇为相似,同样能够借助Python编写的代码来构建数据库中的表格,并对它们执行各种操作。
SQLAlchemy 能够自动处理 Python 对象的继承关系,支持预加载(eager loading)和延迟加载(lazy loading)功能,并能直接将模型(Model)与自定义的数据库表结构进行映射。
SQL语句的功能十分全面,兼容众多数据库系统。以SQLAlchemy为例,它不仅具备与Hibernate相媲美的强大功能,而且依然保持了Python语言的特色。
的简洁优雅。
使用方法:
# -*- coding: utf-8 -*-
from sqlalchemy import *
导入sqlalchemy.orm模块中的sessionmaker和scoped_session类。
在构建数据库引擎的过程中,我们选择直接运用Python 2.5版本内嵌的数据库引擎,即sqlite。
# 直接在当前目录下建立名为 data.db 的数据库
创建数据库引擎时,指定了使用SQLite数据库,并将数据存储在名为'data.db'的文件中,执行了如下代码:engine = create_engine('sqlite:///data.db')。
关于session的详细内容,请查阅相关资料:http://www.sqlalchemy.org/docs/05/session.html。
创建会话时,我们采用了作用域会话模式,设置了不自动提交和不自动刷新的特性,并将数据库引擎与之绑定。
Base = declarative_base()
class Dictionary(Base):
# Python 对象对应关系数据库的表名
该表的名称设定为't_dictionary'。
自动定义,参数包括:数据库列名称,列数据类型,以及其他相关选项。
定义一个名为 'key' 的列,其数据类型为字符串,长度不超过255个字符,且该列被设置为该表的主键。
该字段定义为名为'value'的列,数据类型为字符串,最大长度为255个字符。
# 创建数据库
Base.metadata.execute('CREATE ALL TABLES'),engine负责执行该操作。
session = Session()
对于列表中的每个项目,例如'python'、'ruby'以及'java',都执行以下操作:
# 构造一个对象
字典对象被创建,其键为item,值为item转换为大写的版本。
# 告诉 sqlalchemy ,将该对象加到数据库
session.add(dictionary)
在此环节提交会话,数据库操作得以实际执行,向数据库中成功插入三条新记录。
session.commit()
# 查询数据库中Dictionary对象对应的数据
在会话中,对于每个通过session查询得到的字典对象,
输出字典的键,以及对应的值。
执行上述代码两次后会出现错误,其原因是数据库中主键信息出现了重复。
这样就可以整合到之前的controller.py文件中
# -*- coding: utf-8 -*- from utils import wsgi_wrapper from webob import Response from mako.template import Template # 导入公用的 model 模块 导入Session和Dictionary模块自模型中。 @wsgi_wrapper def hello_app(request): session = Session() # 查询到所有 Dictionary 对象 session中查询到的字典对象被赋值给变量dictionaries 接着,利用 Dictionary 对象的键、值属性,将列表成功转化为一个字典。 data等价于一个字典,它通过列表推导式生成,其中每个元素都是一个键值对,键是dictionary的key,值是dictionary的value,这些键值对来源于dictionaries中的每个dictionary。 tmpl = Template(filename='./simple.html', output_encoding='utf-8') content = tmpl执行渲染操作,其中使用到的名字参数来源于请求中的GET数据,具体为'name'键对应的值,同时渲染过程中所需的数据由变量data提供。 return Response(body=content)
五、URL分发控制
针对各类资源,设定各自独特的网址标识,当客户端发起对该网址的请求时,web应用程序便会依据该网址来确定相应的功能模块,并执行相应的操作。
提供一个干净的 URL 有很多好处:
1. 可读性,通过 URL 就可以大概了解其提供什么功能
2. 用户容易记住也方便直接输入
设计精良的URL通常较短且有力,同时更易于搜索引擎的检索和识别。
使用selector模块来处理url映射
请访问指定的下载链接,下载相应的source文件,然后执行python setup.py install命令进行安装。
首先把urls的配置单独放到urls.py中
# -*- coding: utf-8 -*-
from controller import hello_app
配置项中包含了如下映射:('/hello/{name}',),其中定义了当接收到GET请求时,将调用名为hello_app的应用程序。
修改main.py
# -*- coding: utf-8 -*- from wsgiref import simple_server from urls import mappings from selector import Selector # 构建 url 分发器 app = Selector(mappings) simple_server模块创建了一个服务器实例,该实例监听在本地主机的8080端口,并绑定到app应用。 server.serve_forever()
在hello_app中,您可以通过访问environ字典中的键'wsgiorg.routing_args'来获取name参数。
尽管在 wsgi_wrapper 中,我们还可以对 hello_app 的功能进行进一步的精简:我们可以直接将解析后的参数,
当作函数参数传过去!修改 utils.py:
from webob import Request
def wsgi_wrapper(func):
def new_func(environ, start_response):
request = Request(environ)
environ对象尝试获取名为'wsgiorg.routing_args'的键值,若该键值不存在,则返回一个包含空元组与空字典的元组。即,position_args和keyword_args分别被赋值为environ.get('wsgiorg.routing_args', ((), {}))的结果。
response = 执行函数func,传入参数request,以及剩余的位置参数*position_args,以及关键字参数**keyword_args。
return response(environ, start_response)
new_func.__name__ = func.__name__
new_func.__doc__ = func.__doc__
return new_func
那 hello_app 就可以改成这样了:
... @wsgi_wrapper 定义函数hello_app,接收参数request和name,其中name的默认值为空字符串。 ... 执行模板渲染操作,将变量name和数据data代入模板,生成content变量。 return Response(body=content) 运行main.py脚本,通过访问网址http://localhost:8080/hello/来使用Python功能。
总结
以上所述功能的实现,与Django框架中的若干核心模块相仿,期待这些内容能对各位的学习带来一定的帮助。