简体   繁体   English

修改DRF请求对象以调度到其他视图

[英]Modify DRF Request objects for dispatch to other views

Context 上下文

AWS Elastic Beanstalk Worker tiers can be configured to accept a single AWS SQS queue automatically. 可以将AWS Elastic Beanstalk Worker层配置为自动接受单个 AWS SQS队列。 Incoming messages on this queue can be routed to a single endpoint which some instance in the worker tier will respond to; 该队列上的传入消息可以路由到单个端点,工作层中的某些实例将对此进行响应; they show up to the workers as POST events. 它们作为POST事件显示给工作人员。

The software my workers are running is based on Django/DRF. 我的员工正在运行的软件基于Django / DRF。

Goal 目标

I want my worker tier instances to be able to handle more than one type of incoming event. 我希望我的工作层实例能够处理多种类型的传入事件。 Ideally, SQS could be configured to deliver the requests to more than one endpoint, but that appears to be impossible. 理想情况下,可以将SQS配置为将请求传递到多个端点,但这似乎是不可能的。

My implementation 我的实施

class DispatchSerializer(serializers.Serializer):
    "`to` is the name of the destination endpoint; `payload` is the data I want delivered"
    to = serializers.CharField(
        max_length=80,
    )

    payload = serializers.JSONField()

@api_view(http_method_names=['POST'])
@permission_classes([AllowAny])
def dispatch(request):
    """
    Dispatch a request to a sibling endpoint.
    """
    routing_table = {
        ... # this is specific to my application; 
            # it's just a dict of names to views
    }

    serializer = DispatchSerializer(data=request.data)
    if serializer.is_valid(raise_exception=True):
        to = serializer.validated_data['to']
        try:
            view = routing_table[to]
        except KeyError:
            raise Http404()

        # https://github.com/encode/django-rest-framework/blob/master/rest_framework/request.py#L183-L187
        request._full_data = serializer.validated_data['payload']
        return view(request)

    # If this returns other than 200, SQS simply re-attempts the request later. 
    # No returned data is preserved, so we might as well not return any.
    return Response()

As you can see, I attempt to simply replace _full_data attribute of the request with the payload, so that the inner view sees only the data intended for it (which to the dispatch view is the payload). 如您所见,我尝试将请求的_full_data属性简单地替换为有效负载,以便内部视图仅看到针对该请求的数据(在分发视图中为有效负载)。

The problem 问题

This implementation doesn't actually work. 此实现实际上不起作用。 I get errors of this form: 我收到这种形式的错误:

Traceback (most recent call last):
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
    return super(Request, self).__getattribute__(attr)
AttributeError: 'Request' object has no attribute 'body'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
    return super(Request, self).__getattribute__(attr)
