专业的编程技术博客社区

网站首页 > 博客文章 正文

云计算(3)- python routes URL映射管理

baijin 2025-04-05 16:45:31 博客文章 13 ℃ 0 评论

概述:

Routes是python重新实现的Rails routes system,用来将urls映射到应用具体的action上。routes模块不仅仅是在openstack中进行使用,flask框架也是对其进行了封装。

WSGI:

Web服务器网关接口(python Web Server Gateway Interface)是为python语言定义的web服务器和web应用程序或框架之间的一种简单而通用的接口。

Wsgi区分为两个部分:服务器和应用程序。在处理一个WSGI请求时,服务器会为应用程序提供环境信息及一个回调函数,当应用程序完成处理后,透过前述的回调函数,将结果回传给服务器。

WSGI对于app对象有以下要求:

  1. 必须是一个可调用的对象
  2. 接收两个参数必选参数environ,start_response
  3. 返回值必须是可迭代的,用户表示http body。
class APPClass(object):
    def __init__(**kwargs):
        super(APPCLass, cls).__init__(**kwargs)
    def function(environ, start_response):
        pass
    def __call__(environ, start_response):
        pass


environ包含请求的所以信息,下面列举必须包含的变量:

  • REQUEST_METHOD HTTP请求方法,例如GET,POST
  • SCRIPT_NAME URL路径的起始部分对应的应用对象,如果应用程序对象对应服务器的根,那么这个值可以为空字符串。
  • PATH_INFO URL除淋淋起始部分后的剩余部分,用于找到对应的应用程序对象。如果请求的路径是根路径,这个值为空字符串。
  • QUERY_STRING URL路径中?后面的部分,GET的传参
  • CONTENT_LENGTH HTTP请求中的Content-Length部分
  • SERVER_NAME,SERVER_POR
  • SERVER_PROTOCOL 客户端使用的协议,例如HTTP/1.0,HTTP/1.1,它决定了如何处理HTTP请求的头部。

start_response是HTTP相应的开始,被调用时服务器检查headers中的错误,禁止start_response直接将response_headers传递给客户端,它必须把它们存储起来,一直到应用程序第一次迭代返回一个非空数据后,才能将response_headers传递给客户端。

start_response(status, response_headers, exec_info=None)
# wsgiref工具参考文档:https://docs.python.org/zh-tw/3.11/library/wsgiref.html

from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server

# A relatively simple WSGI application. It's going to print out the
# environment dictionary after being updated by setup_testing_defaults
def simple_app(environ, start_response):
    setup_testing_defaults(environ)

    status = '200 OK'
    headers = [('Content-type', 'text/plain; charset=utf-8')]

    start_response(status, headers)

    ret = [("%s: %s\n" % (key, value)).encode("utf-8")
           for key, value in environ.items()]
    return ret

with make_server('', 8000, simple_app) as httpd:
    print("Serving on port 8000...")
    httpd.serve_forever()

WSGI,uWSGI,uwsgi三者的关系:

WSGI是一个Python Web应用程序与Web服务器之间的接口规范,它定义了应用程序和服务器之间的标准接口,使得应用程序可以再不同的Web服务器上运行。

uWSGI是一个Web服务器,用C语言编写的Web应用程序容器。uWSGI服务器可以作为一个独立的应用服务器,也可以与其他Web服务器(比如Nginx,Apache)一起使用,通过WSGI协议与Python应用程序通信。

uwsgi是一个与uWSGI服务器相关的协议,uwsgi协议是一种二进制协议,它定义了uWSGI服务器与应用程序之间的通信协议。uwsgi协议允许uWSGI服务器与应用程序支架进行双向通信,从而提供了性能。

webob

Webob是一个封装了WSGI的请求和应答的python库。

Webob提供了多个对象,大部分都用来处理HTTP的请求,包括对HTTP头的解析,内容的处理,创建WSGI的应答(包括HTTP的状态,头和body)等。Webob最重要的两个类是Request和Response,Request主要用来构造和解析HTTP请求,而Response主要用来构造HTTP的应答。

    def __call__(self, req, *args, **kw):
        """Call this as a WSGI application or with a request"""
        func = self.func
        if func is None:
            if args or kw:
                raise TypeError(
                    "Unbound %s can only be called with the function it "
                    "will wrap" % self.__class__.__name__)
            func = req
            return self.clone(func)
        if isinstance(req, dict):
            if len(args) != 1 or kw:
                raise TypeError(
                    "Calling %r as a WSGI app with the wrong signature" %
                    self.func)
            environ = req
            start_response = args[0]
            req = self.RequestClass(environ)  #定义RequestClass
            req.response = req.ResponseClass()   # 定义ResponseClass
            try:
                args, kw = self._prepare_args(None, None)
                resp = self.call_func(req, *args, **kw)  # 回调函数
            except HTTPException as exc:
                resp = exc
            if resp is None:
                ## FIXME: I'm not sure what this should be?
                resp = req.response
            if isinstance(resp, text_type):
                resp = bytes_(resp, req.charset)
            if isinstance(resp, bytes):
                body = resp
                resp = req.response
                resp.write(body)
            if resp is not req.response:
                resp = req.response.merge_cookies(resp)
            return resp(environ, start_response)  # 返回数据
        else:
            args, kw = self._prepare_args(args, kw)
            return self.call_func(req, *args, **kw)

