Как мне получить доступ к запросу из обратного вызова сигнала request_finished? - PullRequest
8 голосов
/ 03 марта 2010

Как получить и использовать HttpRequest, используя сигнал request_finished?

Заинтересованы в извлечении URL для целей регистрации.

Текущий код выглядит примерно так:

import logging

def write_to_file(sender, **kwargs):
    logging.debug(type(sender))
    logging.debug(dir(sender))

from django.core.signals import request_finished
request_finished.connect(write_to_file)

генерирует это

2010-03-03 13:18:44,602 DEBUG <type 'type'>
2010-03-03 13:18:44,602 DEBUG ['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', '_get_traceback', 'apply_response_fixes', 'get_response', 'handle_uncaught_exception', 'initLock', 'load_middleware', 'request_class', 'response_fixes']

Ответы [ 3 ]

3 голосов
/ 03 мая 2016

Документация Django для состояния request_finished они предоставляют класс, а не экземпляр (не уверен, почему, было бы более полезно предоставить экземпляр). https://docs.djangoproject.com/en/1.9/ref/signals/#request-finished

Таким образом, сигнал просто дает вам знать, что запрос завершен, но не какой запрос или какая-либо его деталь. У вас есть 2 варианта, чтобы получить запрос. Один из упомянутых выше способов - сохранить запрос в локальном хранилище потока в промежуточном программном обеспечении.

Вот пример, в котором хранится запрос. Но вы можете использовать его для хранения функций, которые будут вызываться в конце.

import collections
import threading

import structlog
from django.utils.cache import patch_vary_headers

logger = structlog.get_logger()

thread_locals = threading.local()


def get_current_request():
    """
    This will return the current request object
    but if the response has been returned the request
    object will be cleaned up
    """
    return getattr(thread_locals, 'request', None)


def request_queue(func, func_id=None, *args, **kwargs):
    """
    Helper function to queue up a function
    and arguments to be run at the end of the request
    if no request, will run straight away
    Usage:
    request_queue(function_to_run, args=(args1, args2), kwargs={'key':'value'})
    """
    request = get_current_request()
    if not request:
        # run the func
        func(*args, **kwargs)
        return
    # else
    # use the supplied id if given
    if not func_id:
        # otherwise use the memory address
        func_id = id(func)
    # store the func and arguments as a tuple under the func id
    request.queue[func_id] = (func, args, kwargs)


class RequestQueueMiddleware(object):
    """
    Use this middleware to get access to the request object
    and to use it to queue functions to run
    """

    def process_request(self, request):
        thread_locals.request = request
        # each request gets a new queue
        request.queue = collections.OrderedDict()

    def process_exception(self, request, exception):
        self.process_queue(request)
        self.cleanup()

    def process_response(self, request, response):
        self.process_queue(request)
        self.cleanup()
        return response

    def cleanup(self):
        try:
            del thread_locals.request
        except AttributeError:
            pass

    def process_queue(self, request):
        if not request:
            request = get_current_request()
        if request and hasattr(request, 'queue'):
            for (func, args, kwargs) in getattr(request, 'queue', {}).itervalues():
                func(*args, **kwargs)
            del request.queue

Функция get_current_request может быть импортирована и использована любым другим способом, когда вам нужен доступ к текущему запросу.

Функция request_queue позволяет поставить в очередь функцию и аргументы для выполнения. Особенность заключается в том, что вы можете поставить в очередь дорогую функцию много раз, и она будет выполнена только один раз.

Таким образом, в вашем обработчике request_finished вы можете вызвать get_current_request, чтобы получить текущий запрос. Но в приведенной выше реализации вам нужно будет удалить код очистки. Я не знаю, будет ли протекать хранение объекта запроса в локальном хранилище потока.

Другой вариант, который не требует промежуточного программного обеспечения, заключается в проверке кадров стека до тех пор, пока вы не найдете запрос.

def get_request():
    """Walk up the stack, return the nearest first argument named "request"."""
    frame = None
    try:
        for f in inspect.stack()[1:]:
            frame = f[0]
            code = frame.f_code
            if code.co_varnames and code.co_varnames[0] == "request":
                return frame.f_locals['request']
    finally:
        del frame

Если у вас есть другая переменная с именем request, она сломается. Может быть адаптирован для проверки типа.

1 голос
/ 15 января 2019

Я думаю, что нашел самое простое решение.

Просматривая официального представителя Django , я нашел единственное использование requests_finished здесь .

Теперь я могу легко переопределить этот метод в точке входа в manage.py:

from django.http.response import HttpResponseBase

class OverriddenHttpResponseBase:

    @staticmethod
    def close(self):
        for closable in self._closable_objects:
            try:
                closable.close()
            except Exception:
                pass
        self.closed = True
        # here you can access your request using self._closable_objects 

        # you can either send it to request_finished 
        signals.request_finished.send(sender=<whatever data you want>)
        # or code your stuff here without using request_finished at all

if __name__ == '__main__':
    HttpResponseBase.close = OverriddenHttpResponseBase.close
0 голосов
/ 03 марта 2010

Попробуйте

sender.request_class.get_full_path()

или

sender.request_class._get_request()

Или, если вы хотите попробовать сделать это с middleware , как предложил mountainswhim, вот фрагмент , который демонстрирует время запроса с использованием middleware .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...