status

草稿

HuangYi; 100%

那么小白决定进一步深入探索 Python web开发技术了,行者建议他从基础入手。

当用户在浏览器中输入网址,浏览器便找到web服务器,向它发起http请求, web服务器再找到web应用程序执行之,并把结果返回给客户端浏览器。

这么说做web开发首先要有web服务器才行,一说到web服务器大家可能会联想到 apache、lighttpd 等成熟稳定高效的web服务器, 但是在开发阶段最好有一个简单方便的开发服务器,容易重启容易调试。 等开发调试完毕,再将代码部署到成熟稳定高效的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 头,text/plain 表示是纯文本
   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 所包含的内容。

PCS304-webmodule-helloworld.png

继续前进之前先补充一点基础知识,浏览器和web应用之间使用的是 http 协议,它规定了 请求和响应的格式。

请求主要包括请求的方法、请求的URL、请求头、请求体。请求的方法 http 规定的有 GET、POST、PUT、DELETE,只不过通过浏览器发起的web请求一般只涉及 GET 和 POST 请求,GET 请求顾名思义,一般用来从服务器获取内容,而POST往往用来修改内容, 一般通过提交 html 的 form 表单发起 POST 请求。 而从协议上来看 GET、POST 请求最大的区别就是 GET 请求没有请求体,而 POST 请求有, 这意味这 POST 请求可以通过请求体向web服务器传送大量的数据,甚至是上传文件,当然 GET 请求 也可以通过 URL 本身以及其参数向web服务器传递信息,比如 url?arg1=value1&arg2=value2 。

请求头就是一些描述请求包本身的一些描述信息了,比如编码、包长度等。

http 的响应的格式更简单一些,包括状态码、响应头和响应体, 状态码表示该请求的结果,比如 200 表示成功;404表示资源没有找到;500表示服务器错误; 301表示该资源已经换了地址,客户端需要跳转等。 响应头和请求类似,就是一些响应包的描述数据, 响应体就是输出的内容了,大部分情况就是页面的 html 代码。

web服务器接收到原始的 http 请求后进行一定程度的包装再交给 web 应用程序,web应用程序处理后 再以一定的格式返回数据给 web服务器,web服务器然后就将数据包装成 http 响应包最后传输给浏览器。 那么这样就完成了一个请求的整个生命周期。

很多人可能对 cgi 比较熟悉,cgi 就是web服务器与web应用程序之间的一个古老的协议, 在cgi协议中,web服务器将http请求的各种信息放到cgi应用程序的环境变量里面, cgi应用程序再通过标准输出,输出它的响应头和相应内容给web服务器。

我们上面用到的这个开发服务器与我们的应用程序之间所使用的协议叫做 wsgi, 它和 cgi 类似,同样是将http请求包装成一种key-value对, 只不过cgi通过环境变量传给cgi应用程序,而wsgi直接使用Python的字典对象,通过参数完成传递。

比如上面 hello_app 函数接收的第一个参数 environ 就是包含web请求信息的字典对象, 上面这个web程序输出的就是这个字典的全部内容,里面有比如请求的路径(PATH_INFO), ,请求的方法(REQUEST_METHOD),客户端cookie(HTTP_COOKIE),客户端的IP(REMOTE_ADDR)等等信息。

hello_app 的第二个参数 start_response 是一个函数,web 应用在输出响应内容前需要先调用它 来输出状态码和响应头。

动态的web应用程序是要和用户进行交互的,意味着我们需要得到用户的输入,然后作出相应地 动作。客户端有很多中方式可以向服务器传递信息,比如URL的参数、POST请求的数据、cookie, 都可以通过 environ 获取,比如 environ['QUERY_STRING'] 就可以获取URL参数, 不过这种方式比较底层,还需要我们自己解析其格式。

小白通过 google 很快就发现 Python 世界有一些专门的模块 可以用来处理http请求信息的。

