Как изменить ответ и длину содержимого в промежуточном программном обеспечении uwsgi?

Я пытаюсь написать промежуточное программное обеспечение, которое заменяет некоторые данные в ответе, тем самым изменяя длину содержимого. Для нашей среды разработки мы хотим имитировать поведение включения 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. Мне нужно фактическое тело, чтобы изменить его, но мне также нужно каким-то образом установить новый заголовок длины содержимого.

Спасибо, Гойр.


person Goir    schedule 20.08.2015    source источник


Ответы (2)


Где в содержании вы хотите внести изменения? Должны ли модификации выполняться только для определенных типов содержимого ответа?

Такие вещи могут быть сложными. В простейшем случае вы отложили бы вызов сервера start_response() в своем промежуточном программном обеспечении, пока не буферизуете полный ответ в памяти, чтобы вы могли изменить его и вычислить новый заголовок ответа для длины содержимого. Однако это вызовет проблемы, если вы возвращаете очень большие ответы или потоковые ответы.

Если вы имеете дело только с HTML и вам нужно внести изменения только в <head>, вы можете использовать механизм, который буферизует, но буферизует только до тех пор, пока не увидит <body>, или в качестве отказоустойчивого, определенное количество байтов было буферизовано. Если вы ожидаете вставить что-либо непосредственно перед </body>, вы не сможете избежать буферизации всего, что обычно плохо.

Большой вопрос в том, для чего вы на самом деле пытаетесь это сделать. Если бы это было известно, возможно, можно было бы дать лучший ответ или направить вас в другом направлении относительно того, что делать.


ОБНОВЛЕНИЕ 1

ПОМОЩЬ. Если вы используете 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 и расширен соответствующим образом.

Таким образом, вы можете использовать:

Если намерение состоит в том, чтобы в конечном итоге нацелиться на механизмы SSI Apache в производственной среде, то это даст вам более надежный результат, поскольку mod_wsgi-express по-прежнему использует Apache для выполнения тяжелой работы.

person Graham Dumpleton    schedule 20.08.2015
comment
Я пытаюсь имитировать поведение SSI веб-серверов (включая файлы) в нашей среде разработки для статических файлов javascript, чтобы избежать настройки веб-сервера для разработки и упростить его. Обычно я бы использовал шаблоны и обрабатывал это при отображении шаблона, но эти файлы не должны запускаться через фактическое приложение. В производственной среде они обрабатываются веб-сервером напрямую, даже не затрагивая фактическое приложение. Приложение не имеет правил URL-адресов и даже не знает, что эти файлы существуют. - person Goir; 21.08.2015
comment
Если вы используете Apache в качестве сервера, какую конфигурацию вы используете для выполнения SSI? Если бы вы использовали mod_wsgi-express (pypi.python.org/pypi/mod_wsgi) для разработки, то может быть не слишком сложно уговорить его выполнять SSI, поскольку он использует Apache под прикрытием без необходимости знать, а сгенерированная конфигурация, которую он использует, может быть дополнена дополнительными функциями. - person Graham Dumpleton; 21.08.2015
comment
Я хочу, чтобы настройка среды разработки была максимально простой и легкой. Я нашел решение проблемы. Обновлю его через секунду с поддержкой #echo. - person Goir; 21.08.2015
comment
Я бы предположил, что вы можете прийти к выводу, что, поскольку задействованы mod_wsgi и Apache, это не легко. Весь смысл mod_wsgi-express в том, что это урезанная и правильно настроенная комбинация Apache и mod_wsgi, где вам не нужно возиться с конфигурациями Apache, этот случай SSI является исключением, поскольку SSI очень редко используется в наши дни. Так что любая идея, что он раздутый, была бы неправильной. То, что можно установить pip install mod_wsgi-express, упрощает установку и запуск так же легко, как и gunicorn. - person Graham Dumpleton; 21.08.2015
comment
То, что mod_wsgi-express имеет специальные функции разработки, такие как автоматическая перезагрузка кода, отладка pdb post mortem, профилирование, анализ покрытия, ведение журнала запросов/ответов и т. д., все это встроено, на самом деле делает его лучшим выбором, чем другие основные потоковые серверы WSGI для разработки. - person Graham Dumpleton; 21.08.2015

Хорошо, я нашел решение, вместо добавления другого промежуточного программного обеспечения я просто перезаписываю 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 с измененными данными вместо фактического файла. Не используйте параметр static_files в run_simple файла werkzeug, это просто добавит стандартное программное обеспечение SharedDataMiddleware, которое нам здесь не нужно.

просто оберните свое приложение промежуточным программным обеспечением выше:

app = SharedDataSSIMiddleware(app, exports={'/foo': 'path'})
person Goir    schedule 21.08.2015