上一篇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