Skip to content

Commit

Permalink
More consistent behavior regarding client disconnects.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Sep 25, 2011
1 parent 694aae3 commit fa1be45
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 5 deletions.
2 changes: 2 additions & 0 deletions docs/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ The following error classes exist in Werkzeug:
This exception is used to signal unicode decode errors of request
data. For more information see the :ref:`unicode` chapter.

.. autoexception:: ClientDisconnected


Baseclass
=========
Expand Down
15 changes: 15 additions & 0 deletions werkzeug/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ class BadRequest(HTTPException):
)


class ClientDisconnected(BadRequest):
"""Internal exception that is raised if Werkzeug detects a disconnected
client. Since the client is already gone at that point attempting to
send the error message to the client might not work and might ultimately
result in another exception in the server. Mainly this is here so that
it is silenced by default as far as Werkzeug is concerned.
Since disconnections cannot be reliably detected and are unspecified
by WSGI to a large extend this might or might not be raised if a client
is gone.
.. versionadded:: 0.8
"""


class Unauthorized(HTTPException):
"""*401* `Unauthorized`
Expand Down
17 changes: 16 additions & 1 deletion werkzeug/testsuite/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from werkzeug.testsuite import WerkzeugTestCase

from werkzeug.wrappers import BaseResponse
from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import BadRequest, ClientDisconnected
from werkzeug.test import Client, create_environ, run_wsgi_app
from werkzeug import wsgi

Expand Down Expand Up @@ -140,6 +140,21 @@ def on_exhausted(self):
stream = wsgi.LimitedStream(io, 3)
assert stream.read(-1) == '123'

def test_limited_stream_disconnection(self):
io = StringIO('A bit of content')

# disconnect detection on out of bytes
stream = wsgi.LimitedStream(io, 255)
with self.assert_raises(ClientDisconnected):
stream.read()

# disconnect detection because file close
io = StringIO('x' * 255)
io.close()
stream = wsgi.LimitedStream(io, 255)
with self.assert_raises(ClientDisconnected):
stream.read()

def test_path_info_extraction(self):
x = wsgi.extract_path_info('http://example.com/app', '/app/hello')
assert x == u'/hello'
Expand Down
23 changes: 19 additions & 4 deletions werkzeug/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,15 @@ def on_exhausted(self):
from werkzeug.exceptions import BadRequest
raise BadRequest('input stream exhausted')

def on_disconnect(self):
"""What should happen if a disconnect is detected? The return
value of this function is returned from read functions in case
the client went away. By default a
:exc:`~werkzeug.exceptions.ClientDisconnected` exception is raised.
"""
from werkzeug.exceptions import ClientDisconnected
raise ClientDisconnected()

def exhaust(self, chunk_size=1024 * 16):
"""Exhaust the stream. This consumes all the data left until the
limit is reached.
Expand All @@ -722,9 +731,12 @@ def read(self, size=None):
if size is None or size == -1: # -1 is for consistence with file
size = self.limit
to_read = min(self.limit - self._pos, size)
read = self._read(to_read)
try:
read = self._read(to_read)
except (IOError, ValueError):
return self.on_disconnect()
if to_read and len(read) != to_read:
raise IOError('Stream has gone away')
return self.on_disconnect()
self._pos += len(read)
return read

Expand All @@ -736,9 +748,12 @@ def readline(self, size=None):
size = self.limit - self._pos
else:
size = min(size, self.limit - self._pos)
line = self._readline(size)
try:
line = self._readline(size)
except (ValueError, IOError):
return self.on_disconnect()
if size and not line:
raise IOError('Stream has gone away')
return self.on_disconnect()
self._pos += len(line)
return line

Expand Down

0 comments on commit fa1be45

Please sign in to comment.