简单介绍Python下自己编写web框架的一些要点


Posted in Python onApril 29, 2015

在正式开始Web开发前,我们需要编写一个Web框架。

为什么不选择一个现成的Web框架而是自己从头开发呢?我们来考察一下现有的流行的Web框架:

  • Django:一站式开发框架,但不利于定制化;
  • web.py:使用类而不是更简单的函数来处理URL,并且URL映射是单独配置的;
  • Flask:使用@decorator的URL路由不错,但框架对应用程序的代码入侵太强;
  • bottle:缺少根据URL模式进行拦截的功能,不利于做权限检查。

所以,我们综合几种框架的优点,设计一个简单、灵活、入侵性极小的Web框架。
设计Web框架

一个简单的URL框架应该允许以@decorator方式直接把URL映射到函数上:

# 首页:
@get('/')
def index():
  return '<h1>Index page</h1>'

# 带参数的URL:
@get('/user/:id')
def show_user(id):
  user = User.get(id)
  return 'hello, %s' % user.name

有没有@decorator不改变函数行为,也就是说,Web框架的API入侵性很小,你可以直接测试函数show_user(id)而不需要启动Web服务器。

函数可以返回str、unicode以及iterator,这些数据可以直接作为字符串返回给浏览器。

其次,Web框架要支持URL拦截器,这样,我们就可以根据URL做权限检查:

@interceptor('/manage/')
def check_manage_url(next):
  if current_user.isAdmin():
    return next()
  else:
    raise seeother('/signin')

拦截器接受一个next函数,这样,一个拦截器可以决定调用next()继续处理请求还是直接返回。

为了支持MVC,Web框架需要支持模板,但是我们不限定使用哪一种模板,可以选择jinja2,也可以选择mako、Cheetah等等。

要统一模板的接口,函数可以返回dict并配合@view来渲染模板:

@view('index.html')
@get('/')
def index():
  return dict(blogs=get_recent_blogs(), user=get_current_user())

如果需要从form表单或者URL的querystring获取用户输入的数据,就需要访问request对象,如果要设置特定的Content-Type、设置Cookie等,就需要访问response对象。request和response对象应该从一个唯一的ThreadLocal中获取:

@get('/test')
def test():
  input_data = ctx.request.input()
  ctx.response.content_type = 'text/plain'
  ctx.response.set_cookie('name', 'value', expires=3600)
  return 'result'

最后,如果需要重定向、或者返回一个HTTP错误码,最好的方法是直接抛出异常,例如,重定向到登陆页:

raise seeother('/signin')

返回404错误:

raise notfound()

基于以上接口,我们就可以实现Web框架了。
实现Web框架

最基本的几个对象如下:

# transwarp/web.py

# 全局ThreadLocal对象:
ctx = threading.local()

# HTTP错误类:
class HttpError(Exception):
  pass

# request对象:
class Request(object):
  # 根据key返回value:
  def get(self, key, default=None):
    pass

  # 返回key-value的dict:
  def input(self):
    pass

  # 返回URL的path:
  @property
  def path_info(self):
    pass

  # 返回HTTP Headers:
  @property
  def headers(self):
    pass

  # 根据key返回Cookie value:
  def cookie(self, name, default=None):
    pass

# response对象:
class Response(object):
  # 设置header:
  def set_header(self, key, value):
    pass

  # 设置Cookie:
  def set_cookie(self, name, value, max_age=None, expires=None, path='/'):
    pass

  # 设置status:
  @property
  def status(self):
    pass
  @status.setter
  def status(self, value):
    pass

# 定义GET:
def get(path):
  pass

# 定义POST:
def post(path):
  pass

# 定义模板:
def view(path):
  pass

# 定义拦截器:
def interceptor(pattern):
  pass

# 定义模板引擎:
class TemplateEngine(object):
  def __call__(self, path, model):
    pass

# 缺省使用jinja2:
class Jinja2TemplateEngine(TemplateEngine):
  def __init__(self, templ_dir, **kw):
    from jinja2 import Environment, FileSystemLoader
    self._env = Environment(loader=FileSystemLoader(templ_dir), **kw)

  def __call__(self, path, model):
    return self._env.get_template(path).render(**model).encode('utf-8')

把上面的定义填充完毕,我们就只剩下一件事情:定义全局WSGIApplication的类,实现WSGI接口,然后,通过配置启动,就完成了整个Web框架的工作。

设计WSGIApplication要充分考虑开发模式(Development Mode)和产品模式(Production Mode)的区分。在产品模式下,WSGIApplication需要直接提供WSGI接口给服务器,让服务器调用该接口,而在开发模式下,我们更希望能通过app.run()直接启动服务器进行开发调试:

wsgi = WSGIApplication()
if __name__ == '__main__':
  wsgi.run()
else:
  application = wsgi.get_wsgi_application()

