zoukankan      html  css  js  c++  java
  • werkzeug源码阅读笔记(二) 下

    wsgi.py————第二部分

    pop_path_info()函数
    先测试一下这个函数的作用:

    >>> from werkzeug.wsgi import pop_path_info
    >>> env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b'}
    >>> pop_path_info(env)
    'a'
    >>> env['SCRIPT_NAME']
    '/foo/a'
    >>> env['PATH_INFO']
    '/b'
    >>> pop_path_info(env)
    'b'
    >>> env['SCRIPT_NAME']
    '/foo/a/b'
    >>> env['PATH_INFO']
    ''
    

    从上面的测试,我们可以看到,pop_path_info()函数的作用是:每执行一次,PATH_INFO中减少一部分,同时在SCRIPT_NAME会增加一部分
    代码很简单,分三种情况:

    1. PATH_INTO:类似于`/b/a`形式(去掉开头的‘/’后,剩下的部分有'/')
    2. PATH_INTO:类似于`/a`形式(去掉开头的‘/’后,剩下的部分没有'/')
    3. PATH_INTO:类似于`b/a`形式(不是以'/'开头)
    

    这里就不详细说了。。

    class SharedDataMiddleware()

    这个类是一个WSGI的中间件(关于WSGI中间件,请看WSGI部分),用于在开发环境中提供用户请求的静态文件,这是官方给的例子:

    import os
    from werkzeug.wsgi import SharedDataMiddleware
    
    app = SharedDataMiddleware(app, {
    				'/shared': os.path.join(os.path.dirname(__file__), 'shared')	#‘/shared’:url路径, ‘shared’: 文件夹名,中间的部分为文件夹地址
            })
    

    当使用包的资源时,也有这种用法:

    app = SharedDataMiddleware(app, {
                '/shared': ('myapplication', 'shared_files')   #‘myapplication’是python包,shared_files是这个包里面的静态文件夹
            })
    

    这样我们就可以通过访问host-address/shared来访问静态文件。这个类的作用就是挂载静态文件到根目录

    def __init__(self, app, exports, disallow=None, cache=True,
                     cache_timeout=60 * 60 * 12, fallback_mimetype='text/plain'):
            self.app = app
            self.exports = {}
            self.cache = cache		#header的缓存
            self.cache_timeout = cache_timeout   #缓存保存的时间
            for key, value in iteritems(exports):   
                if isinstance(value, tuple):    #使用包中的资源,见上例
                    loader = self.get_package_loader(*value)
                elif isinstance(value, string_types):    #根据路径找资源,见上例
                    if os.path.isfile(value):
                        loader = self.get_file_loader(value)  #路径是个静态文件
                    else:
                        loader = self.get_directory_loader(value)  #路径是个文件夹
                else:
                    raise TypeError('unknown def %r' % value)
                self.exports[key] = loader
            if disallow is not None:
                from fnmatch import fnmatch
                self.is_allowed = lambda x: not fnmatch(x, disallow)
            self.fallback_mimetype = fallback_mimetype
    

    这是该类的初始化方法。从前面的例子可以看出,传入的exports是个字典。
    在这段代码里面,有几个有意思的地方,我挑出一个,其他的都大同小异:loader = self.get_file_loader(value)
    这个get_file_loader()函数也是该类的方法,我们看一下:

    def get_file_loader(self, filename):
            return lambda x: (os.path.basename(filename), self._opener(filename))
    

    我们分析下结构:这个函数使用了闭包,当调用get_file_loader(filename)的时候,会返回一个匿名函数,如果想获得匿名函数的内容,需要这样:

    test = get_file_loader(filename)
    t = test(x)
    

    我们先不管这个x到底是什么,这时候,得到的t就是匿名函数中的tuple
    然后,我们看这个匿名函数里面的东西,发现_opener()函数,它也是这个类中的方法:

    def _opener(self, filename):
    	return lambda: (
    		open(filename, 'rb'),
    		datetime.utcfromtimestamp(os.path.getmtime(filename)),
    		int(os.path.getsize(filename))
    	)
    

    _opener()函数也返回一个匿名函数,所以同样的,需要获得匿名函数中的内容,需要这样:

    opener = _opener(filename)
    op = opener()
    

    这时候,op就是匿名函数中的tuple
    可是我们还不明白这样写的作用是什么,于是我接着往后看代码,在类中有个__call__函数,我截取相关部分:

    cleaned_path = get_path_info(environ)	#获取path_info的部分
    cleaned_path = cleaned_path.strip('/')
    for sep in os.sep, os.altsep:	#os.sep:用来取代当前操作系统的路径分隔符(windows中是'\',Linux中是‘/’)
    	if sep and sep != '/':	#windows系统
    		cleaned_path = cleaned_path.replace(sep, '/')
    		path = '/' + '/'.join(x for x in cleaned_path.split('/')
    									if x and x != '..')
    file_loader = None
    for search_path, loader in iteritems(self.exports):
     	if search_path == path:
    		real_filename, file_loader = loader(None)   #real_filename=文件名  file_loader=_opener函数返回的匿名函数
    		if file_loader is not None:
    			break
    '''请无视这部分
    	if not search_path.endswith('/'):
    		search_path += '/'
    		if path.startswith(search_path):
    			real_filename, file_loader = loader(path[len(search_path):])
    			if file_loader is not None:
    				break
    	if file_loader is None or not self.is_allowed(real_filename):
    			eturn self.app(environ, start_response)
    '''
    guessed_type = mimetypes.guess_type(real_filename)
    mime_type = guessed_type[0] or self.fallback_mimetype
    f, mtime, file_size = file_loader()	#调用前面的file_loader(),返回_opener()中的匿名函数的内容:文件流,文件创建时间,文件大小
    

    根据__init__()中的定义,self.exports是个字典,所以search_path是地址,loader是前面分析的loader. 具体分析在代码备注中
    阅读__call__函数,我们可以发现,该函数的作用是当服务器程序接受到用户请求后,传给这个中间件,中间件找应用程序,针对这个请求去读取静态文件,再把这个结果(文件)传递给服务器程序,从而完成交互

    class DispatcherMiddleware()
    这个类的作用是:可以在一个应用程序上挂载多个应用程序。好像有点不好理解,举个例子:
    通常在一个项目中,我们只有一个应用程序,此时的结构是这样的:

    app1/

    app.py
    __init.py

    但是,我想做个博客程序,一般需要两个应用程序:一个用来给用户看;一个作为后台,自己可以添加、更改文章等等。 而往往nginx或者apache是不允许同时运行2个应用程序的。这时候,我们就需要在一个应用程序上挂载另一个应用程序了。此时的文件结构是这样的:

    app1/

    app.py (包含运行程序)
    __init__.py (初始化程序)

    app2/

    app.py
    __init__.py

    app.py(用于统一一个应用程序)

    我们便需要这个class DispatcherMiddleware()类了
    app.py中:

    from werkzeug.wsgi import DispatcherMiddleware
    from app1.app import app as app1
    from app2.app import app as app2
    
    app = DispatcherMiddleware(app1, {'/app2': app2})  
    

    使用这个方法,我们可以在WSGI层面上组合多个WSGI应用(可以是Flask应用,可以是Django应用或者其他应用). 每个应用都是独立的,都以各自的配置运行,互不干扰,并在WSGI层面被调度
    其原理是:每个独立的应用都是一个合法的WSGI应用,它们通过调度中间件组合为一个基于前缀调度的大应用.
    了解了这个类的作用,源码就显得特别简单了:

    class DispatcherMiddleware(object):
        def __init__(self, app, mounts=None):
            self.app = app
            self.mounts = mounts or {}
    
        def __call__(self, environ, start_response):
            script = environ.get('PATH_INFO', '')  #获得路径
            path_info = ''
            while '/' in script:
                if script in self.mounts:
                    app = self.mounts[script]   #获得挂载的对应的应用
                    break
                script, last_item = script.rsplit('/', 1)   #获得减去挂载名的路径
                path_info = '/%s%s' % (last_item, path_info)  
            else:
                app = self.mounts.get(script, self.app)  #获得原应用
            original_script_name = environ.get('SCRIPT_NAME', '')
            environ['SCRIPT_NAME'] = original_script_name + script  #更新SCRIPT_NAME
            environ['PATH_INFO'] = path_info   #更新PATH_INFO
            return app(environ, start_response)
    

    解析见上面源码中的备注

  • 相关阅读:
    KOL运营之——如何与网文作者高效地约稿?
    C#利用反射来判断对象是否包含某个属性的实现方法
    MySQL数据库忘记密码
    MySQL基本概念以及简单操作
    .net Mvc框架原理
    跨域资源共享 CORS 详解
    DOM 操作技术【JavaScript高级程序设计第三版】
    关于在"a"标签中添加点击事件的一些问题
    Visual Studio 2017各版本安装包离线下载、安装全解析
    详解Session分布式共享(.NET CORE版)
  • 原文地址:https://www.cnblogs.com/eric-nirnava/p/werkzeug2-2.html
Copyright © 2011-2022 走看看