简体   繁体   中英

Django rest Framework empty response with gunicorn, but works with runserver

I am trying to implement an Oauth2 authentication using django-oauth-toolkit , and the key exchange works when I am using the built-in django server. However, when I am using gunicorn, I have an empty response. All the other endpoints work fine with gunicorn:

gunicorn command

gunicorn --bind 127.0.0.1:8000 api_name.wsgi

view.py


from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny

import requests


from .serializers import CreateUserSerializer


@api_view(['POST'])
@permission_classes([AllowAny])
def register(request):
    '''
    Registers user to the server. Input should be in the format:
    {"username": "username", "password": "1234abcd"}
    '''
    # Put the data from the request into the serializer
    serializer = CreateUserSerializer(data=request.data)
    # Validate the data
    if serializer.is_valid():
        # If it is valid, save the data (creates a user).
        serializer.save()
        # Then we get a token for the created user.
        # This could be done differentley
        r = requests.post('http://127.0.0.1:8000/o/token/', data={
                'grant_type': 'password',
                'username': request.data['username'],
                'password': request.data['password'],
                'client_id': get_client_id(),
                'client_secret': get_client_secret(),
            },
        )
        return Response(r.json())
    return Response(serializer.errors)


@api_view(['POST'])
@permission_classes([AllowAny])
def token(request):
    '''
    Gets tokens with username and password. Input should be in the format:
    {"username": "username", "password": "1234abcd"}
    '''
    r = requests.post('http://127.0.0.1:8000/o/token/', data={
            'grant_type': 'password',
            'username': request.data['username'],
            'password': request.data['password'],
            'client_id': get_client_id(),
            'client_secret': get_client_secret(),
            },
    )
    return Response(r.json())


@api_view(['POST'])
@permission_classes([AllowAny])
def refresh_token(request):
    '''
    Registers user to the server. Input should be in the format:
    {"refresh_token": "<token>"}
    '''
    r = requests.post('http://127.0.0.1:8000/o/token/', data={
            'grant_type': 'refresh_token',
            'refresh_token': request.data['refresh_token'],
            'client_id': get_client_id(),
            'client_secret': get_client_secret(),
        },
    )
    return Response(r.json())


@api_view(['POST'])
@permission_classes([AllowAny])
def revoke_token(request):
    '''
    Method to revoke tokens.
    {"token": "<token>"}
    '''
    r = requests.post( 'http://127.0.0.1:8000/o/revoke_token/', data={
            'token': request.data['token'],
            'client_id': get_client_id(),
            'client_secret': get_client_secret(),
        },
    )
    # If it goes well return sucess message (would be empty otherwise)
    if r.status_code == requests.codes.ok:
        return Response({'message': 'token revoked'}, r.status_code)
    # Return the error if it goes badly
    return Response(r.json(), r.status_code)

urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('register/', views.register),
    path('token/', views.token),
    path('token/refresh/', views.refresh_token),
    path('token/revoke/', views.revoke_token),
]

when I launch the request:

curl -d "username=new_user&password=12345abcd" "127.0.0.1:8000/authentication/register/" -v

I have the responses:

with django manage.py runserver :

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> POST /authentication/register/ HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 37
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 37 out of 37 bytes
< HTTP/1.1 200 OK
< Date: Tue, 31 Mar 2020 09:55:21 GMT
< Server: WSGIServer/0.2 CPython/3.6.2
< Content-Type: application/json
< Vary: Accept, Authorization, Cookie
< Allow: POST, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Length: 160
< 
* Connection #0 to host 127.0.0.1 left intact

{"access_token":"<access-token>","expires_in":36000,"token_type":"Bearer","scope":"read write","refresh_token":"<refresh-token>"}%

with gunicorn :

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> POST /authentication/register/ HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 37
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 37 out of 37 bytes
* Empty reply from server
* Connection #0 to host 127.0.0.1 left intact

curl: (52) Empty reply from server

gunicorn log

[2020-03-31 12:11:03 +0200] [3639] [INFO] Starting gunicorn 20.0.4
[2020-03-31 12:11:03 +0200] [3639] [INFO] Listening at: http://127.0.0.1:8000 (3639)
[2020-03-31 12:11:03 +0200] [3639] [INFO] Using worker: sync
[2020-03-31 12:11:03 +0200] [3686] [INFO] Booting worker with pid: 3686
[2020-03-31 12:11:46 +0200] [3639] [CRITICAL] WORKER TIMEOUT (pid:3686)

Ah, I see what's happening.

You're using gunicorn with a single worker, and your view calls back to your own app ( 'http://127.0.0.1:8000/o/token/' <--> --bind 127.0.0.1:8000 ).

While runserver is threaded by default, Gunicorn isn't, and while it's serving the request, you're doing another request within that request... Deadlock time!

Either enable more workers for Gunicorn or refactor your app in a way that it doesn't need to make internal requests via HTTP.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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