如何在uwsgi中间件中更改响应和内容长度?

时间:2015-08-20 17:45:43

标签: python wsgi werkzeug

我试图编写一个中间件来替换响应中的一些数据,从而改变内容长度。对于我们的开发环境,我们希望模拟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

2 个答案:

答案 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'})
相关问题