Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions examples/response_closed.py
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

Copy link
Copy Markdown
Contributor

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:


import os

EXAMPLES_PATH = os.path.dirname(__file__)
python_path.insert(0, os.path.abspath(
    os.path.join(EXAMPLES_PATH, os.path.pardir)))

before importing poorwsgi. See examples/metrics.py

Copy link
Copy Markdown
Contributor Author

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.

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()
16 changes: 16 additions & 0 deletions poorwsgi/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -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''
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite behavior without any error or log

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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()

Expand All @@ -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'')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dtto

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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))
Expand Down Expand Up @@ -502,6 +510,10 @@ def data(self):

This property works only if file_obj is seekable.
"""
if self.__file.closed:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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()
Expand All @@ -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:
Expand Down
33 changes: 33 additions & 0 deletions tests_integrity/test_response_closed.py
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")