| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- # (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
|