处理web请求和响应

小白发现有几个模块可以用来处理 web 请求和响应,webob 就是其中之一。

安装 webob 之前我们需要先安装 setuptools ,一个包管理的工具,该工具可以自动为我们搜索并下载想要的 软件包。 在这个页面 http://pypi.python.org/pypi/setuptools ( 精巧地址: http://bit.ly/4fXVjD )下面,非Windows平台可以下载 .egg 文件 ,然后修改权限直接执行之,或者下载源码包,解压后执行 python setup.py install , Windows平台的用户也可以直接下载 exe 后缀名的可执行安装程序,下载后直接运行就好了。

安装完 setuptools 后,安装 webob 就很简单了,打开一个命令行窗口,执行 easy_install webob , setuptools 能够自动找到最新的版本并自动下载安装。当然,如果嫌它单线程的下载速度有点慢,你也 可以直接到 http://pypi.python.org/pypi ( 精巧地址: http://bit.ly/2CA9SG )搜到相应的包,手动下载, 然后加压并执行 python setup.py install ,后面的包的安装方法和这里类似。

安装完成后我们可以直接在 Python shell 里对 webob 所提供的功能进行试验:

   1 >>> # 导入 Request 对象
   2 >>> from webob import Request
   3 >>> environ = {}
   4 >>> # 使用 Request 来包装 environ 字典
   5 >>> req = Request(environ)

