我试图编写一个中间件来替换响应中的一些数据,从而改变内容长度。对于我们的开发环境,我们希望模拟SSI的行为包括实际的Web服务器,如Nginx或Apache,用于某些静态文件,这些文件不是通过应用程序提供的。我们正在使用werkzeug包含的开发服务器。
这是我到目前为止所拥有的:
class ModifyBodyMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environment, start_response):
def my_start_response(status, headers, exc_info=None):
# change content-length somehow
start_response(status, headers, exc_info)
body = self.app(environment, my_start_response)
body = do_modifications(body)
return body
为简化起见,假设do_modifications
确实用foobar
替换整个内容。我需要实际的主体来修改它,但我还需要以某种方式设置新的内容长度标题。
由于 Goir
答案 0 :(得分:1)
您希望在内容中进行哪些修改?是否应仅对某些响应内容类型进行修改?
这种事情可能会变得复杂。在最简单的情况下,您将延迟调用中间件中的服务器start_response()
,直到您在内存中缓存完整的响应,以便您可以修改它并计算内容长度的新响应头。如果您要返回非常大的响应或流式响应,这会导致问题。
如果仅处理HTML并且只需要在<head>
中进行更改,那么您可以使用缓冲机制,但只有缓冲区才能看到<body>
,或者作为故障保护,某个已缓冲的字节数。如果您希望在</body>
之前插入任何内容,那么您无法避免缓冲所有内容,这通常很糟糕。
最大的问题是你究竟想要做什么。如果知道这一点,那么可能会提供更好的答案或引导您朝着不同的方向做什么。
更新1
FWIW。如果您使用的是mod_wsgi-express,那么您需要做的就是添加--include-file
参数,其参数为ssi.conf
,并在ssi.conf
配置文件片段中添加:
LoadModule filter_module ${MOD_WSGI_MODULES_DIRECTORY}/mod_filter.so
LoadModule include_module ${MOD_WSGI_MODULES_DIRECTORY}/mod_include.so
<Location />
Options +Includes
AddOutputFilterByType INCLUDES text/html
</Location>
如果响应内容类型为text/html
,则会通过Apache INCLUDES
过滤器进行传输并进行适当扩展。
因此你可以使用:
如果目的是最终将Apache的SSI机制定位于生产中,那么这将为您提供更可靠的结果,因为mod_wsgi-express仍在使用Apache来完成繁重的工作。
答案 1 :(得分:0)
好的我找到了一个解决方案,而不是添加另一个中间件我只是覆盖了SharedDataMiddleware并在读取时修改了该文件。
编辑:添加了递归调用以在包含的文件中包含文件。 EDIT2:增加了对#echo SSI的支持
class SharedDataSSIMiddleware(SharedDataMiddleware):
""" Replace SSI includes with the real files on request
"""
ssi_incl_expr = re.compile(r'<!-- *# *include *(virtual|file)=[\'\"]([^\'"]+)[\'\"] *-->')
ssi_echo_expr = re.compile(r'<!-- *# *echo *encoding=[\'\"]([^\'"]+)[\'\"] *var=[\'\"]([^\'"]+)[\'\"] *-->')
def __init__(self, app, exports, disallow=None, cache=True, cache_timeout=60 * 60 * 12, fallback_mimetype='text/plain'):
super(SharedDataSSIMiddleware, self).__init__(app, exports, disallow, cache, cache_timeout, fallback_mimetype)
self.environment = None
def get_included_content(self, path_info, path):
full_path = os.path.join(path_info, path)
with open(full_path) as fp:
data = fp.read()
return self._ssi_include(full_path, data)
def _get_ssi_echo_value(self, encoding, var_name):
return self.environment.get(var_name)
def _ssi_include(self, filename, content):
content = re.sub(
self.ssi_incl_expr,
lambda x: self.get_included_content(os.path.dirname(filename), x.groups()[1]),
content
)
content = re.sub(
self.ssi_echo_expr,
lambda x: self._get_ssi_echo_value(*x.groups()),
content
)
return content
def _opener(self, filename):
file = cStringIO.StringIO()
with open(filename, 'rb') as fp:
content = fp.read()
content = self._ssi_include(filename, content)
file.write(content)
file.flush()
size = file.tell()
file.reset()
return lambda: (file, datetime.utcnow(), size)
def __call__(self, environ, start_response):
self.environment = environ
response = super(SharedDataSSIMiddleware, self).__call__(environ, start_response)
self.environment = None
return response
这将读取实际文件,修改它并返回带有修改数据而不是实际文件的StringIO对象。
不要在werkzeug的static_files
中使用run_simple
参数,这只会添加我们不想要的默认SharedDataMiddleware。
使用上面的中间件包装你的应用程序:
app = SharedDataSSIMiddleware(app, exports={'/foo': 'path'})