[3.1.x] Fixed #31594 -- Added ASGIStaticFilesHandler.get_response_async().

Backport of 92309e53d9 from master
This commit is contained in:
Joshua Massover 2020-05-14 19:26:32 -04:00 committed by Mariusz Felisiak
parent 1ac45e619d
commit 3fb69756ea
4 changed files with 78 additions and 1 deletions

View File

@ -1,6 +1,8 @@
from urllib.parse import urlparse from urllib.parse import urlparse
from urllib.request import url2pathname from urllib.request import url2pathname
from asgiref.sync import sync_to_async
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles import utils from django.contrib.staticfiles import utils
from django.contrib.staticfiles.views import serve from django.contrib.staticfiles.views import serve
@ -52,6 +54,12 @@ class StaticFilesHandlerMixin:
except Http404 as e: except Http404 as e:
return response_for_exception(request, e) return response_for_exception(request, e)
async def get_response_async(self, request):
try:
return await sync_to_async(self.serve)(request)
except Http404 as e:
return await sync_to_async(response_for_exception)(request, e)
class StaticFilesHandler(StaticFilesHandlerMixin, WSGIHandler): class StaticFilesHandler(StaticFilesHandlerMixin, WSGIHandler):
""" """

View File

@ -0,0 +1 @@
test

View File

@ -1,18 +1,25 @@
import asyncio import asyncio
import sys import sys
import threading import threading
from pathlib import Path
from unittest import skipIf from unittest import skipIf
from asgiref.sync import SyncToAsync from asgiref.sync import SyncToAsync
from asgiref.testing import ApplicationCommunicator from asgiref.testing import ApplicationCommunicator
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
from django.core.signals import request_finished, request_started from django.core.signals import request_finished, request_started
from django.db import close_old_connections from django.db import close_old_connections
from django.test import AsyncRequestFactory, SimpleTestCase, override_settings from django.test import (
AsyncRequestFactory, SimpleTestCase, modify_settings, override_settings,
)
from django.utils.http import http_date
from .urls import test_filename from .urls import test_filename
TEST_STATIC_ROOT = Path(__file__).parent / 'project' / 'static'
@skipIf(sys.platform == 'win32' and (3, 8, 0) < sys.version_info < (3, 8, 1), 'https://bugs.python.org/issue38563') @skipIf(sys.platform == 'win32' and (3, 8, 0) < sys.version_info < (3, 8, 1), 'https://bugs.python.org/issue38563')
@override_settings(ROOT_URLCONF='asgi.urls') @override_settings(ROOT_URLCONF='asgi.urls')
@ -79,6 +86,45 @@ class ASGITest(SimpleTestCase):
# Allow response.close() to finish. # Allow response.close() to finish.
await communicator.wait() await communicator.wait()
@modify_settings(INSTALLED_APPS={'append': 'django.contrib.staticfiles'})
@override_settings(
STATIC_URL='/static/',
STATIC_ROOT=TEST_STATIC_ROOT,
STATICFILES_DIRS=[TEST_STATIC_ROOT],
STATICFILES_FINDERS=[
'django.contrib.staticfiles.finders.FileSystemFinder',
],
)
async def test_static_file_response(self):
application = ASGIStaticFilesHandler(get_asgi_application())
# Construct HTTP request.
scope = self.async_request_factory._base_scope(path='/static/file.txt')
communicator = ApplicationCommunicator(application, scope)
await communicator.send_input({'type': 'http.request'})
# Get the file content.
file_path = TEST_STATIC_ROOT / 'file.txt'
with open(file_path, 'rb') as test_file:
test_file_contents = test_file.read()
# Read the response.
stat = file_path.stat()
response_start = await communicator.receive_output()
self.assertEqual(response_start['type'], 'http.response.start')
self.assertEqual(response_start['status'], 200)
self.assertEqual(
set(response_start['headers']),
{
(b'Content-Length', str(len(test_file_contents)).encode('ascii')),
(b'Content-Type', b'text/plain'),
(b'Content-Disposition', b'inline; filename="file.txt"'),
(b'Last-Modified', http_date(stat.st_mtime).encode('ascii')),
},
)
response_body = await communicator.receive_output()
self.assertEqual(response_body['type'], 'http.response.body')
self.assertEqual(response_body['body'], test_file_contents)
# Allow response.close() to finish.
await communicator.wait()
async def test_headers(self): async def test_headers(self):
application = get_asgi_application() application = get_asgi_application()
communicator = ApplicationCommunicator( communicator = ApplicationCommunicator(

View File

@ -0,0 +1,22 @@
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
from django.core.handlers.asgi import ASGIHandler
from django.test import AsyncRequestFactory
from .cases import StaticFilesTestCase
class TestASGIStaticFilesHandler(StaticFilesTestCase):
async_request_factory = AsyncRequestFactory()
async def test_get_async_response(self):
request = self.async_request_factory.get('/static/test/file.txt')
handler = ASGIStaticFilesHandler(ASGIHandler())
response = await handler.get_response_async(request)
response.close()
self.assertEqual(response.status_code, 200)
async def test_get_async_response_not_found(self):
request = self.async_request_factory.get('/static/test/not-found.txt')
handler = ASGIStaticFilesHandler(ASGIHandler())
response = await handler.get_response_async(request)
self.assertEqual(response.status_code, 404)