基本使用方法就是这样了,使用一个 Request 类来包装 environ ,然后通过 Request 对象的属性和方法, 对 environ 进行访问。由于只有在 web 环境下才能得到一个真实的 environ 字典,为了方便大家在 Python shell 里面对其进行试验,webob 还提供一种方法可以模拟一个简易的web请求,请看:

   1 >>> from webob import Request
   2 >>> # 构建一个简易的 web 请求
   3 >>> req = Request.blank('http://localhost:8080/hello?name=python')
   4 >>> from pprint import pprint
   5 >>> # 查看该 web 请求具体内容
   6 >>> pprint(req.environ)
   7 {'HTTP_HOST': 'localhost:8080',
   8  'PATH_INFO': '/hello',
   9  'QUERY_STRING': 'name=python',
  10  'REQUEST_METHOD': 'GET',
  11  'SCRIPT_NAME': '',
  12  'SERVER_NAME': 'localhost',
  13  'SERVER_PORT': '8080',
  14  'SERVER_PROTOCOL': 'HTTP/1.0',
  15  'webob._parsed_query_vars': (MultiDict([('name', 'python')]), 'name=python'),
  16  'wsgi.errors': <open file '<stderr>', mode 'w' at 0x00B6F0B0>,
  17  'wsgi.input': <cStringIO.StringI object at 0x00B6A2F0>,
  18  'wsgi.multiprocess': False,
  19  'wsgi.multithread': False,
  20  'wsgi.run_once': False,
  21  'wsgi.url_scheme': 'http',
  22  'wsgi.version': (1, 0),
  23  'wsgiorg.routing_args': ((), {})}
  24 >>> # 通过 Request 的属性可以更方便地对其进行访问
  25 >>> req.method
  26 'GET'
  27 >>> req.host
  28 'localhost:8080'
  29 >>> req.path
  30 '/hello'
  31 >>> req.query_string
  32 'name=python'
  33 >>> # 可以用字典的方式访问 GET 参数
  34 >>> req.GET['name']
  35 'python'
  36 >>> # 访问 POST 数据也类似,不过显然,这不是一个 POST 请求
  37 >>> req.POST
  38 <NoVars: Not a form request>
  39 >>> # 不过我们也可以模拟一个 POST 请求
  40 >>> req.method = 'POST'
  41 >>> # 手动设置模拟 POST 的请求数据
  42 >>> req.body = 'name=python'
  43 >>> # 这下就可以了
  44 >>> req.POST['name']
  45 'python'
  46 >>> # 还可以很方便获取 cookie 数据
  47 >>> # 先模拟一段带来的cookie的客户端请求
  48 >>> req.headers['Cookie'] = 'name=python'
  49 >>> # 同样,通过字典的方式对 cookie 进行访问
  50 >>> req.cookies
  51 {'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 >>> # 构建原始的 http 响应包
  10 >>> print res
  11 200 OK
  12 Content-Length: 11
  13 content-type: text/plain
  14 Set-Cookie: name=python; Domain=example.org; expires="Thu, 25-Sep-2008 14:28:07 GMT"; Max-Age=360; Path=/; secure
  15 
  16 hello world

试完了基本功能,就试试把它加入我们的 web 应用程序吧。 通过 webob 对请求和响应进行包装,我们可以让处理业务逻辑的代码接受 Request对象,返回 Response 对象,这样我们可以简化 hello_app 本身的代码, 让它更加专注于业务逻辑:

   1 # -*- coding: utf-8 -*-
   2 from wsgiref import simple_server
   3 from webob import Request, Response
   4 
   5 # 我们顺便增加了一个功能,就是根据用户在 URL 后面传递的参数
   6 # 显示相应的内容
   7 def hello_app(request):
   8     content = []
   9     # 获取 get 请求的参数
  10     content.append('Hello %s'%request.GET['name'])
  11     # 输出所有 environ 变量
  12     for key, value in request.environ.items():
  13         content.append('%s : %s' % (key, value))
  14 
  15     response = Response(body='\n'.join(content))
  16     response.headers['content-type'] = 'text/plain'
  17     return response
  18 
  19 # 对请求和响应进行包装
  20 def wsgi_wrapper(environ, start_response):
  21     request = Request(environ)
  22     response = hello_app(request)
  23     # response 对象本身也实现了与 wsgi 服务器之间通讯的协议,
  24     # 所以可以帮我们处理与web服务器之间的交互。
  25     return response(environ, start_response)
  26 
  27 server = simple_server.make_server('localhost', 8080, wsgi_wrapper)
  28 server.serve_forever()

这样,执行这个程序后,当我们访问地址 http://localhost:8080/hello?name=python 时, 就可以看到 hello python 和后面跟的一串 wsgi 的环境变量了。

PCS304-webmodule-hellopython.png

为了让 wsgi_wrapper 更加通用一点,可以把它设计成装饰器(参考PCS108函式一节中的装饰器部分) 的形式:

   1 # -*- coding: utf-8 -*-
   2 from wsgiref import simple_server
   3 from webob import Request, Response
   4 
   5 # 写成装饰器的 wsgi_wrapper
   6 def wsgi_wrapper(func):
   7     def new_func(environ, start_response):
   8         request = Request(environ)
   9         response = func(request)
  10         return response(environ, start_response)
  11     new_func.__name__ = func.__name__
  12     new_func.__doc__ = func.__doc__
  13     return new_func
  14 
  15 # 应用程序
  16 @wsgi_wrapper
  17 def hello_app(request):
  18     content = []
  19     content.append('Hello %s'%request.GET['name'])
  20     for key, value in request.environ.items():
  21         content.append('%s : %s' % (key, value))
  22 
  23     response = Response(body='\n'.join(content))
  24     response.headers['content-type'] = 'text/plain'
  25     return response
  26 
  27 server = simple_server.make_server('localhost', 8080, hello_app)
  28 server.serve_forever()

其功能和前面的程序完全等价。

模板

不过显然,现实生活中的web程序很少像这个一样直接返回这么简单的纯文本信息,虽然 ajax 的时代,返回 json、xml 这样的特定格式数据的web程序也越来越多。 但毕竟,很多时候还是需要返回一个 html 页面的,而把一个复杂的 html 页面 代码直接写到Python代码里面就太丑陋了,有没有办法可以把它和业务代码分离开来, 再通过某种方式向其中填充内容呢?

模板就是对这个问题的答案。

Python里面的模板引擎主要有mako、genshi、jinja等。mako 主要特点在于模板里面 可以比较方便的嵌入Python代码,而且执行效率一流;genshi 的特点在于基于 xml, 非常简单易懂的模板语法,对于热爱xhtml的朋友来说是很好的选择,同时也可以嵌入Python 代码,实现一些复杂的展现逻辑;jinja 和 genshi 一样拥有很简单的模板语法,只是不 依赖于 xml 的格式,同样很适合设计人员直接进行模板的制作,同时也可以嵌入Python 代码实现一些复杂的展现逻辑。

小白看 mako 挺顺眼的,于是就钻研了它一把。 可以到这里 http://pypi.python.org/pypi/Mako ( 精巧地址: http://bit.ly/4wqXP6 ) 找到最新版本的下载地址,下载解压, 然后执行 python setup.py install 就可以了。

下面的代码就是写的一个简单的 mako 模板文件,里面直接输出一个 name 对象, 并使用 Python 的 for 循环来把传递给它的 data 字典的内容填充到一个html列表

## -*- coding: utf-8 -*-
<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>

先把上面这些代码保存到 simple.html 文件,然后我们要做的就是给模板传递name和data两个对象,渲染模板, 然后输出最终的 html 代码。下面三句代码可以完成这些工作:

   1 # -*- coding: utf-8 -*-
   2 # 导入模板对象
   3 from mako.template import Template
   4 # 使用模板文件名构造模板对象
   5 tmpl = Template(filename='./simple.html', output_encoding='utf-8')
   6 # 构造一个简单的字典填充模板,并print出来
   7 print tmpl.render(name='python', data = {'a':1, 'b':2})

保存成 test_template.py,并执行这个程序,就可以看到通过模板输出的 html 代码了。

$ python test_template.py 
<html>
  <head>
    <title>简单mako模板</title>
  </head>
  <body>
    <h5>Hello python!</h5>
    <ul>
      <li>
        a - 1
      <li>
      <li>
        b - 2
      <li>
    </ul>
  </body>
</html>

功能研究清楚了,小白开始试着把它整合到这个web应用中来, 这个时候小白发现,需要改动的部分主要集中在 hello_app 这个函数了, 因为业务逻辑都集中在这里进行了,在加入模板的同时,小白决定进行一次小小的重构, 把 wsgi_wrapper 单独放到通用模块 utils.py:

   1 # -*- coding: utf-8 -*-
   2 from webob import Request
   3 
   4 def wsgi_wrapper(func):
   5     def new_func(environ, start_response):
   6         request = Request(environ)
   7         response = func(request)
   8         return response(environ, start_response)
   9     new_func.__name__ = func.__name__
  10     new_func.__doc__ = func.__doc__
  11     return new_func

把 hello_app 给彻底独立出来,形成单独的模块 controller.py :

   1 # -*- coding: utf-8 -*-
   2 from utils import wsgi_wrapper
   3 from webob import Response
   4 from mako import Template
   5 
   6 # 整合了模板功能的 hello_app
   7 @wsgi_wrapper
   8 def hello_app(request):
   9     tmpl = Template(filename='./simple.html', output_encoding='utf-8')
  10     content = tmpl.render(name=request.GET['name'], data=request.environ)
  11     return Response(body=content)

这样 main.py 就变成这样了:

   1 # -*- coding: utf-8 -*-
   2 from wsgiref import simple_server
   3 from controller import hello_app
   4 
   5 server = simple_server.make_server('localhost', 8080, hello_app)
   6 server.serve_forever()

执行 python main.py ,并访问 http://localhost:8080/hello?name=python 你就可以看到通过模板输出的页面了。

PCS304-webmodule-template.png

重构之后,结构当然是清晰了很多,不过对于我们这个代码量来说,可能更大的意义是 方便我贴代码了,嘿嘿。

现在三个文件 Python 代码加起来是 21 行,比最开始增加了 11 行 ;-)

