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)
1 ответ
Ах, я вижу, что происходит.
Вы используете gunicorn
с одним рабочим, и ваше представление обращается к вашему собственному приложению ('http://127.0.0.1:8000/o/token/'
<--> --bind 127.0.0.1:8000
).
Пока runserver
по умолчанию является многопоточным, Gunicorn - нет, и пока он обслуживает запрос, вы выполняете другой запрос в рамках этого запроса... Время тупиковой блокировки!
Либо включите больше воркеров для Gunicorn, либо проведите рефакторинг вашего приложения таким образом, чтобы ему не приходилось делать внутренние запросы через HTTP.