Search code examples
djangodjango-rest-frameworkmanage.pydjango-runserver

Django REST Framework making a request to itself


I have a question that might have a very simple answer.

Everywhere I look it says that the Django development server (manage.py runserver) is multithreaded (https://docs.djangoproject.com/en/3.2/ref/django-admin/) but this is not what I am experiencing.

DISCLAIMER: I know there are other ways to achieve this but I find this solution to be interesting and I cannot understand why it does not work.

I want to create one endpoint in my API that uses another endpoint's response to generate a report, the Views are set up as follows:

from rest_framework.views import APIView
from rest_framework.response import Response

from asgiref.sync import async_to_sync


class View1(APIView):
    def get(self, request, *args, **kwargs):
        response_dict = {"message": "Success!"}
        return Response(response_dict)


class View2(APIView):
    def get(self, request, *args, **kwargs):
        client = Session()
        response = self.get_response(client)
        if response.get("Message") == "Success!":
            return Response("Success!")
        return Response("Failed!")
   
    @async_to_sync
    async def get_response(self, client):
        return await client.get("http://localhost:8000/api/view1"#).json()

Now in my eyes this code looks like it should work because the request to View2 should be picked up by a first worker and the request that View2 is making to View1 should be picked up by a different worker, so that when the request to View1 is completed the request to View2 can be completed.

What I am seeing, using asgiref==3.4.1, Django==3.2.8, and djangorestframework==3.12.4 is that the request for View2 gets stuck just at the line where it makes the request to View1 and I would love to understand why that is the case.


Solution

  • Everywhere I look it says that the Django development server (manage.py runserver) is multithreaded

    If you are talking about:

    --nothreading

    Disables use of threading in the development server. The server is multithreaded by default.

    This option prevents the addition of socketserver.ThreadingMixIn to the wsgiref.simple_server and affects how the server handles network connections. The simple_server is, as the name suggests, a simple WSGI server that comes with Python and is used by Django to run its development server (via python manage.py runserver). On top of that, Django uses a separate thread for its async_to_sync and sync_to_async magic.

    Now, to answer your question:

    In order to handle multiple blocking requests at the same time, you need multiple workers (think of it as multiple servers plus a load balancer). And while running multiple workers usually implies multithreading or multiprocessing, a webserver being multithreaded doesn't automatically imply multiple workers processing requests.

    For your specific code example, I would recommend converting everything to use async. While you should be able to run it fine with the vanilla runserver. If you pip install channels["daphne"] and add "channels" to the INSTALLED_APPS it will replace runserver command with its own that uses Daphne (ASGI) instead of simple WSGI server.