ORM

作为一个动态网站,有很多数据需要持久存储,关系数据库在这方面通常都是不二选择。

有了前面的愉快经历,小白遇到问题都习惯性地要了解一下有没有现成的工具可以使用, 当小白向行者问道如何才能简化对关系数据库的操作时,行者郑重向小白推荐了 sqlalchemy 这个库。

sqlalchemy 是一个 ORM (对象-关系映射)库,提供Python对象与关系数据库之间的映射, 也就是程序只需要对 Python 对象进行操作,sqlalchemy 自动把这些操作映射为 关系数据库的操作,并执行之。 可以直接使用Python代码描述数据结构,然后 sqlalchemy 自动创建对应的表, 通过实例化 Python 对象,设置其属性, sqlalchemy 就自动执行插入或者更新操作, 还可以直接调用 Python 对象的方法进行查询,通过参数进行过滤等等。

sqlalchemy 还可以自动映射 Python 对象的继承,可以实现eager loading、lazy loading, 可以直接将 Model 映射到自定义的 SQL 语句,支持n多的数据库等等等等。 可以说 sqlalchemy 既有不输于 Hibernate 的强大功能,同时不失 Python 的简洁优雅。

在这里 http://www.sqlalchemy.org/download.html (精巧地址: http://bit.ly/3iyaV0 )找到最新版本的下载地址,下载解压, 然后执行 python setup.py install 进行安装。

