mirror of
https://github.com/django/daphne.git
synced 2025-07-14 01:42:17 +03:00
Implement timeout on request body reading
This commit is contained in:
parent
e18bfed8f3
commit
920882f1da
|
@ -14,3 +14,10 @@ class ResponseLater(Exception):
|
||||||
returning a response.
|
returning a response.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RequestTimeout(Exception):
|
||||||
|
"""
|
||||||
|
Raised when it takes too long to read a request body.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
|
@ -4,6 +4,7 @@ import cgi
|
||||||
import codecs
|
import codecs
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
@ -13,11 +14,11 @@ from django.conf import settings
|
||||||
from django.core import signals
|
from django.core import signals
|
||||||
from django.core.handlers import base
|
from django.core.handlers import base
|
||||||
from django.core.urlresolvers import set_script_prefix
|
from django.core.urlresolvers import set_script_prefix
|
||||||
from django.http import FileResponse, HttpResponseServerError
|
from django.http import FileResponse, HttpResponse, HttpResponseServerError
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
from .exceptions import ResponseLater as ResponseLaterOuter
|
from .exceptions import ResponseLater as ResponseLaterOuter, RequestTimeout
|
||||||
|
|
||||||
logger = logging.getLogger('django.request')
|
logger = logging.getLogger('django.request')
|
||||||
|
|
||||||
|
@ -30,6 +31,10 @@ class AsgiRequest(http.HttpRequest):
|
||||||
|
|
||||||
ResponseLater = ResponseLaterOuter
|
ResponseLater = ResponseLaterOuter
|
||||||
|
|
||||||
|
# Number of seconds until a Request gives up on trying to read a request
|
||||||
|
# body and aborts.
|
||||||
|
body_receive_timeout = 60
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
self.message = message
|
self.message = message
|
||||||
self.reply_channel = self.message.reply_channel
|
self.reply_channel = self.message.reply_channel
|
||||||
|
@ -100,10 +105,15 @@ class AsgiRequest(http.HttpRequest):
|
||||||
# Body handling
|
# Body handling
|
||||||
self._body = message.get("body", b"")
|
self._body = message.get("body", b"")
|
||||||
if message.get("body_channel", None):
|
if message.get("body_channel", None):
|
||||||
|
body_handle_start = time.time()
|
||||||
while True:
|
while True:
|
||||||
# Get the next chunk from the request body channel
|
# Get the next chunk from the request body channel
|
||||||
chunk = None
|
chunk = None
|
||||||
while chunk is None:
|
while chunk is None:
|
||||||
|
# If they take too long, raise request timeout and the handler
|
||||||
|
# will turn it into a response
|
||||||
|
if time.time() - body_handle_start > self.body_receive_timeout:
|
||||||
|
raise RequestTimeout()
|
||||||
_, chunk = message.channel_layer.receive_many(
|
_, chunk = message.channel_layer.receive_many(
|
||||||
[message['body_channel']],
|
[message['body_channel']],
|
||||||
block=True,
|
block=True,
|
||||||
|
@ -184,6 +194,9 @@ class AsgiHandler(base.BaseHandler):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = http.HttpResponseBadRequest()
|
response = http.HttpResponseBadRequest()
|
||||||
|
except RequestTimeout:
|
||||||
|
# Parsing the rquest failed, so the response is a Request Timeout error
|
||||||
|
response = HttpResponse("408 Request Timeout (upload too slow)", status_code=408)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.utils import six
|
||||||
from channels import Channel
|
from channels import Channel
|
||||||
from channels.tests import ChannelTestCase
|
from channels.tests import ChannelTestCase
|
||||||
from channels.handler import AsgiRequest
|
from channels.handler import AsgiRequest
|
||||||
|
from channels.exceptions import RequestTimeout
|
||||||
|
|
||||||
|
|
||||||
class RequestTests(ChannelTestCase):
|
class RequestTests(ChannelTestCase):
|
||||||
|
@ -188,3 +189,30 @@ class RequestTests(ChannelTestCase):
|
||||||
self.assertEqual(request.method, "PUT")
|
self.assertEqual(request.method, "PUT")
|
||||||
self.assertEqual(request.read(3), b"one")
|
self.assertEqual(request.read(3), b"one")
|
||||||
self.assertEqual(request.read(), b"twothree")
|
self.assertEqual(request.read(), b"twothree")
|
||||||
|
|
||||||
|
def test_request_timeout(self):
|
||||||
|
"""
|
||||||
|
Tests that the code correctly gives up after the request body read timeout.
|
||||||
|
"""
|
||||||
|
Channel("test").send({
|
||||||
|
"reply_channel": "test",
|
||||||
|
"http_version": "1.1",
|
||||||
|
"method": "POST",
|
||||||
|
"path": b"/test/",
|
||||||
|
"body": b"there_a",
|
||||||
|
"body_channel": "test-input",
|
||||||
|
"headers": {
|
||||||
|
"host": b"example.com",
|
||||||
|
"content-type": b"application/x-www-form-urlencoded",
|
||||||
|
"content-length": b"21",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
# Say there's more content, but never provide it! Muahahaha!
|
||||||
|
Channel("test-input").send({
|
||||||
|
"content": b"re=fou",
|
||||||
|
"more_content": True,
|
||||||
|
})
|
||||||
|
class VeryImpatientRequest(AsgiRequest):
|
||||||
|
body_receive_timeout = 0
|
||||||
|
with self.assertRaises(RequestTimeout):
|
||||||
|
VeryImpatientRequest(self.get_next_message("test"))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user