Routes
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()建立路由的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 ) |
程序输出
1 2 3 4 5 6 7 8 9 10 | 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的名称。下面是一个简单的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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)) |
程序输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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。
参考资料:
Routes Documentation
resource
对于开发 RESTful 类型的服务,Routes提供了快捷的方法定义对资源 resource 的url映射:
map.resource("volume", "volumes"),上面一行主要等效于下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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()创建的路由
1 2 3 4 5 6 7 8 9 | 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:
1 2 | from routes.middleware import RoutesMiddleware wsgi_app = RoutesMiddleware(app, map ) |
middleware会匹配请求的URl,并设置以下WSGI变量:
1 2 3 | 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.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)
程序的入口位于文件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()
执行python main.py后,在终端使用curl命令访问此WSGI服务,可以发现WSGI服务工作正常
1 2 3 | [root@localhost ~] # curl http://localhost:8080/volumes [{ "id" : 1 , "name" : "volume-a" }, { "id" : 2 , "name" : "volume-b" }][root@localhost ~] # [root@localhost ~] # |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理