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

posted @ 2016-04-03 21:38  Hyperionworld  阅读(231)  评论(0编辑  收藏  举报