因此,WSGIApplication定义如下:

class WSGIApplication(object):
  def __init__(self, document_root=None, **kw):
    pass

  # 添加一个URL定义:
  def add_url(self, func):
    pass

  # 添加一个Interceptor定义:
  def add_interceptor(self, func):
    pass

  # 设置TemplateEngine:
  @property
  def template_engine(self):
    pass

  @template_engine.setter
  def template_engine(self, engine):
    pass

  # 返回WSGI处理函数:
  def get_wsgi_application(self):
    def wsgi(env, start_response):
      pass
    return wsgi

  # 开发模式下直接启动服务器:
  def run(self, port=9000, host='127.0.0.1'):
    from wsgiref.simple_server import make_server
    server = make_server(host, port, self.get_wsgi_application())
    server.serve_forever()
Try

把WSGIApplication类填充完毕,我们就得到了一个完整的Web框架。

Python 相关文章推荐
python中使用urllib2获取http请求状态码的代码例子
Jul 07 Python
python元组操作实例解析
Sep 23 Python
Python文件读写保存操作的示例代码
Sep 14 Python
详解Python匿名函数(lambda函数)
Apr 19 Python
pandas实现DataFrame显示最大行列,不省略显示实例
Dec 26 Python
使用IPython或Spyder将省略号表示的内容完整输出
Apr 20 Python
解决python3.x安装numpy成功但import出错的问题
Nov 17 Python
python 发送邮件的四种方法汇总
Dec 02 Python
安装python依赖包psycopg2来调用postgresql的操作
Jan 01 Python
python抢购软件/插件/脚本附完整源码
Mar 04 Python
Python中Permission denied的解决方案
Apr 02 Python
python数字图像处理实现图像的形变与缩放
Jun 28 Python
编写Python的web框架中的Model的教程
Apr 29 #Python
python获取本地计算机名字的方法
Apr 29 #Python
Python中编写ORM框架的入门指引
Apr 29 #Python
python获取本机mac地址和ip地址的方法
Apr 29 #Python
在Python中编写数据库模块的教程
Apr 29 #Python
Python的gevent框架的入门教程
Apr 29 #Python
在Python中使用HTML模版的教程
Apr 29 #Python
You might like
ADODB结合SMARTY使用~超级强
2006/11/25 PHP
为IP查询添加GOOGLE地图功能的代码
2010/08/08 PHP
PHP __autoload()方法真的影响性能吗?
2012/03/30 PHP
Riot.js 快速的JavaScript单元测试框架
2009/11/09 Javascript
jquery.Jwin.js 基于jquery的弹出层插件代码
2012/05/23 Javascript
JQuery onload、ready概念介绍及使用方法
2013/04/27 Javascript
关闭页面时window.location事件未执行的原因分析及解决方案
2014/09/01 Javascript
基于jQuery全屏焦点图左右切换插件responsiveslides
2015/09/07 Javascript
jQuery.uploadify文件上传组件实例讲解
2016/09/23 Javascript
JavaScript实现实时更新系统时间的实例代码
2017/04/04 Javascript
jquery.validate表单验证插件使用详解
2017/06/21 jQuery
vue中七牛插件使用的实例代码
2017/07/28 Javascript
React Native react-navigation 导航使用详解
2017/12/01 Javascript
浅谈vue的props,data,computed变化对组件更新的影响
2018/01/16 Javascript
vue组件实现弹出框点击显示隐藏效果
2020/10/26 Javascript
JS字符串与二进制的相互转化实例代码详解
2019/06/28 Javascript
Vue中jsx不完全应用指南小结
2019/11/01 Javascript
JavaScript实现矩形块大小任意缩放
2020/08/25 Javascript
JavaScript快速调试的两个技巧
2020/11/04 Javascript
python二分法实现实例
2013/11/21 Python
python中MySQLdb模块用法实例
2014/11/10 Python
Python实现输出程序执行进度百分比的方法
2017/09/16 Python
Python实现的建造者模式示例
2018/08/06 Python
python 解压pkl文件的方法
2018/10/25 Python
如何清空python的变量
2020/07/05 Python
python代数式括号有效性检验示例代码
2020/10/04 Python
一个基于canvas的移动端图片编辑器的实现
2020/10/28 HTML / CSS
法国票务网站:Ticketmaster法国
2018/07/09 全球购物
英国花园、DIY、电器和家居用品商店:Robert Dyas
2019/03/18 全球购物
到底Java是如何传递参数的?是by value或by reference?
2012/07/13 面试题
协议书模板
2014/04/23 职场文书
团队拓展活动总结
2014/08/27 职场文书
小学生暑假安全保证书
2015/07/13 职场文书
建立共青团委员会的请示
2019/04/02 职场文书
python中print格式化输出的问题
2021/04/16 Python
python numpy中multiply与*及matul 的区别说明
2021/05/26 Python