• nova的wsgi介绍【WIP】


    有关openstack的所有的帖子。

    https://www.ustack.com/blog/openstack_hacker/#Nova_Workflow

     网上已经很多的分析文章了:

    http://blog.csdn.net/ddl007/article/details/8602731?utm_source=tuicool

    http://wenku.baidu.com/view/34892729af45b307e87197ec.html

    今天liyong同学问起pecan的原理。

    有必要写篇博客介绍, nova的wsgi。

    改博客边走读代码,边写,是一个野路子开发者的流程。

    不习惯使用 tox.

    $ cat /usr/bin/pyunittest
    python -m unittest `tr / . <<< ${@%.py*}`

    1.  首先对wsgi有个形象的了解:

       nova/tests/unit/api/test_wsgi.py

    """
    Test WSGI basics and provide some helper functions for other WSGI tests.
    """
    
    from nova import test
    
    import routes
    import webob
    
    from nova import wsgi
    
    
    class Test(test.NoDBTestCase):
    
        def test_debug(self):
    
            class Application(wsgi.Application):
                """Dummy application to test debug."""
    
                def __call__(self, environ, start_response):
                    start_response("200", [("X-Test", "checking")])
                    return ['Test result']

    application
    = wsgi.Debug(Application()) result = webob.Request.blank('/').get_response(application) self.assertEqual(result.body, "Test result") def test_router(self): class Application(wsgi.Application): """Test application to call from router.""" def __call__(self, environ, start_response): start_response("200", []) return ['Router result'] class Router(wsgi.Router): """Test router.""" def __init__(self): mapper = routes.Mapper() mapper.connect("/test", controller=Application()) super(Router, self).__init__(mapper) result = webob.Request.blank('/test').get_response(Router()) self.assertEqual(result.body, "Router result") result = webob.Request.blank('/bad').get_response(Router()) self.assertNotEqual(result.body, "Router result")

    自己可以print打桩,了解有疑惑的代码。 然后运行。

    $ pyunittest nova/tests/unit/api/test_wsgi.py

     

    查看wsgi的实现。

    $ git grep -n3 "class Router"
    --
    nova/wsgi.py-440-        print()
    nova/wsgi.py-441-
    nova/wsgi.py-442-
    nova/wsgi.py:443:class Router(object):
    nova/wsgi.py-444-    """WSGI middleware that maps incoming requests to WSGI apps."""
    nova/wsgi.py-445-
    nova/wsgi.py-446-    def __init__(self, mapper):

     

    class Router(object):
        """WSGI middleware that maps incoming requests to WSGI apps."""
    
        def __init__(self, mapper):
            """Create a router for the given routes.Mapper.
    
            Each route in `mapper` must specify a 'controller', which is a
            WSGI app to call.  You'll probably want to specify an 'action' as
            well and have your controller be an object that can route
            the request to the action-specific method.
    
            Examples:
              mapper = routes.Mapper()
              sc = ServerController()
    
              # Explicit mapping of one route to a controller+action
              mapper.connect(None, '/svrlist', controller=sc, action='list')
    
              # Actions are all implicitly defined
              mapper.resource('server', 'servers', controller=sc)
              # Pointing to an arbitrary WSGI app.  You can specify the
              # {path_info:.*} parameter so the target app can be handed just that
              # section of the URL.
              mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
    
            """
            self.map = mapper
            self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                              self.map)
    
        @webob.dec.wsgify(RequestClass=Request)
        def __call__(self, req):
            """Route the incoming request to a controller based on self.map.
    
            If no match, return a 404.
    
            """
            return self._router
    
        @staticmethod
        @webob.dec.wsgify(RequestClass=Request)
        def _dispatch(req):
            """Dispatch the request to the appropriate controller.
    
            Called by self._router after matching the incoming request to a route
            and putting the information into req.environ.  Either returns 404
            or the routed WSGI app's response.
    
            """
            match = req.environ['wsgiorg.routing_args'][1]
            if not match:
                return webob.exc.HTTPNotFound()
            app = match['controller']
            return app

    routes.middleware.RoutesMiddleware
    的帮助文档。
    Constructor information:
     Definition:routes.middleware.RoutesMiddleware(self, wsgi_app, mapper, use_method_override=True, path_info=True, singleton=True)
     Docstring:
        Create a Route middleware object
        
        Using the use_method_override keyword will require Paste to be
        installed, and your application should use Paste's WSGIRequest
        object as it will properly handle POST issues with wsgi.input
        should Routes check it.
        
        If path_info is True, then should a route var contain
        path_info, the SCRIPT_NAME and PATH_INFO will be altered
        accordingly. This should be used with routes like:
        
        .. code-block:: python
        
            map.connect('blog/*path_info', controller='blog', path_info='')

    文档中明确说明: use Paste's WSGIRequest

    nova/wsgi.py 实现了load app功能, 从

    class Loader(object):
        """Used to load WSGI applications from paste configurations."""
    
        def __init__(self, config_path=None):
            """Initialize the loader, and attempt to find the config.
    
            :param config_path: Full or relative path to the paste config.
            :returns: None
    
            """
            self.config_path = None
    
            config_path = config_path or CONF.api_paste_config
            if not os.path.isabs(config_path):
                self.config_path = CONF.find_file(config_path)
            elif os.path.exists(config_path):
                self.config_path = config_path
    
            if not self.config_path:
                raise exception.ConfigNotFound(path=config_path)
    
        def load_app(self, name):
            """Return the paste URLMap wrapped WSGI application.
    
            :param name: Name of the application to load.
            :returns: Paste URLMap object wrapping the requested application.
            :raises: `nova.exception.PasteAppNotFound`
    
            """
            try:
                LOG.debug("Loading app %(name)s from %(path)s",
                          {'name': name, 'path': self.config_path})
                return deploy.loadapp("config:%s" % self.config_path, name=name)
            except LookupError:
                LOG.exception(_LE("Couldn't lookup app: %s"), name)
                raise exception.PasteAppNotFound(name=name, path=self.config_path)

    至于  server 怎么加载wsgi的,请参考:

    nova/tests/unit/test_wsgi.py

    $ pyunittest nova/tests/unit/test_wsgi.TestWSGIServerWithSSL.test_ssl_server

    最终urlmap的事情交给 paste

    请参考:

    http://blog.csdn.net/sonicatnoc/article/details/6539716

    一以下纯拷贝:

    首先python paste是一个WSGI工具包,在WSGI的基础上包装了几层,让应用管理和实现变得方便。说实话,Python Paste的文档做的真差劲!加之python代码可读性本来就不怎么滴,真费劲。

    paste.deploy关键部分留个抓印:

    1)python paste.deploy不能只装个paste.deploy包就可以工作了,还需要paste.script包

    2)python paste.deploy中loadapp给的路径可用os.path.abspath(配置文件相对路径)得到配置文件的绝对路径,否则报找不到relative_to path...没搞明白怎么回事,目前不重要,放过。

    3)python paste.deploy中filter,filter_factory,app,app_factory的规范在文档中都没怎么写清楚,我来给你补上吧:

    - app是一个callable object,接受的参数(environ,start_response),这是paste系统交给application的,符合

    WSGI规范的参数. app需要完成的任务是响应envrion中的请求,准备好响应头和消息体,然后交给start_response处理,并返回响应消息体。

    - filter是一个callable object,其唯一参数是(app),这是WSGI的application对象,见(1),filter需要完成的工作是将application包 装成另一个application(“过滤”),并返回这个包装后的application。

    - app_factory是一个callable object,其接受的参数是一些关于application的配置信息:(global_conf,**kwargs),global_conf是在 ini文件中default section中定义的一系列key-value对,而**kwargs,即一些本地配置,是在ini文件中,app:xxx section中定义的一 系列key-value对。app_factory返回值是一个application对象

    - filter_factory是一个callable object,其接受的参数是一系列关于filter的配置信息:(global_conf,**kwargs),global_conf是在ini文件 中default section中定义的一系列key-value对,而**kwargs,即一些本地配置,是在ini文件中,filter:xxx section中定 义的一系列key-value对。filter_factory返回一个filter对象

    给个例子:

    pastedeploylab.ini:

    [DEFAULT]  
    key1=value1  
    key2=value2  
    key3=values  
    [composite:pdl]  
    use=egg:Paste#urlmap  
    /:root  
    /calc:calc  
    [pipeline:root]  
    pipeline = logrequest showversion  
    [pipeline:calc]  
    pipeline = logrequest calculator  
    [filter:logrequest]  
    username = root  
    password = root123  
    paste.filter_factory = pastedeploylab:LogFilter.factory  
    [app:showversion]  
    version = 1.0.0  
    paste.app_factory = pastedeploylab:ShowVersion.factory  
    [app:calculator]  
    description = This is an "+-*/" Calculator   
    paste.app_factory = pastedeploylab:Calculator.factory 
    View Code

    pastedeploylab.py

    ''''' 
    Created on 2011-6-12 
    @author: Sonic 
    '''  
    import os  
    import webob  
    from webob import Request  
    from webob import Response  
    from paste.deploy import loadapp  
    from wsgiref.simple_server import make_server  
    #Filter  
    class LogFilter():  
        def __init__(self,app):  
            self.app = app  
            pass  
        def __call__(self,environ,start_response):  
            print "filter:LogFilter is called."  
            return self.app(environ,start_response)  
        @classmethod  
        def factory(cls, global_conf, **kwargs):  
            print "in LogFilter.factory", global_conf, kwargs  
            return LogFilter  
    class ShowVersion():  
        def __init__(self):  
            pass  
        def __call__(self,environ,start_response):  
            start_response("200 OK",[("Content-type", "text/plain")])  
            return ["Paste Deploy LAB: Version = 1.0.0",]  
        @classmethod  
        def factory(cls,global_conf,**kwargs):  
            print "in ShowVersion.factory", global_conf, kwargs  
            return ShowVersion()  
    class Calculator():  
        def __init__(self):  
            pass  
          
        def __call__(self,environ,start_response):  
            req = Request(environ)  
            res = Response()  
            res.status = "200 OK"  
            res.content_type = "text/plain"  
            # get operands  
            operator = req.GET.get("operator", None)  
            operand1 = req.GET.get("operand1", None)  
            operand2 = req.GET.get("operand2", None)  
            print req.GET  
            opnd1 = int(operand1)  
            opnd2 = int(operand2)  
            if operator == u'plus':  
                opnd1 = opnd1 + opnd2  
            elif operator == u'minus':  
                opnd1 = opnd1 - opnd2  
            elif operator == u'star':  
                opnd1 = opnd1 * opnd2  
            elif operator == u'slash':  
                opnd1 = opnd1 / opnd2  
            res.body = "%s /nRESULT= %d" % (str(req.GET) , opnd1)  
            return res(environ,start_response)  
        @classmethod  
        def factory(cls,global_conf,**kwargs):  
            print "in Calculator.factory", global_conf, kwargs  
            return Calculator()  
    
    if __name__ == '__main__':  
        configfile="pastedeploylab.ini"  
        appname="pdl"  
        wsgi_app = loadapp("config:%s" % os.path.abspath(configfile), appname)  
        server = make_server('localhost',8088,wsgi_app)  
        server.serve_forever()  
        pass  
    View Code

    使用:

    http://127.0.0.1:8080/

    输出:

    Paste Deploy LAB: Version = 1.0.0

    http://127.0.0.1:8080/calc?operator=plus&operand1=12&operand2=23

    输出:

    UnicodeMultiDict([('operator', u'plus'), ('operand1', u'12'), ('operand2', u'23')])

    RESULT= 35
    ====================================================
    进一步猜测filter的使用过程:在paste deploy库中应该有类似这样的一段代码对application进行重组包装:
    #
    # 假设在ini文件中, 某条pipeline的顺序是filter1, filter2, filter3
    # app, 那么,最终运行的app_real是这样组织的:
    #

    app_real = filter1(filter2(filter3(app)))

    # 在app真正被调用的过程中,filter1.__call__(environ,start_response)被首先调用,若某种检查未通过,filter1做出反应;否则交给filter2__call__(environ,start_response)进一步处理,若某种检查未通过,filter2做出反应,中断链条,否则交给filter3.__call__(environ,start_response)处理,若filter3的某种检查都通过了,最后交给app.__call__(environ,start_response)进行处理。

    看到令贤写了一篇更简单的。

    http://blog.csdn.net/lynn_kong/article/details/8818005

  • 相关阅读:
    Effective C++ 1.让自己习惯C++
    C++Primer 第十九章
    C++Primer 第十八章
    C++Primer 第十七章
    C++Primer 第十六章
    C++Primer 第十五章
    C++Primer 第十四章
    ~~在python中踩过的坑以及问题~~(不断更新)
    ~~Python解释器安装教程及环境变量配置~~
    ~~Py2&Py3~~
  • 原文地址:https://www.cnblogs.com/shaohef/p/4631065.html
Copyright © 2020-2023  润新知