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 osfrom werkzeug.wsgi import SharedDataMiddlewareapp = 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 = Nonefor 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_mimetypef, 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 DispatcherMiddlewarefrom app1.app import app as app1from app2.app import app as app2app = 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)
解析见上面源码中的备注