diff --git a/channels/generic/http.py b/channels/generic/http.py index 0d043cc3a..80706a456 100644 --- a/channels/generic/http.py +++ b/channels/generic/http.py @@ -1,8 +1,14 @@ +import logging +import traceback + from channels.consumer import AsyncConsumer from ..db import aclose_old_connections from ..exceptions import StopConsumer +logger = logging.getLogger("channels.consumer") +logger.setLevel(logging.DEBUG) + class AsyncHttpConsumer(AsyncConsumer): """ @@ -77,9 +83,14 @@ async def http_request(self, message): """ if "body" in message: self.body.append(message["body"]) + if not message.get("more_body"): try: await self.handle(b"".join(self.body)) + except Exception: + logger.exception(f"Error in handle(): {traceback.format_exc()}") + await self.send_response(500, b"Internal Server Error") + raise finally: await self.disconnect() raise StopConsumer() diff --git a/tests/test_generic_http.py b/tests/test_generic_http.py index 0b6d0ecb5..41f71b40b 100644 --- a/tests/test_generic_http.py +++ b/tests/test_generic_http.py @@ -1,6 +1,7 @@ import asyncio import json import time +from unittest.mock import patch import pytest @@ -142,3 +143,41 @@ def send_awaitable_callable(*args, **kwargs): assert response["body"] == b"42" assert response["status"] == 200 assert response["headers"] == [(b"Content-Type", b"text/plain")] + + +@pytest.mark.django_db(transaction=True) +@pytest.mark.asyncio +async def test_error_logging(): + """Regression test for error logging.""" + + class TestConsumer(AsyncHttpConsumer): + async def handle(self, body): + raise AssertionError("Error correctly raised") + + communicator = HttpCommunicator(TestConsumer(), "GET", "/") + with patch("channels.generic.http.logger.error") as mock_logger_error: + try: + await communicator.get_response(timeout=0.05) + except AssertionError: + pass + args, _ = mock_logger_error.call_args + assert "Error in handle()" in args[0] + assert "AssertionError: Error correctly raised" in args[0] + + +@pytest.mark.django_db(transaction=True) +@pytest.mark.asyncio +async def test_error_handling_and_send_response(): + """Regression test to check error handling.""" + + class TestConsumer(AsyncHttpConsumer): + async def handle(self, body): + raise AssertionError("Error correctly raised") + + communicator = HttpCommunicator(TestConsumer(), "GET", "/") + with patch.object(AsyncHttpConsumer, "send_response") as mock_send_response: + try: + await communicator.get_response(timeout=0.05) + except AssertionError: + pass + mock_send_response.assert_called_once_with(500, b"Internal Server Error")