• WSGI阅读笔记之三


    上一篇WSGI中主要分析get_current_url的代码,在接下来的wsgi源码中有多个功能类似的函数,作用都是接受一个environ环境变量的值,提取字典中的value。 现在主要理解ShareDataMiddlewar这个中间件。

    首先调用一下它,究竟在作什么,引用werkzeug中官方源码的例子,如下:

    class Shortly(object):
         #省略部分源码
        def wsgi_app(self, environ, start_response):
            request = Request(environ)
            response = self.dispatch_request(request)
            return response(environ, start_response)
    
        def __call__(self, environ, start_response):
            return self.wsgi_app(environ, start_response)
    
    def create_app(redis_host='localhost', redis_port=6379, with_static=True):                    #APP工厂函数
        app = Shortly({
            'redis_host':       redis_host,
            'redis_port':       redis_port
        })
        if with_static:
            app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
                '/static':  os.path.join(os.path.dirname(__file__), 'static')           #将app.wsgi_app实例化为SharedDataMiddleware。
            })
        return app
    
    
    if __name__ == '__main__':
        from werkzeug.serving import run_simple
        app = create_app()                                                                                                     #用工厂函数,创建一个app。
        run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True) 
     #当客户端请求进来时,调用app的__call__方法,__call__方法调用wsgi_app;因为wsgi_app是SharedDataMiddleware的实例,所以调用wsgi_app,即调用       SharedDataMiddleware的__call__方法。最后根据是否存在static静态文件,返回响应信息。
    

    从代码我们可以看出,SharedDataMiddleware 的作用是给包装过的app提供静态文件。

    接下来,分析一下运作的原理:

    class SharedDataMiddleware(object): 
        def __init__(self, app, exports, disallow=None, cache=True,
            cache_timeout=60 * 60 * 12, fallback_mimetype='text/plain'):                   #构造函数,参数app为需要wrap的可调用对象,exports是可迭代的字典对象,即需
                                                                                                                                     要响应的内容                                                          
            self.app = app
            self.exports = {}
            self.cache = cache
            self.cache_timeout = cache_timeout
            for key, value in iteritems(exports):                                                            #for 循环迭代exports中的内容,下面的三个条件语句,根据不同的value类型
                                                                                                                                        对value进行不同的操作
                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)           #上述三个get_..._loader方法都是shareddatamiddleware的方法,loader在下面分析。
                else:
                    raise TypeError('unknown def %r' % value)
                self.exports[key] = loader                    #loader是一个闭包函数!!!
            if disallow is not None:
                from fnmatch import fnmatch                                                               
                self.is_allowed = lambda x: not fnmatch(x, disallow)                             #匿名函数返回bool类型,fnmatch的作用是,判断x是否包含在disallow里面,返回bool型。
            self.fallback_mimetype = fallback_mimetype
    

    以上是构造函数的作用,下面分析_open()函数:

    def _opener(self, filename):
            return lambda: (
                open(filename, 'rb'), 
                datetime.utcfromtimestamp(os.path.getmtime(filename)),
                int(os.path.getsize(filename))                                                                  #返回一个lambda函数,lambda函数返回由文件对象、文件修改的时间戳以及文件
                                                                                                                                  大小的元组
    

    接下来三个方法都是get.loader的形式,基本原理都差不多,分析一下get_package_loader :

    def get_package_loader(self, package, package_path):
            from pkg_resources import DefaultProvider, ResourceManager, 
                get_provider
            loadtime = datetime.utcnow()                                                                     #获得当前时间戳
            provider = get_provider(package)                   #获取package的支持文件,不太懂==,pkg_resources不太了解
            manager = ResourceManager()
            filesystem_bound = isinstance(provider, DefaultProvider)       
    
            def loader(path):                          #这个函数的定义,对构造函数中exports中的value进行操作,这里value是package
                if path is None:
                    return None, None
                path = posixpath.join(package_path, path)
                if not provider.has_resource(path):
                    return None, None
                basename = posixpath.basename(path)                                                    #posixpath模块将获得类posix标准的路径名。
                if filesystem_bound:
                    return basename, self._opener(
                        provider.get_resource_filename(manager, path))
                return basename, lambda: (
                    provider.get_resource_stream(manager, path),
                    loadtime,
                    0
                )
            return loader                           #返回loader函数,loader函数在__call__方法中被调用
    
    重点分析一下call方法
    def __call__(self, environ, start_response):
            cleaned_path = get_path_info(environ)
            if PY2:
                cleaned_path = cleaned_path.encode(get_filesystem_encoding())
            # sanitize the path for non unix systems
            cleaned_path = cleaned_path.strip('/')
            for sep in os.sep, os.altsep:
                if sep and sep != '/':
                    cleaned_path = cleaned_path.replace(sep, '/')
            path = '/' + '/'.join(x for x in cleaned_path.split('/')
                                  if x and x != '..')                                                                      #以上代码,使获取的路径名兼容python2、兼容不同的操作系统
            file_loader = None
            for search_path, loader in iteritems(self.exports):
                if search_path == path:
                    real_filename, file_loader = loader(None)
                    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):
                return self.app(environ, start_response)                                                  #根据上面for循环中的file_loader值,判断,如果file_loader为None直接调用注册的APP
    
            guessed_type = mimetypes.guess_type(real_filename)
            mime_type = guessed_type[0] or self.fallback_mimetype                           #根据os.path.basename(path),获取文件类型
            f, mtime, file_size = file_loader()                                            #调用lambda函数,获得文件对象,文件modify时间,文件大小
    
            headers = [('Date', http_date())]                   
            if self.cache:
                timeout = self.cache_timeout
                etag = self.generate_etag(mtime, file_size, real_filename)
                headers += [
                    ('Etiag', '"%s"' % etag),
                    ('Cache-Control', 'max-age=%d, public' % timeout)
                ]
                if not is_resource_modified(environ, etag, last_modified=mtime):
                    f.close()
                    start_response('304 Not Modified', headers)
                    return []
                headers.append(('Expires', http_date(time() + timeout)))
            else:
                headers.append(('Cache-Control', 'public'))                                                       #处理cache有关的信息
      
            headers.extend((                                                    
                ('Content-Type', mime_type),
                ('Content-Length', str(file_size)),
                ('Last-Modified', http_date(mtime))
            ))
            start_response('200 OK', headers)                       #返回响应头
            return wrap_file(environ, f)                                #返回一个可迭代的文件对象
    

    接下的DispatcherMiddleware可以根据不同路径导向不同的App。

    代码片段的注释是什么鬼,被markdown搞醉了,将就看吧。
    参考:
    http://www.cnblogs.com/eric-nirnava/p/werkzeug2-2.html

  • 相关阅读:
    MySQL 替换和截取指定位置字符串
    notepad++安装XML格式化插件
    个人信用报告,长啥样?怎么查?
    MySQL 查询decimal字段去除自动补零
    mapstruct使用详解
    电商物流行业中的RDC、FDC和TDC分别表示什么意思?发网详解
    借助Proxifier实现内网访问
    C++源码流程图分析!
    FFmpeg中AVPacket处理函数av_free_packet()和av_packet_free()的区别以及用法
    fopen 和它的读写标识 r、r+、rb+、rt+、w+.....
  • 原文地址:https://www.cnblogs.com/hyperionworld/p/5347238.html
Copyright © 2020-2023  润新知