summaryrefslogtreecommitdiff
path: root/paste/response.py
diff options
context:
space:
mode:
Diffstat (limited to 'paste/response.py')
-rw-r--r--paste/response.py240
1 files changed, 240 insertions, 0 deletions
diff --git a/paste/response.py b/paste/response.py
new file mode 100644
index 0000000..5ce0320
--- /dev/null
+++ b/paste/response.py
@@ -0,0 +1,240 @@
+# (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