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'])

程序输出:  

如何在routes中使用pinia routes for_参考资料

如何在routes中使用pinia routes for_字符串_02

[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中实现。代码如下:

如何在routes中使用pinia routes for_参考资料

如何在routes中使用pinia routes for_字符串_02

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映射的建立,以及业务功能的具体实现。代码如下: 

如何在routes中使用pinia routes for_参考资料

如何在routes中使用pinia routes for_字符串_02

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服务。代码如下: 

如何在routes中使用pinia routes for_参考资料

如何在routes中使用pinia routes for_字符串_02

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 ~]#