-
-
Notifications
You must be signed in to change notification settings - Fork 3
Fix I/O operation on closed file error in Response classes #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bcbd433
877291f
a9a6b1b
c86d701
b82fcd9
3b4c34a
eb2e3f4
4138046
1de7612
75b8e5a
831704e
e19daa7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is quite behavior without any error or log
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added warning log in commit eb2e3f4: "Attempt to access data from closed buffer. Response has likely been sent already." |
||
| 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'') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dtto
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added error log in commit eb2e3f4: "Buffer is closed in end_of_response. This should not happen." |
||
| 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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is quite behavior without any error or log :-( May be some error should returned.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added warning log for data property and error log for end_of_response in commit eb2e3f4. The error log indicates this should not happen under normal circumstances. |
||
| 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: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Examples are running without poorwsgi installed. So you need to add poorwsgi path to python library like in other examples:
before importing poorwsgi. See examples/metrics.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added Python path setup in commit e19daa7. The example now includes the sys.path manipulation to allow running without poorwsgi installed, following the same pattern as other examples like metrics.py.