代码胜千言,sqlalchemy 到底可以做什么,直接上代码展示下 sqlalchemy 的基本功能吧:

   1 # -*- coding: utf-8 -*-
   2 from sqlalchemy import *
   3 from sqlalchemy.orm import sessionmaker, scoped_session
   4 from sqlalchemy.ext.declarative import declarative_base
   5 
   6 # 创建数据库引擎,这里我们直接使用 Python2.5 自带的数据库引擎:sqlite,
   7 # 直接在当前目录下建立名为 data.db 的数据库
   8 engine = create_engine('sqlite:///data.db')
   9 # sqlalchemy 中所有数据库操作都要由某个session来进行管理
  10 # 关于 session 的详细信息请参考:http://www.sqlalchemy.org/docs/05/session.html
  11 Session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
  12 Base = declarative_base()
  13 
  14 class Dictionary(Base):
  15     # Python 对象对应关系数据库的表名
  16     __tablename__ = 't_dictionary'
  17     # 定义自动,参数含义分别为:数据库字段名,字段类型,其他选项
  18     key = Column('key', String(255), primary_key=True)
  19     value =  Column('value', String(255))
  20 
  21 # 创建数据库
  22 Base.metadata.create_all(engine)
  23 
  24 session = Session()
  25 for item in ['python','ruby','java']:
  26     # 构造一个对象
  27     dictionary = Dictionary(key=item, value=item.upper())
  28     # 告诉 sqlalchemy ,将该对象加到数据库
  29     session.add(dictionary)
  30 
  31 # 提交session,在这里才真正执行数据库的操作,添加三条记录到数据库
  32 session.commit()
  33 
  34 # 查询数据库中Dictionary对象对应的数据
  35 for dictionary in session.query(Dictionary):
  36     print dictionary.key, dictionary.value

直接执行这个程序就可以了,中间会建立数据库,插入测试数据,最后输出的就是数据库中的内容了。 不过这只是个单独的 demo,为了方便对 sqlalchemy 进行试验,也方便最后整合到 web 应用里面去, 还需要先对它的代码结构进行一下重构。

