Source code for falcon.response

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

"""Response class."""

import mimetypes

from six import PY2
from six import string_types as STRING_TYPES

# NOTE(tbug): In some cases, http_cookies is not a module
# but a dict-like structure. This fixes that issue.
# See issue https://github.com/falconry/falcon/issues/556
from six.moves import http_cookies  # NOQA: I202

from falcon import DEFAULT_MEDIA_TYPE
from falcon.media import Handlers
from falcon.response_helpers import (
    format_content_disposition,
    format_header_value_list,
    format_range,
    header_property,
    is_ascii_encodable,
)
from falcon.util import dt_to_http, TimezoneGMT
from falcon.util.uri import encode as uri_encode
from falcon.util.uri import encode_value as uri_encode_value


SimpleCookie = http_cookies.SimpleCookie
CookieError = http_cookies.CookieError

GMT_TIMEZONE = TimezoneGMT()


[docs]class Response(object): """Represents an HTTP response to a client request. Note: `Response` is not meant to be instantiated directly by responders. Keyword Arguments: options (dict): Set of global options passed from the API handler. Attributes: status (str): HTTP status line (e.g., '200 OK'). Falcon requires the full status line, not just the code (e.g., 200). This design makes the framework more efficient because it does not have to do any kind of conversion or lookup when composing the WSGI response. If not set explicitly, the status defaults to '200 OK'. Note: Falcon provides a number of constants for common status codes. They all start with the ``HTTP_`` prefix, as in: ``falcon.HTTP_204``. media (object): A serializable object supported by the media handlers configured via :class:`falcon.RequestOptions`. See :ref:`media` for more information regarding media handling. body (str or unicode): String representing response content. If set to a Unicode type (``unicode`` in Python 2, or ``str`` in Python 3), Falcon will encode the text as UTF-8 in the response. If the content is already a byte string, use the :attr:`data` attribute instead (it's faster). data (bytes): Byte string representing response content. Use this attribute in lieu of `body` when your content is already a byte string (``str`` or ``bytes`` in Python 2, or simply ``bytes`` in Python 3). See also the note below. Note: Under Python 2.x, if your content is of type ``str``, using the `data` attribute instead of `body` is the most efficient approach. However, if your text is of type ``unicode``, you will need to use the `body` attribute instead. Under Python 3.x, on the other hand, the 2.x ``str`` type can be thought of as having been replaced by what was once the ``unicode`` type, and so you will need to always use the `body` attribute for strings to ensure Unicode characters are properly encoded in the HTTP response. stream: Either a file-like object with a `read()` method that takes an optional size argument and returns a block of bytes, or an iterable object, representing response content, and yielding blocks as byte strings. Falcon will use *wsgi.file_wrapper*, if provided by the WSGI server, in order to efficiently serve file-like objects. stream_len (int): Expected length of `stream`. If `stream` is set, but `stream_len` is not, Falcon will not supply a Content-Length header to the WSGI server. Consequently, the server may choose to use chunked encoding or one of the other strategies suggested by PEP-3333. context (dict): Dictionary to hold any data about the response which is specific to your app. Falcon itself will not interact with this attribute after it has been initialized. context_type (class): Class variable that determines the factory or type to use for initializing the `context` attribute. By default, the framework will instantiate standard ``dict`` objects. However, you may override this behavior by creating a custom child class of ``falcon.Response``, and then passing that new class to `falcon.API()` by way of the latter's `response_type` parameter. Note: When overriding `context_type` with a factory function (as opposed to a class), the function is called like a method of the current Response instance. Therefore the first argument is the Response instance itself (self). options (dict): Set of global options passed from the API handler. """ __slots__ = ( 'body', 'data', '_headers', '_cookies', 'status', 'stream', 'stream_len', 'context', 'options', '__dict__', ) # Child classes may override this context_type = dict def __init__(self, options=None): self.status = '200 OK' self._headers = {} self.options = options if options else ResponseOptions() # NOTE(tbug): will be set to a SimpleCookie object # when cookie is set via set_cookie self._cookies = None self._media = None self.body = None self.data = None self.stream = None self.stream_len = None self.context = self.context_type() @property def media(self): return self._media @media.setter def media(self, obj): self._media = obj if not self.content_type: self.content_type = self.options.default_media_type handler = self.options.media_handlers.find_by_media_type( self.content_type, self.options.default_media_type ) self.data = handler.serialize(self._media) def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.status)
[docs] def set_stream(self, stream, stream_len): """Convenience method for setting both `stream` and `stream_len`. Although the `stream` and `stream_len` properties may be set directly, using this method ensures `stream_len` is not accidentally neglected when the length of the stream is known in advance. Note: If the stream length is unknown, you can set `stream` directly, and ignore `stream_len`. In this case, the WSGI server may choose to use chunked encoding or one of the other strategies suggested by PEP-3333. """ self.stream = stream self.stream_len = stream_len
[docs] def get_header(self, name): """Retrieve the raw string value for the given header. Args: name (str): Header name, case-insensitive. Must be of type ``str`` or ``StringType``, and only character values 0x00 through 0xFF may be used on platforms that use wide characters. Returns: str: The header's value if set, otherwise ``None``. """ return self._headers.get(name.lower(), None)
[docs] def set_header(self, name, value): """Set a header for this response to a given value. Warning: Calling this method overwrites the existing value, if any. Warning: For setting cookies, see instead :meth:`~.set_cookie` Args: name (str): Header name (case-insensitive). The restrictions noted below for the header's value also apply here. value (str): Value for the header. Must be convertable to ``str`` or be of type ``str`` or ``StringType``. Strings must contain only US-ASCII characters. Under Python 2.x, the ``unicode`` type is also accepted, although such strings are also limited to US-ASCII. """ # NOTE(kgriffs): uwsgi fails with a TypeError if any header # is not a str, so do the conversion here. It's actually # faster to not do an isinstance check. str() will encode # to US-ASCII. name = str(name) value = str(value) # NOTE(kgriffs): normalize name by lowercasing it self._headers[name.lower()] = value
[docs] def delete_header(self, name): """Delete a header that was previously set for this response. If the header was not previously set, nothing is done (no error is raised). Note that calling this method is equivalent to setting the corresponding header property (when said property is available) to ``None``. For example:: resp.etag = None Args: name (str): Header name (case-insensitive). Must be of type ``str`` or ``StringType`` and contain only US-ASCII characters. Under Python 2.x, the ``unicode`` type is also accepted, although such strings are also limited to US-ASCII. """ # NOTE(kgriffs): normalize name by lowercasing it self._headers.pop(name.lower(), None)
[docs] def append_header(self, name, value): """Set or append a header for this response. Warning: If the header already exists, the new value will be appended to it, delimited by a comma. Most header specifications support this format, Set-Cookie being the notable exceptions. Warning: For setting cookies, see :py:meth:`~.set_cookie` Args: name (str): Header name (case-insensitive). The restrictions noted below for the header's value also apply here. value (str): Value for the header. Must be convertable to ``str`` or be of type ``str`` or ``StringType``. Strings must contain only US-ASCII characters. Under Python 2.x, the ``unicode`` type is also accepted, although such strings are also limited to US-ASCII. """ # NOTE(kgriffs): uwsgi fails with a TypeError if any header # is not a str, so do the conversion here. It's actually # faster to not do an isinstance check. str() will encode # to US-ASCII. name = str(name) value = str(value) name = name.lower() if name in self._headers: value = self._headers[name] + ',' + value self._headers[name] = value
[docs] def set_headers(self, headers): """Set several headers at once. Warning: Calling this method overwrites existing values, if any. Args: headers (dict or list): A dictionary of header names and values to set, or a ``list`` of (*name*, *value*) tuples. Both *name* and *value* must be of type ``str`` or ``StringType`` and contain only US-ASCII characters. Under Python 2.x, the ``unicode`` type is also accepted, although such strings are also limited to US-ASCII. Note: Falcon can process a list of tuples slightly faster than a dict. Raises: ValueError: `headers` was not a ``dict`` or ``list`` of ``tuple``. """ if isinstance(headers, dict): headers = headers.items() # NOTE(kgriffs): We can't use dict.update because we have to # normalize the header names. _headers = self._headers for name, value in headers: # NOTE(kgriffs): uwsgi fails with a TypeError if any header # is not a str, so do the conversion here. It's actually # faster to not do an isinstance check. str() will encode # to US-ASCII. name = str(name) value = str(value) _headers[name.lower()] = value
cache_control = header_property( 'Cache-Control', """Set the Cache-Control header. Used to set a list of cache directives to use as the value of the Cache-Control header. The list will be joined with ", " to produce the value for the header. """, format_header_value_list) content_location = header_property( 'Content-Location', """Set the Content-Location header. This value will be URI encoded per RFC 3986. If the value that is being set is already URI encoded it should be decoded first or the header should be set manually using the set_header method. """, uri_encode) content_range = header_property( 'Content-Range', """A tuple to use in constructing a value for the Content-Range header. The tuple has the form (*start*, *end*, *length*, [*unit*]), where *start* and *end* designate the range (inclusive), and *length* is the total length, or '\*' if unknown. You may pass ``int``'s for these numbers (no need to convert to ``str`` beforehand). The optional value *unit* describes the range unit and defaults to 'bytes' Note: You only need to use the alternate form, 'bytes \*/1234', for responses that use the status '416 Range Not Satisfiable'. In this case, raising ``falcon.HTTPRangeNotSatisfiable`` will do the right thing. (See also: RFC 7233, Section 4.2) """, format_range) content_type = header_property( 'Content-Type', """Sets the Content-Type header. The ``falcon`` module provides a number of constants for common media types, including ``falcon.MEDIA_JSON``, ``falcon.MEDIA_MSGPACK``, ``falcon.MEDIA_YAML``, ``falcon.MEDIA_XML``, ``falcon.MEDIA_HTML``, ``falcon.MEDIA_JS``, ``falcon.MEDIA_TEXT``, ``falcon.MEDIA_JPEG``, ``falcon.MEDIA_PNG``, and ``falcon.MEDIA_GIF``. """) downloadable_as = header_property( 'Content-Disposition', """Set the Content-Disposition header using the given filename. The value will be used for the *filename* directive. For example, given ``'report.pdf'``, the Content-Disposition header would be set to: ``'attachment; filename="report.pdf"'``. """, format_content_disposition) etag = header_property( 'ETag', 'Set the ETag header.') last_modified = header_property( 'Last-Modified', """Set the Last-Modified header. Set to a ``datetime`` (UTC) instance. Note: Falcon will format the ``datetime`` as an HTTP date string. """, dt_to_http) location = header_property( 'Location', """Set the Location header. This value will be URI encoded per RFC 3986. If the value that is being set is already URI encoded it should be decoded first or the header should be set manually using the set_header method. """, uri_encode) retry_after = header_property( 'Retry-After', """Set the Retry-After header. The expected value is an integral number of seconds to use as the value for the header. The HTTP-date syntax is not supported. """, str) vary = header_property( 'Vary', """Value to use for the Vary header. Set this property to an iterable of header names. For a single asterisk or field value, simply pass a single-element ``list`` or ``tuple``. The "Vary" header field in a response describes what parts of a request message, aside from the method, Host header field, and request target, might influence the origin server's process for selecting and representing this response. The value consists of either a single asterisk ("*") or a list of header field names (case-insensitive). (See also: RFC 7231, Section 7.1.4) """, format_header_value_list) accept_ranges = header_property( 'Accept-Ranges', """Set the Accept-Ranges header. The Accept-Ranges header field indicates to the client which range units are supported (e.g. "bytes") for the target resource. If range requests are not supported for the target resource, the header may be set to "none" to advise the client not to attempt any such requests. Note: "none" is the literal string, not Python's built-in ``None`` type. """) def _set_media_type(self, media_type=None): """Wrapper around set_header to set a content-type. Args: media_type: Media type to use for the Content-Type header. """ # PERF(kgriffs): Using "in" like this is faster than using # dict.setdefault (tested on py27). set_content_type = (media_type is not None and 'content-type' not in self._headers) if set_content_type: self.set_header('content-type', media_type) def _wsgi_headers(self, media_type=None, py2=PY2): """Convert headers into the format expected by WSGI servers. Args: media_type: Default media type to use for the Content-Type header if the header was not set explicitly (default ``None``). """ headers = self._headers self._set_media_type(media_type) if py2: # PERF(kgriffs): Don't create an extra list object if # it isn't needed. items = headers.items() else: items = list(headers.items()) if self._cookies is not None: # PERF(tbug): # The below implementation is ~23% faster than # the alternative: # # self._cookies.output().split("\\r\\n") # # Even without the .split("\\r\\n"), the below # is still ~17% faster, so don't use .output() items += [('set-cookie', c.OutputString()) for c in self._cookies.values()] return items
[docs]class ResponseOptions(object): """Defines a set of configurable response options. An instance of this class is exposed via :any:`API.resp_options` for configuring certain :py:class:`~.Response` behaviors. Attributes: secure_cookies_by_default (bool): Set to ``False`` in development environments to make the `secure` attribute for all cookies default to ``False``. This can make testing easier by not requiring HTTPS. Note, however, that this setting can be overridden via `set_cookie()`'s `secure` kwarg. default_media_type (str): The default Internet media type (RFC 2046) to use when deserializing a response. This value is normally set to the media type provided when a :class:`falcon.API` is initialized; however, if created independently, this will default to the ``DEFAULT_MEDIA_TYPE`` specified by Falcon. media_handlers (Handlers): A dict-like object that allows you to configure the media-types that you would like to handle. By default, a handler is provided for the ``application/json`` media type. static_media_types (dict): A mapping of dot-prefixed file extensions to Internet media types (RFC 2046). Defaults to ``mimetypes.types_map`` after calling ``mimetypes.init()``. """ __slots__ = ( 'secure_cookies_by_default', 'default_media_type', 'media_handlers', 'static_media_types', ) def __init__(self): self.secure_cookies_by_default = True self.default_media_type = DEFAULT_MEDIA_TYPE self.media_handlers = Handlers() mimetypes.init() self.static_media_types = mimetypes.types_map