【问题标题】:How to change response and content-length in uwsgi middleware?如何更改 uwsgi 中间件中的响应和内容长度?
【发布时间】:2015-11-14 11:45:30
【问题描述】:

我正在尝试编写一个中间件来替换响应中的一些数据,从而改变内容长度。对于我们的开发环境,我们希望模拟 SSI 的行为,包括实际的网络服务器,如 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。我需要实际的正文来修改它,但我还需要以某种方式设置新的内容长度标头。

谢谢 高尔

【问题讨论】:

    标签: python wsgi werkzeug


    【解决方案1】:

    您想在内容的哪个位置进行修改?是否应该只对某些响应内容类型进行修改?

    这类事情会变得复杂。在最简单的情况下,您将延迟调用中间件中的服务器start_response(),直到您在内存中缓冲了完整的响应,以便您可以修改它并计算新的响应标头的内容长度。但是,如果您返回非常大的响应或流式响应,这会导致问题。

    如果只处理 HTML 并且只需要在<head> 中进行更改,那么您可以使用一种缓冲机制,但只缓冲直到它看到<body>,或者作为故障安全,一定数量的字节有被缓冲了。如果您希望在 </body> 之前插入任何内容,那么您将无法避免缓冲所有内容,这通常很糟糕。

    最大的问题是您实际上要这样做是为了什么。如果知道这一点,那么可能会提供更好的答案或指导您采取不同的方向。


    更新 1

    FWIW。如果您使用的是 mod_wsgi-express,您需要做的就是添加带有 ssi.conf 参数的附加 --include-file 选项,并在 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 来完成繁重的工作。

    【讨论】:

    • 我要做的是在我们的开发环境中为静态 javascript 文件模拟网络服务器 SSI 行为(包括文件),以避免为开发设置网络服务器并保持简单。通常我会使用模板并在模板被渲染时处理这个问题,但这些文件不应该通过实际的应用程序运行。在生产环境中,它们由网络服务器直接处理,甚至无需访问实际应用程序。该应用程序没有 url 规则,甚至不知道这些文件存在。
    • 如果您使用 Apache 作为服务器,您用于 SSI 的配置是什么?如果您要使用 mod_wsgi-express (pypi.python.org/pypi/mod_wsgi) 进行开发,那么诱使它执行 SSI 可能并不难,因为它在幕后使用 Apache,而您无需知道它使用的生成配置可以增强为做额外的事情。
    • 我想让开发环境的设置尽可能简单和轻量级。我找到了解决问题的方法。将在 #echo 支持下更新它。
    • 我建议您可能会得出这样的结论,即由于涉及 mod_wsgi 和 Apache,它的重量并不轻。 mod_wsgi-express 的全部意义在于它是 Apache 和 mod_wsgi 的精简和适当调整的组合,您不必摆弄 Apache 配置,这种 SSI 案例是一个例外,因为 SSI 现在很少使用。所以任何认为它臃肿的想法都是错误的。那个可以 pip install mod_wsgi-express 安装方便,运行和gunicorn一样容易。
    • mod_wsgi-express 具有特殊的开发功能,例如自动代码重新加载、pdb 事后调试、分析、覆盖率分析、请求/响应日志记录等,所有这些都是内置的,实际上使它成为比其他主要的更好的选择流 WSGI 服务器进行开发。
    【解决方案2】:

    好的,我找到了解决方案,我没有添加另一个中间件,而是覆盖 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 的 run_simple 中使用 static_files 参数,这只会在此处添加我们不需要的默认 SharedDataMiddleware。

    只需使用上面的中间件包装您的应用程序:

    app = SharedDataSSIMiddleware(app, exports={'/foo': 'path'})
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-10
      • 1970-01-01
      • 2015-04-30
      • 1970-01-01
      相关资源
      最近更新 更多