AttributeError: 'Request' object has no attribute 'body'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/core/handlers/exception.py", line 39, in inner
    response = get_response(request)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 483, in dispatch
    response = self.handle_exception(exc)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 443, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 480, in dispatch
    response = handler(request, *args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/decorators.py", line 52, in handler
    return func(*args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/worker/views.py", line 196, in dispatch
    return view(request)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 483, in dispatch
    response = self.handle_exception(exc)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 443, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/views.py", line 480, in dispatch
    response = handler(request, *args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/decorators.py", line 52, in handler
    return func(*args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/core/views/mailchimp.py", line 229, in wrapper
    return func(*args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/worker/views.py", line 52, in wrapper
    result = f(log_entry, *args, **kwargs)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/project/django-backend/cardamom/worker/views.py", line 106, in match_one
    user_id = int(request.data['user_id'])
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
    return super(Request, self).__getattribute__(attr)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 186, in data
    self._load_data_and_files()
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 246, in _load_data_and_files
    self._data, self._files = self._parse()
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 290, in _parse
    stream = self.stream
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 378, in __getattribute__
    return super(Request, self).__getattribute__(attr)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 173, in stream
    self._load_stream()
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 270, in _load_stream
    self._stream = six.BytesIO(self.body)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 382, in __getattribute__
    return getattr(self._request, attr)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/rest_framework/request.py", line 382, in __getattribute__
    return getattr(self._request, attr)
  File "/mnt/d/Users/coriolinus/Documents/Toptal/cardamom/.venv/lib/python3.4/site-packages/django/http/request.py", line 264, in body
    raise RawPostDataException("You cannot access body after reading from request's data stream")
django.http.request.RawPostDataException: You cannot access body after reading from request's data stream

Problem restatement 重述问题

The request object is complicated; request对象很复杂; generating a new one is implausible. 产生一个新的是不可行的。 At the same time, we need to modify the data of this one in order to properly pass it along to the next view, so that view only sees the data in its expected format. 同时,我们需要修改该数据,以便将其正确传递给下一个视图,以便该视图只能看到其预期格式的数据。

How can I accomplish this? 我该怎么做?

[edit] Passing an un-modified request doesn't work either [edit]传递未修改的请求也不起作用

I tried editing the above example to remove any modification of the request object, and instead created the following serializer mixin: 我尝试编辑上面的示例以删除对request对象的任何修改,而是创建了以下序列化器mixin:

class UndispatcherMixin:
    """
    Adjust a serializer such that it can accept its normal serialization,
    or alternately the payload of a DispatchSerializer.
    """

    def is_valid(self, raise_exception=False):
        if not hasattr(self, '_validated_data'):
            assert hasattr(self, 'initial_data'), (
                'Cannot call `.is_valid()` as no `data=` keyword argument was '
                'passed when instantiating the serializer instance.'
            )
            ds = DispatchSerializer(data=self.initial_data)

            if ds.is_valid(raise_exception=False):
                self.initial_data = ds.validated_data['payload']
        return super().is_valid(raise_exception=raise_exception)

This mixin was then added to the relevant serializers used by the downstream views. 然后将此混入添加到下游视图使用的相关序列化器中。 Interestingly, it failed with the same django.http.RawPostDataException as before. 有趣的是,它以与以前相同的django.http.RawPostDataException失败。

It's received wisdom all over SO that you can just call your views as normal functions, so long as you pass in a proper request object. SO广为接受,只要您传入适当的request对象,就可以将视图称为普通函数。 This may not actually be true, at least for the case where the view was created by the DRF @api_view decorator. 至少在DRF @api_view装饰器创建视图的情况下,实际上可能并非如此。

Still working on a way to actually solve this. 仍在努力解决这一问题。

For whatever reason--this may be an error in DRF, but I'm not sure--you can't just call an @api_view function from within another one and pass along the request object. 出于某种原因-这可能是DRF中的错误,但我不确定-您不能只从另一个函数中调用@api_view函数并传递request对象。 It just doesn't work. 就是行不通。

What does work is to factor out the code which does the actual work, and call it separately from the @api_view function and also the dispatch view. 什么工作是分解出来完成实际工作的代码,并从单独调用它@api_view功能,还dispatch视图。 That is, something like this: 也就是说,像这样:

def perform_action(request, data):
    """
    Do something

    `request` is included for completeness in case it's necessary,
    but the data normally read from `request.data` should be read instead
    from the `data` argument.
    """
    serializer = ActionSerializer(data=data)
    if serializer.is_valid():
        ... # Whatever your action should be, goes here

@api_view(http_method_names=['POST'])
@permission_classes([AllowAny])
def action(request)
    perform_action(request, request.data)
    return Response()  # 200, no content

@api_view(http_method_names=['POST'])
@permission_classes([AllowAny])
def dispatch(request):
    """
    Dispatch a request to a sibling endpoint.
    """
    routing_table = {
        'action': perform_action, # etc...
    }

    serializer = DispatchSerializer(data=request.data)
    if serializer.is_valid(raise_exception=True):
        to = serializer.validated_data['to']
        try:
            view = routing_table[to]
        except KeyError:
            raise Http404()

        view(request, serializer.validated_data['payload'])

    return Response()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM