简单介绍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实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
Jun 04 Python
python try except 捕获所有异常的实例
Oct 18 Python
对Pyhon实现静态变量全局变量的方法详解
Jan 11 Python
深入浅析python3中的unicode和bytes问题
Jul 03 Python
Python内置加密模块用法解析
Nov 25 Python
Python跑循环时内存泄露的解决方法
Jan 13 Python
一文详述 Python 中的 property 语法
Sep 01 Python
如何让PyQt5中QWebEngineView与JavaScript交互
Oct 21 Python
多个版本的python共存时使用pip的正确做法
Oct 26 Python
Python爬虫入门案例之爬取二手房源数据
Oct 16 Python
python多次执行绘制条形图
Apr 20 Python
python blinker 信号库
May 04 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
CodeIgniter CLI模式简介
2014/06/17 PHP
PHP使用token防止表单重复提交的方法
2016/04/07 PHP
PHP实现十进制、二进制、八进制和十六进制转换相关函数用法分析
2017/04/25 PHP
详解PHP多个进程配合redis的有序集合实现大文件去重
2019/03/06 PHP
PHP 实现文件压缩解压操作的方法
2019/06/14 PHP
使用SyntaxHighlighter实现HTML高亮显示代码的方法
2010/02/04 Javascript
JS实现在线统计一个页面内鼠标点击次数的方法
2015/02/28 Javascript
Bootstrap每天必学之标签页(Tab)插件
2020/08/09 Javascript
字太多用...代替的方法(两种)
2017/03/15 Javascript
three.js中3D视野的缩放实现代码
2017/11/16 Javascript
解决使用vue.js路由后失效的问题
2018/03/17 Javascript
详解关于webpack多入口热加载很慢的原因
2019/04/24 Javascript
前端路由&amp;webpack基础配置详解
2019/06/10 Javascript
jQuery - AJAX load() 实例用法详解
2019/08/27 jQuery
解决 window.onload 被覆盖的问题方法
2020/01/14 Javascript
Javascript幻灯片播放功能实现过程解析
2020/05/07 Javascript
用vue设计一个日历表
2020/12/03 Vue.js
[03:17]2014DOTA2 国际邀请赛中国区预选赛 四强专访
2014/05/23 DOTA
python命令行参数解析OptionParser类用法实例
2014/10/09 Python
Python比较2个时间大小的实现方法
2018/04/10 Python
python os.path模块常用方法实例详解
2018/09/16 Python
python实现顺时针打印矩阵
2019/03/02 Python
详解Python二维数组与三维数组切片的方法
2019/07/18 Python
如何使用django的MTV开发模式返回一个网页
2019/07/22 Python
python re模块匹配贪婪和非贪婪模式详解
2020/02/11 Python
Python3如何使用tabulate打印数据
2020/09/25 Python
python 检测nginx服务邮件报警的脚本
2020/12/31 Python
Python项目实战之使用Django框架实现支付宝付款功能
2021/02/23 Python
HTML5全屏(Fullscreen)API详细介绍
2015/04/24 HTML / CSS
html5唤醒APP小记
2019/03/27 HTML / CSS
项目副经理岗位职责
2013/12/30 职场文书
酒店中秋节促销方案
2014/01/30 职场文书
高三政治教学反思
2014/02/06 职场文书
公务员个人年终总结
2015/02/12 职场文书
教师信息技术学习心得体会
2016/01/21 职场文书
Canvas跟随鼠标炫彩小球的实现
2021/04/11 Javascript