Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the most elegant way to convert requests' response to DRF response in Django?

Consider the following flow:

public client ----> DRF API on Service A ------> DRF API on Service B

Some of the DRF API on Service A merely proxying to Service B, so in the particular API on Service A looks like this:

class SomeServiceAPI(APIView):
    def get(request):
        resp = requests.get('http://service-b.com/api/...')
        return Response(resp.json())

While this works on normal status, but it has a few issues:

  1. It doesn't proxy the actual status code from service b.
  2. Unnecessary round-trip of json serialization within Response()
  3. If service b returns a non-json error, service does not return actual error from service b.

The question is, is there a better way to do it? I had a look at Django Rest Framework Proxy project, but I am not entirely sure if it actually suits my use case here.

like image 391
James Lin Avatar asked Aug 10 '17 09:08

James Lin


People also ask

What is the typical order of an HTTP request response cycle in Django?

Whenever a request comes into Django, it is handled by middlewares. When the Django server starts, the first thing it loads after settings.py is middlewares. The Request is processed by various middlewares one at a time. So, from the above list, when the request comes it will be passed through the security middleware.

Is Django REST framework DRF used for building Web APIs?

Django REST framework (DRF) is a powerful and flexible toolkit for building Web APIs. Its main benefit is that it makes serialization much easier.

What is request Meta in Django?

HttpRequest. META. A dictionary containing all available HTTP headers. Available headers depend on the client and server, but here are some examples: CONTENT_LENGTH – The length of the request body (as a string).


2 Answers

  1. You can solve the status code part by modifying your Response:

    return Response(resp.json(), status=resp.status_code)
    
  2. For the second part though, this is the essence of Proxying... (True, sometimes you want to manipulate the request and/or the response in the middleman of the proxy, but what you do is the essence).

Notes:

  • The DRF Proxy that you are suggesting seems to do the job just fine, without the need for you to write a specific view just for the roundtrip.
  • There exist another tool, DRF Reverse Proxy which is a DRF port of Django Revproxy and you may want to consider.

The general idea of both of the above is that you create a URL path specifically to Proxy the path to another API:

DRF Proxy:

Add your proxy to settings.py:

REST_PROXY = {
    'HOST': 'http://service-b.com/api/'
}

In urls.py:

url(
    r'^somewere_in_a/$', 
    ProxyView.as_view(source='somewere_in_b/'), 
    name='a_name'
) 

DRF Reverse Proxy:

Pretty much similar with the above, without the settings part:

url(
    r'^(?P<path>.*)$', 
    ProxyView.as_view(upstream='http://service-b.com/api/somewere_in_b/'),
    name='a_name'
)

Opinion: the DRF Proxy seems more solid...

like image 150
John Moutafis Avatar answered Nov 10 '22 02:11

John Moutafis


I had a look at both existing packages mentioned in John's answer but they don't seem to perfectly suit in my use case, so I have created a simple wrapper to proxy the requests' response to DRF response.

# encoding: utf-8
from __future__ import unicode_literals
from __future__ import absolute_import
from rest_framework.response import Response
from requests.models import Response as RResponse


class InCompatibleError(Exception):
    pass


class DRFResponseWrapper(Response):
    """
    Wraps the requests' response
    """
    def __init__(self, data, *args, **kwargs):
        if not isinstance(data, RResponse):
            raise InCompatibleError

        status = data.status_code
        content_type = data.headers.get('content_type')

        try:
            content = data.json()
        except:
            content = data.content

        super(DRFResponseWrapper, self).__init__(content, status=status, content_type=content_type)

And use as below:

    resp = requests.get(
        '{}://{}/api/v5/business/'.format(settings.SEARCH_HOST_SCHEMA, settings.SEARCH_HOST),
        params=request.query_params
    )
    return DRFResponseWrapper(resp)
like image 20
James Lin Avatar answered Nov 10 '22 03:11

James Lin