首先提取出一个公用的 model.py 文件。

   1 # -*- coding: utf-8 -*-
   2 from sqlalchemy import *
   3 from sqlalchemy.orm import sessionmaker, scoped_session
   4 from sqlalchemy.ext.declarative import declarative_base
   5 
   6 engine = create_engine('sqlite:///data.db')
   7 Session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
   8 Base = declarative_base()
   9 
  10 class Dictionary(Base):
  11     __tablename__ = 't_dictionary'
  12     key = Column('key', String(255), primary_key=True)
  13     value =  Column('value', String(255))

因为创建数据库以及初始化测试数据的工作只需要做一次就可以了,我们把这个部分代码放到一个 单独的程序 createdb.py 里面去。

   1 # -*- coding: utf-8 -*-
   2 # 导入公用的 model 模块
   3 from model import Base, Session, Dictionary, engine
   4 
   5 # 创建数据库
   6 Base.metadata.create_all(engine)
   7 
   8 # 插入初始数据
   9 session = Session()
  10 for item in ['python','ruby','java']:
  11     dictionary = Dictionary(key=item, value=item.upper())
  12     session.add(dictionary)
  13 
  14 session.commit()

现在打开 Python shell ,导入 models 模块,对照 sqlalchemy 的文档,就可以试验 sqlalchemy 的各种功能了。