方法介绍

routes.Mapper.connect()

routes.url()

routes.url_for()

routes.Mapper.resource()

routes.Mapper.redirect()

routes.Mapper.match()

使用方法

  • 注册路由,路由名称'zbj', 路径是 '/clj', controller为 'main', action为 'index'# 匹配到此条路由URL的请求:交由controller类处理,请求预调用的函数index
map.connect('zbj', '/clj', controller='main', action='index')
  • 匹配路由,match返回匹配的数据以及controller类,routematch比match多返回route对象。
map.connect('/home/{action:index|jia}/{id:\d+}', controller='home',action='index')
res = map.match('/home/jia/200')
print(res)  # {'action': 'index', 'id': '200', 'controller': 'home'}
  • 路由集合,具体参数使用方法自行百度
map.resource("message", 'message', controller=controller, path_prefix='/{project_id}',
             name_prefix='lala_', collection={"list_many":'GET', 'create_many':'POST'},
             member={'update_many':"POST", 'delete_many':"POST"},
            new={'preview':"POST"}, 
            parent_resource=dict(member_name='haha', collection_name='heihei'))


范例

参考openstack nova的源码进行梳理

def _create_controller(main_controller, action_controller_list):  # 将实例函数注册到Resourced对象中
    controller = wsgi.Resource(main_controller())
    for ctl in action_controller_list:  # 如果实例中存在被wsgi_action装饰的函数,将该函数属性保存。已备调用
        controller.register_actions(ctl())
    return controller

ROUTE_LIST = (
    ('/flavors', {
        'GET': [flavor_controller, 'index'],
        'POST': [flavor_controller, 'create']
    })
 )

 # 使用偏函数完成类的注册
agents_controller = functools.partial(
    _create_controller, agents.AgentController, [])

# paste加载app的入口
class APIRouterV21(base_wsgi.Router):  # 继承父类
    def __init__(self, custom_routes=None):
    		# 初始化实例,将map对象传给父类
        super(APIRouterV21, self).__init__(nova.api.openstack.ProjectMapper())

        for path, methods in ROUTE_LIST + custom_routes:
            for method, controller_info in methods.items():
                controller = controller_info[0]()   # 控制类
                action = controller_info[1]  #动作
                self.map.create_route(path, method, controller, action)  # 将实例映射到routes.Mapper.connect(),供url映射调用

    @classmethod
    def factory(cls, global_config, **local_config):
        return cls()  #  返回实例对象


class Resource(wsgi.Application):
    def __init__(self, controller):
        self.controller = controller
        self.default_serializers = dict(json=JSONDictSerializer)
        self.wsgi_actions = {}
        if controller:
            self.register_actions(controller)

    def register_actions(self, controller):  # 被wsgi_action装饰器装饰的实例函数
        actions = getattr(controller, 'wsgi_actions', {})  # 如何实现该方法,可以参考元类定义
        for key, method_name in actions.items():
            self.wsgi_actions[key] = getattr(controller, method_name)

    def get_action_args(self, request_environment):  # 解析匹配的参数,包含action,GET参数
        if hasattr(self.controller, 'get_action_args'):
            return self.controller.get_action_args(request_environment)
        try:
            args = request_environment['wsgiorg.routing_args'][1].copy()
        except (KeyError, IndexError, AttributeError):
            return {}
        return args

    def get_body(self, request):
        content_type = request.get_content_type()
        return content_type, request.body

    @webob.dec.wsgify(RequestClass=Request) 
    def __call__(self, request):   # 调用实例,调用真正的应用程序
        context = request.environ['nova.context']
        action_args = self.get_action_args(request.environ)  # 获取get参数
        action = action_args.pop('action', None)

        try:
            content_type, body = self.get_body(request)   # 获取body里面的数据
            accept = request.best_match_content_type()
        except exception.InvalidContentType:
            msg = _("Unsupported Content-Type")
            return Fault(webob.exc.HTTPUnsupportedMediaType(explanation=msg))
        return self._process_stack(request, action, action_args, content_type, body, accept)

    def _process_stack(self, request, action, action_args, content_type, body, accept):
        meth = self.get_method(request, action, content_type, body)
        contents = self._get_request_content(body, request)
        
        action_args.update(contents)
        response = None
        try:
            with ResourceExceptionHandler():
                action_result = self.dispatch(meth, request, action_args)  # 回调实例方法,返回返回值
        except Fault as ex:
            response = ex

        if not response:
            resp_obj = None
            if type(action_result) is dict or action_result is None:
                resp_obj = ResponseObject(action_result)
            elif isinstance(action_result, ResponseObject):
                resp_obj = action_result
            else:
                response = action_result

            if resp_obj:
                if hasattr(meth, 'wsgi_code'):
                    resp_obj._default_code = meth.wsgi_code

            if resp_obj and not response:
                response = resp_obj.serialize(request, accept)
        return response

    def get_method(self, request, action, content_type, body):
        meth = self._get_method(request, action, content_type, body)
        return meth

    def _get_method(self, request, action, content_type, body):
        try:
            meth = getattr(self, action) if not self.controller else getattr(self.controller, action)
            return meth
        except AttributeError:
                raise
        action_name = action_peek(body) if action == 'action' else action
        return (self.wsgi_actions[action_name])

    def dispatch(self, method, request, action_args):
        return method(req=request, **action_args)


class APIMapper(routes.Mapper):
    def routematch(self, url=None, environ=None):
        if url == "":
            result = self._match("", environ)
            return result[0], result[1]
        return routes.Mapper.routematch(self, url, environ)

    def connect(self, *args, **kargs):
        if not kargs['requirements'].get('format'):
            kargs['requirements']['format'] = 'json|xml'
        return routes.Mapper.connect(self, *args, **kargs)

class ProjectMapper(APIMapper):
    def create_route(self, path, method, controller, action):
        project_id_token = self._get_project_id_token()
        self.connect('/%s%s' % (project_id_token, path), conditions=dict(method=[method]), 
                     controller=controller, action=action)
        self.connect(path, conditions=dict(method=[method]), controller=controller, 
                     action=action)


class Request(webob.Request):
    def __init__(self, environ, *args, **kwargs):
        if CONF.wsgi.secure_proxy_ssl_header:
            scheme = environ.get(CONF.wsgi.secure_proxy_ssl_header)
            if scheme:
                environ['wsgi.url_scheme'] = scheme
        super(Request, self).__init__(environ, *args, **kwargs)

class Router(object):
    def __init__(self, mapper):
        self.map = mapper
        self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)   
				# 调取app,将url与实例的映射关系,回调函数传给RoutesMiddleware。该函数会进行url的匹配

    @webob.dec.wsgify(RequestClass=Request)
    def __call__(self, req):
        return self._router  #调取RoutesMiddleware

    @staticmethod
    @webob.dec.wsgify(RequestClass=Request)
    def _dispatch(req):  # 将url匹配成功app进行调用,调取Resouce类的__call__函数
        match = req.environ['wsgiorg.routing_args'][1]
        app = match['controller']  # Resource对象,封装真正的类对象
        return app 


# routes.middleware.py
class RoutesMiddleware(object):
    def __init__(self, wsgi_app, mapper, use_method_override=True, path_info=True, singleton=True):
        self.app = wsgi_app
        self.mapper = mapper
        self.path_info = path_info

    def __call__(self, environ, start_response):
        if self.singleton:  是否是单例模式
            config = request_config()
            config.mapper = self.mapper
            config.environ = environ
            match = config.mapper_dict
            route = config.route
        else:
            results = self.mapper.routematch(environ=environ)
            if results:
                match, route = results[0], results[1]  # 返回匹配参数(包括action,传入的参数),以及route对象
            else:
                match = route = None

        url = URLGenerator(self.mapper, environ)
        environ['wsgiorg.routing_args'] = ((url), match)
        environ['routes.route'] = route
        environ['routes.url'] = url

        response = self.app(environ, start_response)  # 回调函数
        return response


class APIMapper(routes.Mapper):
    def routematch(self, url=None, environ=None):   # 在RoutesMiddler中进行调用
        if url == "":
            result = self._match("", environ)
            return result[0], result[1]
        return routes.Mapper.routematch(self, url, environ)

    def connect(self, *args, **kargs):
        if not kargs['requirements'].get('format'):
            kargs['requirements']['format'] = 'json|xml'
        return routes.Mapper.connect(self, *args, **kargs)


参考文档

routes安装:
https://pypi.org/project/Routes/

routes文档:
https://routes.readthedocs.io/en/latest/

小计:

  1. 元类的使用方法
  2. openstack定时任务
  3. python3 异步函数

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表