Files
deb-python-falcon/falcon/request_helpers.py
Kurt Griffiths 0666ec8b8f fix(Request): Make wsgi.input wrapper more robust
Fix two bugs with the wsgi.input wrapper that is used to normalize
behavior between wsgiref and production-class web servers:

1. Do not hang when reading with a size < Content-Length but the
   stream has already been consumed.
2. If Content-Length is invalid or missing, assume no content and
   still wrap the stream, rather than sometimes wrapping and sometimes
   not.

Also, improve the tests to more cleanly shut down the wsgiref server
between tests so that the above can be tested in a more modular
fashion.
2015-11-09 18:16:07 -06:00

138 lines
4.0 KiB
Python

# Copyright 2013 by Rackspace Hosting, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
def header_property(wsgi_name):
"""Creates a read-only header property.
Args:
wsgi_name (str): Case-sensitive name of the header as it would
appear in the WSGI environ ``dict`` (i.e., 'HTTP_*')
Returns:
A property instance than can be assigned to a class variable.
"""
def fget(self):
try:
return self.env[wsgi_name] or None
except KeyError:
return None
return property(fget)
class Body(object):
"""Wrap *wsgi.input* streams to make them more robust.
``socket._fileobject`` and ``io.BufferedReader`` are sometimes used
to implement *wsgi.input*. However, app developers are often burned
by the fact that the `read()` method for these objects block
indefinitely if either no size is passed, or a size greater than
the request's content length is passed to the method.
This class normalizes *wsgi.input* behavior between WSGI servers
by implementing non-blocking behavior for the cases mentioned
above.
Args:
stream: Instance of ``socket._fileobject`` from
``environ['wsgi.input']``
stream_len: Expected content length of the stream.
"""
def __init__(self, stream, stream_len):
self.stream = stream
self.stream_len = stream_len
self._bytes_remaining = self.stream_len
def __iter__(self):
return self
def __next__(self):
return next(self.stream)
next = __next__
def _read(self, size, target):
"""Helper function for proxing reads to the underlying stream.
Args:
size (int): Maximum number of bytes/characters to read.
Will be coerced, if None or -1, to `self.stream_len`. Will
likewise be coerced if greater than `self.stream_len`, so
that if the stream doesn't follow standard io semantics,
the read won't block.
target (callable): Once `size` has been fixed up, this function
will be called to actually do the work.
Returns:
Data read from the stream, as returned by `target`.
"""
# NOTE(kgriffs): Default to reading all remaining bytes if the
# size is not specified or is out of bounds. This behaves
# similarly to the IO streams passed in by non-wsgiref servers.
if (size is None or size == -1 or size > self._bytes_remaining):
size = self._bytes_remaining
self._bytes_remaining -= size
return target(size)
def read(self, size=None):
"""Read from the stream.
Args:
size (int): Maximum number of bytes/characters to read.
Defaults to reading until EOF.
Returns:
Data read from the stream.
"""
return self._read(size, self.stream.read)
def readline(self, limit=None):
"""Read a line from the stream.
Args:
limit (int): Maximum number of bytes/characters to read.
Defaults to reading until EOF.
Returns:
Data read from the stream.
"""
return self._read(limit, self.stream.readline)
def readlines(self, hint=None):
"""Read lines from the stream.
Args:
hint (int): Maximum number of bytes/characters to read.
Defaults to reading until EOF.
Returns:
Data read from the stream.
"""
return self._read(hint, self.stream.readlines)