{{{# >>> from model import Session, Dictionary >>> sess = Session() >>> query = sess.query(Dictionary) >>> for dictionary in query.all(): ... print dictionary.key ... python ruby java }}}

然后再将这个功能也整合进我们的 web 程序里面来吧,修改 controller.py 文件:

   1 # -*- coding: utf-8 -*-
   2 from utils import wsgi_wrapper
   3 from webob import Response
   4 from mako.template import Template
   5 # 导入公用的 model 模块
   6 from model import Session, Dictionary
   7 
   8 @wsgi_wrapper
   9 def hello_app(request):
  10     session = Session()
  11     # 查询到所有 Dictionary 对象
  12     dictionaries = session.query(Dictionary)
  13     # 然后根据 Dictionary 对象的 key、value 属性把列表转换成一个字典
  14     data = dict([(dictionary.key, dictionary.value) for dictionary in dictionaries])
  15 
  16     tmpl = Template(filename='./simple.html', output_encoding='utf-8')
  17     content = tmpl.render(name=request.GET['name'], data=data)
  18     return Response(body=content)

执行 python main.py ,并访问 http://localhost:8080/hello?name=python,就可以看到从数据库中 取出来的三条测试数据了。

PCS304-webmodule-db.png

URL 分发

一个web应用程序往往要提供很多资源的展示及其相应的操作,比如查看一个列表、 修改一篇文章,上传一副照片, 那么客户如何访问到不同的功能,web应用程序又如何根据 客户端的请求提供不同的功能呢?答案就是 URL 。 给不同的资源设计不同的 URL, 客户端请求这个 URL,web应用程序再根据用户请求的 URL 定位到具体功能并执行之。

提供一个干净的 URL 有很多好处,比如可读性,通过 URL 就可以大概了解其提供什么功能;而且 用户容易记住也方便直接输入;另外设计良好的 URL 一般都更短小精悍,对搜索引擎也 更友好。

当然,处理 URL 的定义与映射同样有现成的工具可以使用,selector 是其中 一个比较简单易用的一个。

在这里 http://pypi.python.org/pypi/selector (精巧地址: http://bit.ly/3gJAOo) 找到最新版本的下载地址,下载解压, 然后执行 python setup.py install 就可以了。

先来看一个 selector 的示例:

{{{# >>> from selector import Selector >>> # 写两个测试函数 >>> def get_app():pass >>> def post_app():pass >>> # 配置 URL 与函数的映射,并且可以根据不同请求方法映射到不同的函数 >>> # 这里我们配置了一个 blog 的 URL,可通过年月日来访问对应的 blog 列表,通过文章名字来访问文章详细信息 >>> mappings = [('/hello/{name}', {'GET':get_app, 'POST':post_app})] >>> # 构建selector对象 >>> s = Selector(mappings) >>> # 解析 URL,返回映射到的函数,以及从URL中解析出来的数据等。 >>> s.select('/hello/Python', 'GET') (<function get_app at 0x00D58870>, {'name': 'Python'}, ['POST', 'GET'], '/hell o/Python') >>> s.select('/hello/Python', 'POST') (<function post_app at 0x00D58870>, {'name': 'Python'}, ['POST', 'GET'], '/hell o/Python') }}}

selector 首先使用配置的规则去解析 URL ,如果匹配,再根据请求方法找到对应的函数, URL 的规则可以包含一些动态的部分,使用大括号进行区分,比如上面的 {name:alpha} , 冒号前面的是名字,冒号后面的部分定义其格式,冒号以及冒号后面的部分可选的。 解析的过程中,selector 先获取到URL路径中的这一段,如果指定了格式则进行匹配, 如果匹配就把匹配到的内容以前面指定的名字存放起来。 解析 URL 的过程中获得的数据存放到 environ['wsgiorg.routing_args'] 中, 然后后续的应用程序就可以使用这些参数进行相应的操作。

我们首先把 urls 的配置单独放到一个文件中,urls.py:

   1 # -*- coding: utf-8 -*-
   2 from controller import hello_app
   3 mappings = [('/hello/{name}', {'GET':hello_app})]

修改 main.py:

   1 # -*- coding: utf-8 -*-
   2 from wsgiref import simple_server
   3 from urls import mappings
   4 from selector import Selector
   5 
   6 # 构建 url 分发器
   7 app = Selector(mappings)
   8 server = simple_server.make_server('localhost', 8080, app)
   9 server.serve_forever()

然后,在 hello_app 中就可以通过 environ['wsgiorg.routing_args'] 获取到 name 参数了, 不过在 wsgi_wrapper 其实还可以进一步简化 hello_app 的工作: 直接把解析得到的参数当作函数参数传过去!修改 utils.py:

   1 from webob import Request
   2 
   3 def wsgi_wrapper(func):
   4     def new_func(environ, start_response):
   5         request = Request(environ)
   6         position_args, keyword_args = environ.get('wsgiorg.routing_args', ((), {}))
   7         response = func(request, *position_args, **keyword_args)
   8         return response(environ, start_response)
   9     new_func.__name__ = func.__name__
  10     new_func.__doc__ = func.__doc__
  11     return new_func

那 hello_app 就可以改成这样了:

   1 ...
   2 @wsgi_wrapper
   3 def hello_app(request, name=''):
   4     ...
   5     content = tmpl.render(name=name, data=data)
   6     return Response(body=content)

执行 main.py ,然后访问 http://localhost:8080/hello/Python ,即可看到和上面那个相同的页面。

部署

web应用最终是要部署到成熟稳当高性能的web服务器上去的, 由于我们这个 web应用程序是基于 wsgi 这样一个标准协议的, 所以部署方式的选择有很多,因为所有针对 wsgi 的部署方法都可以用。 不同部署方式适合不同的场景,下面给出不同部署方式的一些参考信息, 限于篇幅,这里就不一一说明了。

小结

终于像模像样了,现在回过头来看一下,究竟整出来一个什么样的程序呢?小白不禁惊呼:“这不是...不就是...传说中的...框架?!”

当然我们这个框架还只有最基本的一些功能,对于一个完整框架来说至少还有缓存、 session管理、用户验证和权限管理,表单生成与校验等等。 当然创建一个成功且成熟的框架不是一件容易的事情,本文只是小白对 Python web 开发的一个深度探索, 这些代码只是一个 demo,希望对大家理解成熟框架究竟是如何做的有一定的参考价值。 想要更深入的学习,阅读 Python 世界那些成熟框架的源代码是个不错的选择。

ObpLovelyPython/AbtWebModules (last edited 2009-12-25 07:15:55 by localhost)