status |
草稿 |
HuangYi; 50% |
那么小白决定进一步探索 python web开发更高级一点的技术了,行者建议他从基础入手。
当用户在浏览器中输入网址,浏览器便找到web服务器,向它发起http请求, web服务器再找到web应用程序执行之,并把结果返回给客户端浏览器。
那么做web开发之前首先要有web服务器才行,开发阶段最好使用一个简单方便的开发服务器。 等开发调试完毕,再将代码部署到 apache、lighttpd 等成熟高效的web服务器, 放心,使用python做web开发,最后部署的阶段是很轻松的,在本章的后面便会提到。
开发服务器
那么 python2.5 就自带了一个叫做 wsgiref 模块,它提供一些专业 web 开发所需要的一些基础工具,比如一
个开发服务器:
1 # -*- coding: utf-8 -*-
2 from wsgiref import simple_server
3
4 # 定义一个输出 hello world 和环境变量的简单web应用程序
5 def hello_app(environ, start_response):
6 # 输出 http 头
7 start_response('200 OK', [('Content-type','text/plain')])
8 # 准备输出的内容
9 content = []
10 content.append('Hello world')
11 for key, value in environ.items():
12 content.append('%s : %s' % (key, value))
13 # 输出,根据 wsgi 协议,返回的需要是一个迭代器,返回一个 list 就可以
14 return ['\n'.join(content)]
15
16 # 构造开发服务器对象,设置绑定的地址和端口,并把 hello world 应用程序传给他
17 server = simple_server.make_server('localhost', 8080, hello_app)
18 # 启动开发服务器
19 server.serve_forever()
把以上代码以utf-8编码保存成python程序,不妨命名为 main.py。然后执行上面这个程序后,打开浏览器,随
便访问一个以 http://localhost:8080 开头的网址即可看到 environ 所包含的内容。
wsgiref 带的这个开发服务器调用应用程序所使用的协议叫做 wsgi,wsgiref就是对wsgi协议的 一个参考实现,wsgi 协议内容可以参考:URL ,在这里你只需要知道,hello_app 的第一个参数environ 是包含web环境变量的字典,熟悉 cgi 的朋友可以看做是 cgi 里的环境变量, 里面包含了整个web请求有关的信息(从上面这个程序输出的内容就可以看得出来),比如请求的路径
(PATH_INFO), ,是get请求还是post请求(REQUEST_METHOD),cookie(HTTP_COOKIE),客户端的IP(REMOTE_ADDR)等等信
息。
第二个参数 start_response 是一个函数,在返回具体内容之前需要先调用它来输出 http 状态码和响应头。
web应用程序是要和用户进行交互的,意味着我们需要能够得到用户的输入,然后作出相应地 动作。 通常用户通过浏览器访问网站有两种方式,一种是 get 请求,就是直接访问一个URL,另一种是 post 请求,比如用户提交了一个表单。web应用程序可以通过 environ['REQUEST_METHOD'] 来判断是哪种类型
的请求。 get 请求的时候用户可以往URL的后面添加参数来向web应用程序传递数据, web应用程序可以通过 environ['QUERY_STRING'] 来获取这些参数,不过由于URL的长度往往是有限制的, 所以不建议使用get方法来传递的比较大的数据; post 请求的情况下用户通过填写表单来向服务器传递数据,post 提交的数据是没有大小限制的, web应用程序可以通过 environ['wsgi.input'].read() 来从 socket 中逐步读取这些数据。
显然,直接通过 environ 来处理用户输入还是有点麻烦的,小白很快就发现 python世界有一些专门的模块是可以帮忙简化这个事情的。
处理web请求和响应
小白发现有几个模块可以用来处理 web 请求和响应,webob 就是其中之一。
安装 webob 之前我们先安装 setuptools ,一个包管理的工具,到时候可以自动为我们搜索并下载想要的 软件包。 在这个页面 http://pypi.python.org/pypi/setuptools 下面,非windows平台可以下载 .egg 文件 ,然后修改权限直接执行之,或者下载源码包,解压后执行 python setup.py install , windows平台的用户也可以直接下载 exe 后缀名的可执行安装程序,下载后直接执行就好。
安装完 setuptools 后,安装 webob 就很简单了,打开一个命令行窗口,执行 easy_install webob , setuptools 能够自动找到最新的版本并自动下载安装。当然,如果嫌它单线程的下载速度有点慢,你也 可以直接到 http://pypi.python.org/pypi 搜到相应的包,手动下载, 然后加压并执行 python setup.py install ,后面的包的安装方法和这里一样。
安装完成后我们可以直接在 python shell 里对 webob 所提供的功能进行试验:
基本使用方法就是这样了,使用一个 Request 类来包装 environ ,然后通过 Request 对象的属性和方法, 对 environ 进行访问。由于只有在 web 环境下才能有一个完整的 environ 字典,为了方便在 python shell 里面进行试验,webob 还提供一种方法可以模拟一个默认的web环境,请看:
1 >>> from webob import Request
2 >>> req = Request.blank('http://localhost:8080/hello?name=python')
3 >>> from pprint import pprint
4 >>> # 自动构建的简易 web 环境
5 >>> pprint(req.environ)
6 {'HTTP_HOST': 'localhost:8080',
7 'PATH_INFO': '/hello',
8 'QUERY_STRING': 'name=python',
9 'REQUEST_METHOD': 'GET',
10 'SCRIPT_NAME': '',
11 'SERVER_NAME': 'localhost',
12 'SERVER_PORT': '8080',
13 'SERVER_PROTOCOL': 'HTTP/1.0',
14 'webob._parsed_query_vars': (MultiDict([('name', 'python')]), 'name=python'),
15 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x00B6F0B0>,
16 'wsgi.input': <cStringIO.StringI object at 0x00B6A2F0>,
17 'wsgi.multiprocess': False,
18 'wsgi.multithread': False,
19 'wsgi.run_once': False,
20 'wsgi.url_scheme': 'http',
21 'wsgi.version': (1, 0),
22 'wsgiorg.routing_args': ((), {})}
23 >>> # 通过 Request 的属性可以更方便地进行访问
24 >>> req.method
25 'GET'
26 >>> req.host
27 'localhost:8080'
28 >>> req.path
29 '/hello'
30 >>> req.query_string
31 'name=python'
32 >>> # 可以通过字典的方式直接访问 get 参数
33 >>> req.GET['name']
34 'python'
35 >>> # 和 post 数据,显然,这不是一个 post 请求
36 >>> req.POST
37 <NoVars: Not a form request>
38 >>> # 不过我们仍然可以模拟一个 post 请求
39 >>> req.method = 'POST'
40 >>> # 手动设置模拟 post 的数据
41 >>> req.body = 'name=python'
42 >>> # 这下就可以了
43 >>> req.POST['name']
44 'python'
45 >>> # 还可以很方便获取 cookie 数据
46 >>> # 先模拟一段客户端请求带来的cookie
47 >>> req.headers['Cookie'] = 'name=python'
48 >>> # 同样,通过字典的方式对 cookie 进行访问
49 >>> req.cookies
50 {'name': 'python'}
同样,web应用程序的响应数据,比如状态码,响应头,设置 cookie 等, webob 都通过 Response 对象进行了包装:
1 >>> from webob import Response
2 >>> # 构造一个 Response对象
3 >>> res = Response(status=200, body='hello world')
4 >>> # 设置响应头
5 >>> res.headers['content-type'] = 'text/plain'
6 >>> # 设置 cookie
7 >>> res.set_cookie('name', 'python', max_age=360, path='/',
8 ... domain='example.org', secure=True)
9 >>> print res
10 200 OK
11 Content-Length: 11
12 content-type: text/plain
13 Set-Cookie: name=python; Domain=example.org; expires="Thu, 25-Sep-2008 14:28:07
14 GMT"; Max-Age=360; Path=/; secure
15
16 hello world
试完了基本功能,就试试把它加入我们的 web 应用程序吧,有个 webob, 那处理业务逻辑的代码就可以通过 Request、Response 对象来跟 web 服务器打交道, 这样我们可以把实现业务逻辑的代码分离开来:
1 # -*- coding: utf-8 -*-
2 from wsgiref import simple_server
3 from webob import Request, Response
4
5 # 专门实现业务的函数,现在简洁多了
6 def hello_app(request):
7 content = []
8 # 获取 get 请求的参数
9 content.append('Hello %s'%request.GET['name'])
10 # 输出所有 environ 变量
11 for key, value in request.environ.items():
12 content.append('%s : %s' % (key, value))
13
14 response = Response(body='\n'.join(content))
15 response.headers['content-type'] = 'text/plain'
16 return response
17
18 # 对请求和响应进行包装
19 def wrapper(environ, start_response):
20 request = Request(environ)
21 response = hello_app(request)
22 # response 对象本身也实现了与 wsgi 服务器之间通讯的协议,
23 # 所以可以帮我们处理与web服务器之间的交互。
24 return response(environ, start_response)
25
26 server = simple_server.make_server('localhost', 8080, wrapper)
27 server.serve_forever()
这样,执行这个程序后,当我们访问地址 http://localhost:8080/hello?name=python 时, 就可以看到 hello python 和后面跟的一串 wsgi 的环境变量了。
模板
但是 web程序大部分时候都是要返回一个 html 页面的,而把一个复杂的 html 页面 代码直接写到python代码里面就太丑陋了,应该把它放到一个模板里去,把页面展现与 业务逻辑分离开来。
python里面的模板引擎主要有mako、genshi、jinja等。mako 主要特点在于模板里面 可以比较方便的嵌入python代码,而且执行效率一流;genshi 的特点在于基于 xml, 非常简单易懂的模板语法,对于热爱xhtml的朋友来说是很好的选择,同时也可以嵌入python 代码,实现一些复杂的展现逻辑;jinja 和 genshi 一样拥有很简单的模板语法,只是不 依赖于 xml 的格式,同样很适合设计人员直接进行模板的制作,同时也可以嵌入python 代码实现一些复杂的展现逻辑。
小白看 mako 挺顺眼的,于是就钻研了它一把。 可以到这里 http://www.makotemplates.org/download.html 找到最新版本的下载地址,下载解压, 然后执行 python setup.py install 就可以了。
下面的代码就是写的一个简单的 mako 模板文件,里面使用 python 的 for 循环来把 data 这个字典的 内容填充到一个html列表(mako模板的详细语法请参考:URL)。
<html> <head> <title>简单mako模板</title> </head> <body> <h5>Hello ${name}!</h5> <ul> % for key, value in data.items(): <li> ${key} - ${value} <li> % endfor </ul> </body> </html>
然后要做的就是给模板传递一个叫做 data 的字典对象了,可以编写如下三句代码实现:
执行以上程序,就可以看到通过模板输出的 html 代码了。 功能研究清楚了,小白开始试着把它整合到这个web应用中来, 这个时候小白发现,需要改动的部分主要集中在 hello_app 这个函数了, 因为核心的逻辑就是在这里进行的,小白决定进行一次小小的重构, 把 hello_app 给彻底独立出来,形成单独的模块 controller.py , 这样 main.py 就变成这样了:
1 # -*- coding: utf-8 -*-
2 from wsgiref import simple_server
3 from webob import Request
4 from controller import hello_app
5
6 def wrapper(environ, start_response):
7 request = Request(environ)
8 response = hello_app(request)
9 return response(environ, start_response)
10
11 server = simple_server.make_server('localhost', 8080, wrapper)
12 server.serve_forever()
而 controller.py 和模板功能整合以后成为:
执行 python main.py ,并访问 http://localhost:8080/hello?name=python 你就可以看到通过模板输出的页
面了。
通过把 hello_app 分离开来, main.py 基本上就固定下来了,以后对业务逻辑的修改就只设计
controller.py 这个文件。
ORM
作为一个动态网站,有很多数据需要持久存储,关系数据库在这方面通常都是不二选择。 当小白向行者问道如何才能简化对数据库的访问时,行者郑重向小白推荐了 sqlalchemy 这个库。
sqlalchemy 是一个 ORM (对象-关系映射)库,提供python对象与关系数据库之间的映射, 可以通过 python 对象很方便地对关系数据库进行操纵,比如使用python代码描述数据库结构, 创建数据库,执行插入、查询、删除等数据库操作,非常方便。除了这些 ORM 的基本功能外, sqlalchemy 还支持Model的继承,自引用(Self-referential)、eager loading、lazy loading 等等, 还可以直接将 Model 映射到自定义的 SQL 语句。可以说既有不输于 Hibernate 的强大功能, 同时不失 Python 的优雅简洁。
在这里 http://www.sqlalchemy.org/download.html 找到最新版本的下载地址,下载解压, 然后执行 python setup.py install 进行安装。
代码胜千言,sqlalchemy 到底可以做什么,直接上代码展示下 sqlalchemy 的基本功能吧:
1 # -*- coding: utf-8 -*-
2 from sqlalchemy.ext.declarative import declarative_base
3
4 # 创建数据库引擎,这里我们直接使用 python2.5 自带的数据库引擎:sqlite,
5 # 直接在当前目录下建立名为 data.db 的数据库
6 engine = create_engine('sqlite:///data.db')
7 # sqlalchemy 中所有数据库操作都要由某个session来进行管理
8 # 关于 session 的详细信息请参考:http://www.sqlalchemy.org/docs/05/session.html
9 Session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
10 Base = declarative_base()
11
12 class Dictionay(Base):
13 # python 对象对应关系数据库的表名
14 __tablename__ = 't_dictionay'
15 # 两个字段:
16 key = Column('key', String(255), primary_key=True)
17 value = Column('value', String(255))
18
19 # 创建数据库
20 Base.metadata.create_all(engine)
21
22 session = Session()
23 for item in ['python','ruby','java']:
24 dictionay = Dictionay(key=item, value=item.upper())
25 session.add(dictionay)
26
27 # 提交session,在这里才真正执行数据库的操作,添加三条记录到数据库
28 session.commit()
29
30 # 查询Dictionary类对应的数据
31 for dictionay in session.query(Dictionary):
32 print dictionay.name, dictionay.value
直接执行这个程序就可以了。 不过这是个单独的 demo,要把放到 web 应用里面,还需要先对它的结构重新组织一下。
先剥离出一个公用的 model.py 文件。
1 # -*- coding: utf-8 -*-
2 from sqlalchemy.ext.declarative import declarative_base
3
4 engine = create_engine('sqlite:///data.db')
5 Session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
6 Base = declarative_base()
7
8 class Dictionay(Base):
9 __tablename__ = 't_dictionay'
10 key = Column('key', String(255), primary_key=True)
11 value = Column('value', String(255))
然后建立一个创建数据库并插入一些测试数据的单独的程序,创建数据库只需要一开始做一次就可以了。
1 # -*- coding: utf-8 -*-
2 # 导入公用的 model 模块
3 from model import Base, Session, Dictionary
4
5 # 创建数据库
6 Base.metadata.create_all(engine)
7
8 # 插入初始数据
9 session = Session()
10 for item in ['python','ruby','java']:
11 dictionay = Dictionay(key=item, value=item.upper())
12 session.add(dictionay)
13
14 session.commit()
最后再将这个功能加入到我们的 web 程序里面来,修改 controller.py 文件:
1 # -*- coding: utf-8 -*-
2 from webob import Response
3 from mako import Template
4 # 导入公用的 model 模块
5 from model import Session, Dictionary
6
7 def hello_app(request):
8 session = Session()
9 # 查询到所有 Dictionary 对象
10 dictionaries = session.Query(Dictionary)
11 # 然后根据 Dictionary 对象的 name、value 属性把列表转换成一个字典
12 data = dict([(dictionary.name, dictionary.value) for dictionary in dictionaries])
13
14 tmpl = Template('./simple.html.html')
15 content = tmpl.render(name=request.GET['name'], data=data)
16 return Response(body=content)
执行 python main.py ,并访问 http://localhost:8080/hello?name=python,就可以看到从数据库中 取出来的三条测试数据了。
TODO 其他组件的介绍
现在让我们回头看一下,我们最终得到了一个什么样的程序。
TODO 成熟 web 框架介绍