Routes是使用Python重新实现了Rails Routes系统的库。Routes用来将URL映射到应用的行为,也可以反过来生成URL。对于RESTful,使用Routes可以创建简洁明了的URL。
对于Web开发了说,设计URL以及URL到代码的映射是很关键的。使用直接映射的方法是最原始和最简单的,例如:/dir/file/function => dir.file.function,就可以将URL映射到目录dir下文件file中的函数function,如果使用类,则URL可以设计
成/file/class/method => file.class.method,即映射到指定类中的方法。使用直接映射的方式,定义和增加映射时,比较繁琐;并且当需要修改URL设计或者对应代码时,工作量比较大。使用Routes可以将URL的设计和其映射的代码分离开来。
可以通过pip或easy_install来安装Routes
参考资料:
Routes Documentation
connect
routes库提供的最重要的类是Mapper,此类负责url映射的建立、保存和匹配。可以使用Mapper的方法connect()来建立url映射。
我们先来介绍一下connect()的参数。
name:路由的名称。可以指定希望的路由名,也可以使用None以建立无名路由。
routepath:路由的路径。
controller:控制其的名称,字符串形式
action:行动的名称,字符串形式
conditions:限制,字典形式
requirements:要求,字典形式
下面是一个使用connect()建立路由的例子
from routes import Mapper
mapper = Mapper()
mapper.connect('volume-index',
'/volumes',
controller='volume_controller',
action='index')
mapper.connect('images-show',
'/images',
controller='images_controller',
action='show')
for m in mapper.matchlist:
print('name: \t\t%s' % m.name)
print('routepath: \t%s' % m.routepath)
print('controller: \t%s' % m.defaults['controller'])
print('action: \t\t%s' % m.defaults['action'])
print('-' * 20)
程序输出
name: volume-index
routepath: /volumes
controller: volume_controller
action: index
--------------------
name: images-show
routepath: /images
controller: images_controller
action: show
--------------------
上面的例子建立了两个路由,路由的信息保存在matchlist中。路由的名称分别为volume-index和images-show。
除了上例中使用connect()的方法外,还有其它的用法,表示如下。
map.connect(None, "/error/{action}/{id}", controller="error")定义了一个route,此route没有名字(即无名route),匹配/error/.../...的url,并能够解析出action和id;
map.connect("home", "/", controller="main", action="index")定义了一个route,此route的名称为home,匹配/的url,并设定controller为main,action为index。
map.connect(None, "/error/{action}/{id}", controller="error", requirements={"id": R"\d+"})只匹配id为数字的url。
map.connect("/download/{platform}/{filename}", requirements={"platform": R"windows|mac"})只匹配platform为"windows"或"mac"的url。
map.connect("/user/list", controller="user", action="list",
conditions=dict(method=["GET"]))只匹配method为GET的url。
参考资料:
Setting up routes
match
可以使用match()函数来获取匹配的结果。match()返回的是一个字典,包含controller和action的名称。下面是一个简单的例子
from routes import Mapper
mapper = Mapper()
mapper.connect('volume-index',
'/volumes',
controller='volume_controller',
action='index')
mapper.connect('images-show',
'/images',
controller='images_controller',
action='show')
for m in mapper.matchlist:
print('name: \t\t%s' % m.name)
print('routepath: \t%s' % m.routepath)
print('controller: \t%s' % m.defaults['controller'])
print('action: \t\t%s' % m.defaults['action'])
print('-' * 20)
urls = ('/abc', '/volumes', '/images')
for url in urls:
result = mapper.match(url)
print('result for %s: %r' % (url, result))
程序输出:
name: volume-index
routepath: /volumes
controller: volume_controller
action: index
--------------------
name: images-show
routepath: /images
controller: images_controller
action: show
--------------------
result for /abc: None
result for /volumes: {'action': u'index', 'controller': u'volume_controller'}
result for /images: {'action': u'show', 'controller': u'images_controller'}
在上面的例子中,先创建类Mapper的实例map,通过方法conect()创建url到应用/行为的映射,通过方法match可以进行匹配,并将结果返回。如果匹配失败,会返回None。对于/abc来说,在所有已建立的路由中都没有找到对应的项,所以会返回None,表
示未找到匹配的路由。对于/volumes和/images,都找到了对应的路由,并返回了对应的ontroller和action。
resource
对于开发 RESTful 类型的服务,Routes提供了快捷的方法定义对资源 resource 的url映射:
map.resource("volume", "volumes"),上面一行主要等效于下面的代码:
map.connect("volumes", "/volumes",
controller="volumes", action="create",
conditions=dict(method=["POST"]))
map.connect("volumes", "/volumes",
controller="volumes", action="index",
conditions=dict(method=["GET"]))
map.connect("formatted_volumes", "/volumes.{format}",
controller="volumes", action="index",
conditions=dict(method=["GET"]))
map.connect("new_volume", "/volumes/new",
controller="volumes", action="new",
conditions=dict(method=["GET"]))
map.connect("formatted_new_volume", "/volumes/new.{format}",
controller="volumes", action="new",
conditions=dict(method=["GET"]))
map.connect("/volumes/{id}",
controller="volumes", action="update",
conditions=dict(method=["PUT"]))
map.connect("/volumes/{id}",
controller="volumes", action="delete",
conditions=dict(method=["DELETE"]))
map.connect("edit_volume", "/volumes/{id}/edit",
controller="volumes", action="edit",
conditions=dict(method=["GET"]))
map.connect("formatted_edit_volume", "/volumes/{id}.{format}/edit",
controller="volumes", action="edit",
conditions=dict(method=["GET"]))
map.connect("volume", "/volumes/{id}",
controller="volumes", action="show",
conditions=dict(method=["GET"]))
map.connect("formatted_volume", "/volumes/{id}.{format}",
controller="volumes", action="show",
conditions=dict(method=["GET"]))
对于资源resource来说,主要有四个route名称,对应六个动作:
GET => show
new form => GET
new => POST
edit form => GET
edit => PUT
delete => DELETE
可以使用下面的例子,查看到resource()创建的路由
from routes import Mapper
map = Mapper()
map.resource('volume', 'volumes')
for i in range(len(map.matchlist)):
route = map.matchlist[i]
print('[%r] %r' % (i, route.name))
print('\t regpath: ' + route.regpath)
print('\t method: %r' % route.conditions['method'])
程序输出:
[0] None
regpath: /volumes.%(format)s
method: ['POST']
[1] None
regpath: /volumes
method: ['POST']
[2] 'formatted_volumes'
regpath: /volumes.%(format)s
method: ['GET']
[3] 'volumes'
regpath: /volumes
method: ['GET']
[4] 'formatted_new_volume'
regpath: /volumes/new.%(format)s
method: ['GET']
[5] 'new_volume'
regpath: /volumes/new
method: ['GET']
[6] None
regpath: /volumes/%(id)s.%(format)s
method: ['PUT']
[7] None
regpath: /volumes/%(id)s
method: ['PUT']
[8] None
regpath: /volumes/%(id)s.%(format)s
method: ['DELETE']
[9] None
regpath: /volumes/%(id)s
method: ['DELETE']
[10] 'formatted_edit_volume'
regpath: /volumes/%(id)s/edit.%(format)s
method: ['GET']
[11] 'edit_volume'
regpath: /volumes/%(id)s/edit
method: ['GET']
[12] 'formatted_volume'
regpath: /volumes/%(id)s.%(format)s
method: ['GET']
[13] 'volume'
regpath: /volumes/%(id)s
method: ['GET']
程序输出
参考资料:
RESTful services
RoutesMiddleware
对于一个应用来说,通常会创建一个Mapper实例,然后调用match()方法;而对于一个WSGI框架来说,会使用类似下面的代码来使用RoutesMiddleware:
from routes.middleware import RoutesMiddleware
wsgi_app = RoutesMiddleware(app, map)
middleware会匹配请求的URl,并设置以下WSGI变量:
environ['wsgiorg.routing_args'] = ((url, match))
environ['routes.route'] = route
environ['routes.url'] = url
其中,url是URLGenerator的实例,match是一个包含匹配信息的字典,route是匹配的路由。
下面,使用middleware创建一个WSGI的Web框架,此框架提供了路由建立和路由匹配的功能,应用层只需专注于业务即可。
整个WSGI的Web框架在文件wsgi.py中实现。代码如下:
import json
import routes
import routes.middleware
import webob
import webob.dec
import webob.exc
class APIMapper(routes.Mapper):
pass
class Router(object):
def __init__(self, mapper):
mapper.redirect("", "/")
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.map)
@classmethod
def factory(cls, global_conf={}, **local_conf):
return cls(APIMapper())
@webob.dec.wsgify
def __call__(self, req):
return self._router
@staticmethod
@webob.dec.wsgify
def _dispatch(req):
match = req.environ['wsgiorg.routing_args'][1]
if not match:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
class Request(webob.Request):
pass
class Resource(object):
def __init__(self, controller, deserializer=None, serializer=None):
self.controller = controller
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, request):
action_args = self.get_action_args(request.environ)
action = action_args.pop('action', None)
action_result = self.dispatch(self.controller, action,
request, **action_args)
try:
response = webob.Response(
request=request, body=json.dumps(action_result),
)
return response
except webob.exc.HTTPException as e:
return e
except Exception as e:
print('e: %r' % e)
return action_result
def dispatch(self, obj, action, *args, **kwargs):
try:
method = getattr(obj, action)
except AttributeError:
return {}
return method(*args, **kwargs)
def get_action_args(self, request_environment):
try:
args = request_environment['wsgiorg.routing_args'][1].copy()
except Exception:
return {}
try:
del args['controller']
except KeyError:
pass
try:
del args['format']
except KeyError:
pass
return args
Router_test/wsgi
业务层的代码位于文件router.py中。主要是调用了框架的接口,实现了URL映射的建立,以及业务功能的具体实现。代码如下:
import Router_test.wsgi as wsgi
class VolumeController(object):
def __init__(self):
pass
def index(self, req):
return [
{'id': 1, 'name': 'volume-a'},
{'id': 2, 'name': 'volume-b'}
]
class APIRouter(wsgi.Router):
def __init__(self,mapper):
mapper.resource('volume', 'volumes',
controller=wsgi.Resource(VolumeController()))
super(APIRouter, self).__init__(mapper)
Router_test/router.py
程序的入口位于文件main.py中,主要是通过wsgiref提供的make_server()函数,启动一个WSGI服务。代码如下:
from wsgiref.simple_server import make_server
import Router_test.router as router
wsgi_app = router.APIRouter.factory()
server = make_server('localhost', 8080, wsgi_app)
server.serve_forever()
Router_test/main.py
执行python main.py后,在终端使用curl命令访问此WSGI服务,可以发现WSGI服务工作正常
[root@localhost ~]# curl http://localhost:8080/volumes
[{"id": 1, "name": "volume-a"}, {"id": 2, "name": "volume-b"}][root@localhost ~]#
[root@localhost ~]#