206 lines
7.1 KiB
Python
206 lines
7.1 KiB
Python
"""Defines the Response class
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
import falcon
|
|
|
|
CONTENT_TYPE_NAMES = set(['Content-Type', 'content-type', 'CONTENT-TYPE'])
|
|
|
|
|
|
class Response(object):
|
|
"""Represents an HTTP response to a client request
|
|
|
|
|
|
Attributes:
|
|
status: HTTP status code, such as "200 OK" (see also falcon.HTTP_*)
|
|
|
|
body: String representing response content. If Unicode, Falcon will
|
|
encode as UTF-8 in the response. If data is already a byte string,
|
|
use the data attribute instead (it's faster).
|
|
data: Byte string representing response content.
|
|
stream: Iterable stream-like object, representing response content.
|
|
stream_len: Expected length of stream (e.g., file size).
|
|
|
|
content_type: Value for the Content-Type header
|
|
etag: Value for the ETag header
|
|
cache_control: An array of cache directives (see http://goo.gl/fILS5
|
|
and http://goo.gl/sM9Xx for a good description.) The array will be
|
|
joined with ', ' to produce the value for the Cache-Control
|
|
header.
|
|
last_modified: A datetime (UTC) instance to use as the Last-Modified
|
|
header. Falcon will format the datetime as an HTTP date. See
|
|
also: http://goo.gl/R7So4
|
|
retry_after: Number of seconds to use as the value for the Retry-After
|
|
header. Note that the HTTP-date option is not supported. See
|
|
also: http://goo.gl/DIrWr
|
|
vary: Value to use for the Vary header. From Wikipedia: "Tells
|
|
downstream proxies how to match future request headers to decide
|
|
whether the cached response can be used rather than requesting a
|
|
fresh one from the origin server." See also: http://goo.gl/NGHdL
|
|
|
|
Assumed to be an array of values. For a single asterisk or field
|
|
value, simply pass a single-element array.
|
|
location: Value for the Location header. Note that relative URIs are
|
|
OK per http://goo.gl/DbVqR
|
|
content_location: Value for the Content-Location header. See
|
|
also: http://goo.gl/1slsA
|
|
content_range: A tuple to use in constructing a value for the
|
|
Content-Range header. The tuple has the form (start, end, length),
|
|
where start and end is the inclusive byte range, and length is the
|
|
total number of bytes, or '*' if unknown.
|
|
|
|
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: http://goo.gl/Iglhp
|
|
|
|
|
|
"""
|
|
|
|
__slots__ = (
|
|
'body',
|
|
'cache_control',
|
|
'content_location',
|
|
'content_range',
|
|
'content_type',
|
|
'data',
|
|
'etag',
|
|
'_headers',
|
|
'last_modified',
|
|
'location',
|
|
'retry_after',
|
|
'status',
|
|
'stream',
|
|
'stream_len',
|
|
'vary'
|
|
)
|
|
|
|
def __init__(self, default_media_type):
|
|
"""Initialize response attributes to default values
|
|
|
|
Args:
|
|
wsgierrors: File-like stream for logging errors
|
|
|
|
"""
|
|
|
|
self.status = '200 OK'
|
|
self._headers = [('Content-Type', default_media_type)]
|
|
|
|
self.body = None
|
|
self.data = None
|
|
self.stream = None
|
|
self.stream_len = None
|
|
|
|
self.content_type = None
|
|
self.etag = None
|
|
self.cache_control = None
|
|
self.last_modified = None
|
|
self.retry_after = None
|
|
self.vary = None
|
|
self.location = None
|
|
self.content_location = None
|
|
self.content_range = None
|
|
|
|
def set_header(self, name, value):
|
|
"""Set a header for this response to a given value.
|
|
|
|
Warning: Overwrites the existing value, if any.
|
|
|
|
Args:
|
|
name: Header name to set. Must be of type str or StringType, and
|
|
only character values 0x00 through 0xFF may be used on
|
|
platforms that use wide characters.
|
|
value: Value for the header. Must be of type str or StringType, and
|
|
only character values 0x00 through 0xFF may be used on
|
|
platforms that use wide characters.
|
|
|
|
"""
|
|
|
|
self._headers.append((name, value))
|
|
|
|
def set_headers(self, headers):
|
|
"""Set several headers at once. May be faster than set_header().
|
|
|
|
Warning: Overwrites existing values, if any.
|
|
|
|
Args:
|
|
headers: A dict containing header names and values to set. Both
|
|
names and values must be of type str or StringType, and
|
|
only character values 0x00 through 0xFF may be used on
|
|
platforms that use wide characters.
|
|
|
|
Raises:
|
|
ValueError: headers was not a dictionary or list of tuples.
|
|
|
|
"""
|
|
|
|
self._headers.extend(headers.items())
|
|
|
|
def _append_attribute_headers(self, headers): # NOQA
|
|
if self.etag is not None:
|
|
headers.append(('ETag', self.etag))
|
|
|
|
if self.cache_control is not None:
|
|
headers.append(('Cache-Control', ', '.join(self.cache_control)))
|
|
|
|
if self.last_modified is not None:
|
|
headers.append(('Last-Modified',
|
|
falcon.dt_to_http(self.last_modified)))
|
|
|
|
if self.retry_after is not None:
|
|
headers.append(('Retry-After', str(self.retry_after)))
|
|
|
|
if self.vary is not None:
|
|
headers.append(('Vary', ', '.join(self.vary)))
|
|
|
|
if self.location is not None:
|
|
headers.append(('Location', self.location))
|
|
|
|
if self.content_location is not None:
|
|
headers.append(('Content-Location', self.content_location))
|
|
|
|
content_range = self.content_range
|
|
if content_range is not None:
|
|
# PERF: Concatenation is faster than % string formatting as well
|
|
# as ''.join() in this case.
|
|
formatted_range = ('bytes ' +
|
|
str(content_range[0]) + '-' +
|
|
str(content_range[1]) + '/' +
|
|
str(content_range[2]))
|
|
|
|
headers.append(('Content-Range', formatted_range))
|
|
|
|
def _wsgi_headers(self, set_content_type):
|
|
"""Convert headers into the format expected by WSGI servers
|
|
|
|
WARNING: Only call once! Not idempotent.
|
|
|
|
"""
|
|
|
|
headers = self._headers
|
|
|
|
if not set_content_type:
|
|
del headers[0]
|
|
elif self.content_type is not None:
|
|
headers.append(('Content-Type', self.content_type))
|
|
|
|
self._append_attribute_headers(headers)
|
|
|
|
return headers
|