241 lines
7.5 KiB
Python
241 lines
7.5 KiB
Python
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
|
|
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
"""Routines to generate WSGI responses"""
|
|
|
|
############################################################
|
|
## Headers
|
|
############################################################
|
|
import warnings
|
|
|
|
class HeaderDict(dict):
|
|
|
|
"""
|
|
This represents response headers. It handles the headers as a
|
|
dictionary, with case-insensitive keys.
|
|
|
|
Also there is an ``.add(key, value)`` method, which sets the key,
|
|
or adds the value to the current value (turning it into a list if
|
|
necessary).
|
|
|
|
For passing to WSGI there is a ``.headeritems()`` method which is
|
|
like ``.items()`` but unpacks value that are lists. It also
|
|
handles encoding -- all headers are encoded in ASCII (if they are
|
|
unicode).
|
|
|
|
@@: Should that encoding be ISO-8859-1 or UTF-8? I'm not sure
|
|
what the spec says.
|
|
"""
|
|
|
|
def __getitem__(self, key):
|
|
return dict.__getitem__(self, self.normalize(key))
|
|
|
|
def __setitem__(self, key, value):
|
|
dict.__setitem__(self, self.normalize(key), value)
|
|
|
|
def __delitem__(self, key):
|
|
dict.__delitem__(self, self.normalize(key))
|
|
|
|
def __contains__(self, key):
|
|
return dict.__contains__(self, self.normalize(key))
|
|
|
|
has_key = __contains__
|
|
|
|
def get(self, key, failobj=None):
|
|
return dict.get(self, self.normalize(key), failobj)
|
|
|
|
def setdefault(self, key, failobj=None):
|
|
return dict.setdefault(self, self.normalize(key), failobj)
|
|
|
|
def pop(self, key, *args):
|
|
return dict.pop(self, self.normalize(key), *args)
|
|
|
|
def update(self, other):
|
|
for key in other:
|
|
self[self.normalize(key)] = other[key]
|
|
|
|
def normalize(self, key):
|
|
return str(key).lower().strip()
|
|
|
|
def add(self, key, value):
|
|
key = self.normalize(key)
|
|
if key in self:
|
|
if isinstance(self[key], list):
|
|
self[key].append(value)
|
|
else:
|
|
self[key] = [self[key], value]
|
|
else:
|
|
self[key] = value
|
|
|
|
def headeritems(self):
|
|
result = []
|
|
for key, value in self.items():
|
|
if isinstance(value, list):
|
|
for v in value:
|
|
result.append((key, str(v)))
|
|
else:
|
|
result.append((key, str(value)))
|
|
return result
|
|
|
|
#@classmethod
|
|
def fromlist(cls, seq):
|
|
self = cls()
|
|
for name, value in seq:
|
|
self.add(name, value)
|
|
return self
|
|
|
|
fromlist = classmethod(fromlist)
|
|
|
|
def has_header(headers, name):
|
|
"""
|
|
Is header named ``name`` present in headers?
|
|
"""
|
|
name = name.lower()
|
|
for header, value in headers:
|
|
if header.lower() == name:
|
|
return True
|
|
return False
|
|
|
|
def header_value(headers, name):
|
|
"""
|
|
Returns the header's value, or None if no such header. If a
|
|
header appears more than once, all the values of the headers
|
|
are joined with ','. Note that this is consistent /w RFC 2616
|
|
section 4.2 which states:
|
|
|
|
It MUST be possible to combine the multiple header fields
|
|
into one "field-name: field-value" pair, without changing
|
|
the semantics of the message, by appending each subsequent
|
|
field-value to the first, each separated by a comma.
|
|
|
|
However, note that the original netscape usage of 'Set-Cookie',
|
|
especially in MSIE which contains an 'expires' date will is not
|
|
compatible with this particular concatination method.
|
|
"""
|
|
name = name.lower()
|
|
result = [value for header, value in headers
|
|
if header.lower() == name]
|
|
if result:
|
|
return ','.join(result)
|
|
else:
|
|
return None
|
|
|
|
def remove_header(headers, name):
|
|
"""
|
|
Removes the named header from the list of headers. Returns the
|
|
value of that header, or None if no header found. If multiple
|
|
headers are found, only the last one is returned.
|
|
"""
|
|
name = name.lower()
|
|
i = 0
|
|
result = None
|
|
while i < len(headers):
|
|
if headers[i][0].lower() == name:
|
|
result = headers[i][1]
|
|
del headers[i]
|
|
continue
|
|
i += 1
|
|
return result
|
|
|
|
def replace_header(headers, name, value):
|
|
"""
|
|
Updates the headers replacing the first occurance of the given name
|
|
with the value provided; asserting that no further occurances
|
|
happen. Note that this is _not_ the same as remove_header and then
|
|
append, as two distinct operations (del followed by an append) are
|
|
not atomic in a threaded environment. Returns the previous header
|
|
value for the provided name, if any. Clearly one should not use
|
|
this function with ``set-cookie`` or other names that may have more
|
|
than one occurance in the headers.
|
|
"""
|
|
name = name.lower()
|
|
i = 0
|
|
result = None
|
|
while i < len(headers):
|
|
if headers[i][0].lower() == name:
|
|
assert not result, "two values for the header '%s' found" % name
|
|
result = headers[i][1]
|
|
headers[i] = (name, value)
|
|
i += 1
|
|
if not result:
|
|
headers.append((name, value))
|
|
return result
|
|
|
|
|
|
############################################################
|
|
## Deprecated methods
|
|
############################################################
|
|
|
|
def error_body_response(error_code, message, __warn=True):
|
|
"""
|
|
Returns a standard HTML response page for an HTTP error.
|
|
**Note:** Deprecated
|
|
"""
|
|
if __warn:
|
|
warnings.warn(
|
|
'wsgilib.error_body_response is deprecated; use the '
|
|
'wsgi_application method on an HTTPException object '
|
|
'instead', DeprecationWarning, 2)
|
|
return '''\
|
|
<html>
|
|
<head>
|
|
<title>%(error_code)s</title>
|
|
</head>
|
|
<body>
|
|
<h1>%(error_code)s</h1>
|
|
%(message)s
|
|
</body>
|
|
</html>''' % {
|
|
'error_code': error_code,
|
|
'message': message,
|
|
}
|
|
|
|
|
|
def error_response(environ, error_code, message,
|
|
debug_message=None, __warn=True):
|
|
"""
|
|
Returns the status, headers, and body of an error response.
|
|
|
|
Use like:
|
|
|
|
.. code-block:: python
|
|
|
|
status, headers, body = wsgilib.error_response(
|
|
'301 Moved Permanently', 'Moved to <a href="%s">%s</a>'
|
|
% (url, url))
|
|
start_response(status, headers)
|
|
return [body]
|
|
|
|
**Note:** Deprecated
|
|
"""
|
|
if __warn:
|
|
warnings.warn(
|
|
'wsgilib.error_response is deprecated; use the '
|
|
'wsgi_application method on an HTTPException object '
|
|
'instead', DeprecationWarning, 2)
|
|
if debug_message and environ.get('paste.config', {}).get('debug'):
|
|
message += '\n\n<!-- %s -->' % debug_message
|
|
body = error_body_response(error_code, message, __warn=False)
|
|
headers = [('content-type', 'text/html'),
|
|
('content-length', str(len(body)))]
|
|
return error_code, headers, body
|
|
|
|
def error_response_app(error_code, message, debug_message=None,
|
|
__warn=True):
|
|
"""
|
|
An application that emits the given error response.
|
|
|
|
**Note:** Deprecated
|
|
"""
|
|
if __warn:
|
|
warnings.warn(
|
|
'wsgilib.error_response_app is deprecated; use the '
|
|
'wsgi_application method on an HTTPException object '
|
|
'instead', DeprecationWarning, 2)
|
|
def application(environ, start_response):
|
|
status, headers, body = error_response(
|
|
environ, error_code, message,
|
|
debug_message=debug_message, __warn=False)
|
|
start_response(status, headers)
|
|
return [body]
|
|
return application
|