Bottle源码阅读笔记(二):路由
前言
程序收到请求后,会根据URL来寻找相应的视图函数,随后由其生成页面发送回给客户端。其中,不同的URL对应着不同的视图函数,这就存在一个映射关系。而处理这个映射关系的功能就叫做路由。路由的实现分为两部分:
1. 生成URL映射关系
2. 根据请求匹配正确的视图函数
本文将围绕这两个部分进行分析。
生成URL映射关系
在Bottle的示例程序中,我们使用@app.route修饰器来将地址'/hello'映射到视图函数hello:
@app.route('/hello')
def hello():
return 'Hello World!'
下面以'/hello'为例子来分析app.route的代码。
def route(self, path=None, method='GET', callback=None, name=None,
apply=None, skip=None, **config):
"""
:param callback: An optional shortcut to avoid the decorator
syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
"""
if callable(path): path, callback = None, path
plugins = makelist(apply)
skiplist = makelist(skip)
def decorator(callback):
if isinstance(callback, basestring): callback = load(callback)
for rule in makelist(path) or yieldroutes(callback):
for verb in makelist(method):
verb = verb.upper()
route = Route(self, rule, verb, callback, name=name,
plugins=plugins, skiplist=skiplist, **config)
self.add_route(route)
return callback
return decorator(callback) if callback else decorator
注意注释和最后一行代码,这种形式的return意味着我们还可以使用app.route('/hello', callback=hello)来实现相同的功能。
route方法将我们定下的路由规则('/hello')和与之相关的HTTP方法(默认为GET)、使用的插件等参数组合成一个Route路由对象,然后通过Router.add()将这个路由添加到处理映射关系的Router对象中。
Router.add()部分代码:
def add(self, rule, method, target, name=None):
# do some something
if is_static and not self.strict_order:
self.static.setdefault(method, {})
self.static[method][self.build(rule)] = (target, None)
retun
# dynamic path parse
在Router对象中,它维护着两个字典:static和dyna_route,分别用来存储静态路由和动态路由(动态路由的映射与静态相似,这里按下不表)。以我们的示例程序为例:
static = {
'GET': {
'/hello': hello,
}
}
可以看出,Bottle最终是用一个字典来保存这个映射关系的,而且还添加了对HTTP方法的支持。所以在Bottle文档中看到可以用多个路由装饰器装饰同一个视图函数,也就不足为奇了。
@app.route('/', method='POST')
@app.route('/', method='GET')
@app.route('/hello', method='GET')
def func():
pass static = {
'GET': {
'/': func,
'/hello': func,
}
'POST': {
'/': func,
}
}
现在映射关系生成了,那么程序在处理请求的时候,它的内部是如何实现匹配的呢?
匹配视图函数
这里提个小插曲,之前在我阅读到这部分的时候,我还没有搞清楚WSGI是什么,所以我在分析这一步时并没有从Bottle.__call__开始,而是直接Ctrl+f寻找static来确定这个字典在哪里被调用了。虽然是个笨方法,但好歹找到了答案。:D
在Router.match中,可以找到关于static的调用。match()先是从envrion变量中取出请求的URL和请求方法,然后直接从static中取值。
def match(self, environ):
''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). '''
verb = environ['REQUEST_METHOD'].upper()
path = environ['PATH_INFO'] or '/'
target = None
if verb == 'HEAD':
methods = ['PROXY', verb, 'GET', 'ANY']
else:
methods = ['PROXY', verb, 'ANY'] for method in methods:
if method in self.static and path in self.static[method]:
target, getargs = self.static[method][path]
return target, getargs(path) if getargs else {}
elif method in self.dyna_regexes:
for combined, rules in self.dyna_regexes[method]:
match = combined(path)
if match:
target, getargs = rules[match.lastindex - 1]
return target, getargs(path) if getargs else {}
接着我不断在用类似的方法寻找上级调用,最终画出了一个这样的函数调用关系链。
以上内容很好地验证了我们上一篇所说的:请求的信息都存储在envrion中,以及Bottle.__call__是我们阅读源程序的入口。
在处理输出的Bottle._handle()中,找到对应的路由后,直接调用路由的call方法,也就是我们的视图函数hello。
def _handle(self, envrion):
route, args = self.router.match(environ)
return route.call(**args)
错误页面
如果程序出错了,Bottl会显示一个默认的错误页面,例如我们熟悉的404页面。
在Bottle内部,对于错误页面的处理跟普通的页面差不多。在Bottle维护着一个专门处理错误页面映射关系的error_handler字典,不过它的键不是HTTP方法或者URL,而是HTTP错误状态码。
类似地,Bottle有专门的@error装饰器让我们自定义错误页面。
def error(self, code=500)
def wrapper(handler):
self.error_handler[int(code)] = handler
return handler
return wrapper
当程序因找不到合适的视图函数,或者其他内部错误,Bottle._handle()会抛出一个HTTPError,然后在Bottle._cast()中会根据错误状态码在error_handler找到对应的错误处理函数,最后将这个结果当作普通页面来处理
Bottle.wsgi()
out = self._cast(self._handle(environ))
Bottle._cast()
if isinstance(out, HTTPError):
out = self.error_handler.get(out.status_code, self.default_error_handler)(out)
return self._cast(out)
最后
Bottle用字典来保存URL映射关系来实现路由和错误页面。现在按照相同的思路,我们来为最简单的WSGI应用添加路由功能和一个简单的错误页面。
class WSGIApp(object): def __init__(self):
self.routes = {} def route(self, path, method='GET'):
def wrapper(callback):
self.routes.setdefault(method, {})
self.routes[method][path] = callback
return callback
return wrapper def error_handler(self, envrion, start_response):
out = [b'Somethind Wrong!']
status = '404 NotFound'
response_headers = [("content-type", "text/plain")]
start_response(status, response_headers)
return out def __call__(self, envrion, start_response):
path = envrion['PATH_INFO']
method = envrion['REQUEST_METHOD'].upper()
if method in self.routes and path in self.routes[method]:
handler = self.routes[method][path]
else:
handler = self.error_handler
return handler(envrion, start_response) app = WSGIApp() @app.route('/')
def simple_app(envrion, start_response):
out = [b'Hello World!']
status = '200 OK'
response_headers = [("content-type", "text/plain")]
start_response(status, response_headers)
return out if __name__ == '__main__':
from wsgiref.simple_server import make_server
with make_server('', 8000, app) as httpd:
print("Server is Running...")
httpd.serve_forever()
Bottle源码阅读笔记(二):路由的更多相关文章
- Bottle源码阅读笔记(一):WSGI
前言 Bottle是一个Python Web框架.整个框架只有一个文件,不到4k行的代码,没有Python标准库以外的依赖,却包含了路由.模板和插件等Web框架常用功能.通过阅读Bottle源码来了解 ...
- werkzeug源码阅读笔记(二) 下
wsgi.py----第二部分 pop_path_info()函数 先测试一下这个函数的作用: >>> from werkzeug.wsgi import pop_path_info ...
- werkzeug源码阅读笔记(二) 上
因为第一部分是关于初始化的部分的,我就没有发布出来~ wsgi.py----第一部分 在分析这个模块之前, 需要了解一下WSGI, 大致了解了之后再继续~ get_current_url()函数 很明 ...
- Detectron2源码阅读笔记-(二)Registry&build_*方法
Trainer解析 我们继续Detectron2代码阅读笔记-(一)中的内容. 上图画出了detectron2文件夹中的三个子文件夹(tools,config,engine)之间的关系.那么剩下的 ...
- Android源码阅读笔记二 消息处理机制
消息处理机制: .MessageQueue: 用来描述消息队列2.Looper:用来创建消息队列3.Handler:用来发送消息队列 初始化: .通过Looper.prepare()创建一个Loope ...
- Apollo源码阅读笔记(二)
Apollo源码阅读笔记(二) 前面 分析了apollo配置设置到Spring的environment的过程,此文继续PropertySourcesProcessor.postProcessBeanF ...
- 【原】FMDB源码阅读(二)
[原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...
- CI框架源码阅读笔记4 引导文件CodeIgniter.php
到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...
- Three.js源码阅读笔记-5
Core::Ray 该类用来表示空间中的“射线”,主要用来进行碰撞检测. THREE.Ray = function ( origin, direction ) { this.origin = ( or ...
随机推荐
- HTML5 进阶系列:indexedDB 数据库
前言 在 HTML5 的本地存储中,有一种叫 indexedDB 的数据库,该数据库是一种存储在客户端本地的 NoSQL 数据库,它可以存储大量的数据.从上篇:HTML5 进阶系列:web Stora ...
- Android开源项目库汇总
最近做了一个Android开源项目库汇总,里面集合了OpenDigg 上的优质的Android开源项目库,方便移动开发人员便捷的找到自己需要的项目工具等,感兴趣的可以到GitHub上给个star. 抽 ...
- js 数字递增特效 仿支付宝我的财富 HTML5
上周五应着公司临时需求,一天的时间解决掉官网(ps:比较简单哈哈),需求里面有一个特效就是数字递增到指定的数值,其实JS写也不复杂的,但是我发现一个js小插件,这个插件轻巧简单,用起来也非常简单实用. ...
- 那些过目不忘的无线端交互设计(DRIBBBLE GIF动态图)
Dribbble精选:Dribbble上令人惊叹的无线端交互设计!来自全球牛人们的奇思妙想,新颖动人的交互在这一张张GIF动态图上一览无余!当然界面一样打动人心,腾出手点赞的同时!记得另存哟:) 作者 ...
- mysql5.6源码自动安装脚本
将脚本与源码安装包放在同一目录下,执行脚本即可(执行脚本会使用yum安装依赖包) 安装完成之后,既可以使用mysql -uroot -p登录 脚本内容如下: [root@mysql src]# c ...
- javascript设计模式详解之命令模式
每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某 ...
- Python装饰器实现几类验证功能做法(续)
:昨天聊了一下构造.今天试了一下.感觉昨天聊的还是不够细化.今天结合代码实现,加以一点补充. 首先观察下面这个例子 from functools import wrapsdef decorator(f ...
- python 使用 'python -m pip install --upgrade pip'提示PermissionError: [WinError 5] 拒绝访问
执行pip install --upgrade pip 提示"PermissionError: [WinError 5] 拒绝访问",如下图,由于更新的用户权限不够,换成管理员运行 ...
- Bash Excercises
1. cat <<EOF #!/bin/bash function printHelp { cat<<EOF Run the Dash vector tests. Usage: ...
- Java中 EvenQueue.invokeLater用法
在Java中Swing是线程不安全的,是单线程的设计,这样的造成结果就是:只能从事件派发线程访问将要在屏幕上绘制的Swing组件.事件派发线程是调用paint和update等回调方法的线程,它还是事件 ...