diff --git a/examples/response_closed.py b/examples/response_closed.py new file mode 100644 index 0000000..e42b10a --- /dev/null +++ b/examples/response_closed.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Example demonstrating Response with closed buffer scenario.""" +from sys import path as python_path + +import os + +EXAMPLES_PATH = os.path.dirname(__file__) +python_path.insert(0, os.path.abspath( + os.path.join(EXAMPLES_PATH, os.path.pardir))) + +# pylint: disable=wrong-import-position +from poorwsgi import Application # noqa +from poorwsgi.response import Response # noqa + +app = application = Application("response_closed") + + +@app.route("/test") +def handler(_): + """Simple test handler that returns a Response.""" + res = Response("Test data") + # After response is sent, the buffer will be closed by WSGI server + # Accessing res.data after that should be handled gracefully + return res + + +@app.route("/test-after-response") +def handler_after_response(_): + """Handler that tries to access response data after creation.""" + res = Response("Test data") + # Try to access data property immediately (should work) + data = res.data + # Just verify it works, don't log + assert data == b"Test data" + return res + + +if __name__ == "__main__": + from wsgiref.simple_server import make_server + print("Starting server on http://localhost:8080") + httpd = make_server("localhost", 8080, app) + httpd.serve_forever() diff --git a/poorwsgi/response.py b/poorwsgi/response.py index 9836d7e..ef8ad5c 100644 --- a/poorwsgi/response.py +++ b/poorwsgi/response.py @@ -356,6 +356,10 @@ def __init__(self, data: Union[str, bytes] = b'', @property def data(self): + if self.__buffer.closed: # pylint: disable=using-constant-test + log.warning("Attempt to access data from closed buffer. " + "Response has likely been sent already.") + return b'' self.__buffer.seek(0) return self.__buffer.read() @@ -367,6 +371,10 @@ def write(self, data: Union[str, bytes]): self.__buffer.write(data) def __end_of_response__(self): + if self.__buffer.closed: # pylint: disable=using-constant-test + log.error("Buffer is closed in __end_of_response__. " + "This should not happen.") + return IBytesIO(b'') self.__buffer.seek(self._start) if self._end: return IBytesIO(self.__buffer.read(self._end - self._start + 1)) @@ -502,6 +510,10 @@ def data(self): This property works only if file_obj is seekable. """ + if self.__file.closed: + log.warning("Attempt to access data from closed file. " + "Response has likely been sent already.") + return b'' if self.__file.seekable(): self.__file.seek(self.__pos) return self.__file.read() @@ -515,6 +527,10 @@ def __end_of_response__(self): This method was called from Application object at the end of request for returning right value to wsgi server. """ + if self.__file.closed: + log.error("File is closed in __end_of_response__. " + "This should not happen.") + return IBytesIO(b'') if self.__file.seekable(): self.__file.seek(self._start) if self._end: diff --git a/tests_integrity/test_response_closed.py b/tests_integrity/test_response_closed.py new file mode 100644 index 0000000..6fa9ed8 --- /dev/null +++ b/tests_integrity/test_response_closed.py @@ -0,0 +1,33 @@ +"""Integrity test for closed buffer handling.""" +from os.path import dirname, join, pardir + +from pytest import fixture + +from .support import check_url, start_server + +# pylint: disable=missing-function-docstring +# pylint: disable=redefined-outer-name + + +@fixture(scope="module") +def url(request): + """URL (server fixture in fact).""" + process = start_server( + request, + join(dirname(__file__), pardir, 'examples/response_closed.py')) + + yield "http://localhost:8080" # server is running + process.kill() + process.wait() + + +class TestResponseClosed: + """Test for Response with closed buffer handling.""" + + def test_simple_response(self, url): + """Test that a simple response works normally.""" + check_url(url+"/test") + + def test_response_data_access(self, url): + """Test that accessing data property before sending works.""" + check_url(url+"/test-after-response")