def __init__(self, response=None, status=None, headers=None, mimetype=None, content_type=None): """Response can be any kind of iterable or string. If it's a string it's considered being an iterable with one item which is the string passed. Headers can be a list of tuples or a `Headers` object. Special note for `mimetype` and `content_type`. For most mime types `mimetype` and `content_type` work the same, the difference affects only 'text' mimetypes. If the mimetype passed with `mimetype` is a mimetype starting with `text/` it becomes a charset parameter defined with the charset of the response object. In constrast the `content_type` parameter is always added as header unmodified. """ if response is None: self.response = [] elif isinstance(response, basestring): self.response = [response] else: self.response = iter(response) if not headers: self.headers = Headers() elif isinstance(headers, Headers): self.headers = headers else: self.headers = Headers(headers) if content_type is None: if mimetype is None and 'Content-Type' not in self.headers: mimetype = self.default_mimetype if mimetype is not None: mimetype = get_content_type(mimetype, self.charset) content_type = mimetype if content_type is not None: self.headers['Content-Type'] = content_type if status is None: status = self.default_status if isinstance(status, (int, long)): self.status_code = status else: self.status = status
def __init__(self, response=None, status=200, headers=None, mimetype=None, content_type=None): """ Response can be any kind of iterable or string. If it's a string it's considered being an iterable with one item which is the string passed. headers can be a list of tuples or a `Headers` object. Special note for `mimetype` and `content_type`. For most mime types `mimetype` and `content_type` work the same, the difference affects only 'text' mimetypes. If the mimetype passed with `mimetype` is a mimetype starting with `text/` it becomes a charset parameter defined with the charset of the response object. In constrast the `content_type` parameter is always added as header unmodified. """ if response is None: self.response = [] elif isinstance(response, basestring): self.response = [response] else: self.response = iter(response) if not headers: self.headers = Headers() elif isinstance(headers, Headers): self.headers = headers else: self.headers = Headers(headers) if content_type is None: if mimetype is None and 'Content-Type' not in self.headers: mimetype = self.default_mimetype if mimetype is not None and mimetype.startswith('text/'): mimetype += '; charset=' + self.charset content_type = mimetype if content_type is not None: self.headers['Content-Type'] = content_type if isinstance(status, (int, long)): self.status_code = status else: self.status = status
class BaseResponse(object): """Base response class. The most important fact about a response object is that it's a regular WSGI application. It's initialized with a couple of response parameters (headers, body, status code etc.) and will start a valid WSGI response when called with the environ and start response callable. Because it's a WSGI application itself processing usually ends before the actual response is sent to the server. This helps debugging systems because they can catch all the exceptions before responses are started. Here a small example WSGI application that takes advantage of the response objects:: from werkzeug import BaseResponse as Response def index(): return Response('Index page') def application(environ, start_response): path = environ.get('PATH_INFO') or '/' if path == '/': response = index() else: response = Response('Not Found', status=404) return response(environ, start_response) Like `BaseRequest` which object is lacking a lot of functionality implemented in mixins. This gives you a better control about the actual API of your response objects, so you can create subclasses and add custom functionality. A full featured response object is available as `Response` which implements a couple of useful mixins. To enforce a new type of already existing responses you can use the `force_type` method. This is useful if you're working with different subclasses of response objects and you want to post process them with a know interface. Per default the request object will assume all the text data is `utf-8` encoded. Please refer to `the unicode chapter <unicode.txt>`_ for more details about customizing the behavior. """ charset = 'utf-8' default_status = 200 default_mimetype = 'text/plain' def __init__(self, response=None, status=None, headers=None, mimetype=None, content_type=None): """Response can be any kind of iterable or string. If it's a string it's considered being an iterable with one item which is the string passed. Headers can be a list of tuples or a `Headers` object. Special note for `mimetype` and `content_type`. For most mime types `mimetype` and `content_type` work the same, the difference affects only 'text' mimetypes. If the mimetype passed with `mimetype` is a mimetype starting with `text/` it becomes a charset parameter defined with the charset of the response object. In constrast the `content_type` parameter is always added as header unmodified. """ if response is None: self.response = [] elif isinstance(response, basestring): self.response = [response] else: self.response = iter(response) if not headers: self.headers = Headers() elif isinstance(headers, Headers): self.headers = headers else: self.headers = Headers(headers) if content_type is None: if mimetype is None and 'Content-Type' not in self.headers: mimetype = self.default_mimetype if mimetype is not None: mimetype = get_content_type(mimetype, self.charset) content_type = mimetype if content_type is not None: self.headers['Content-Type'] = content_type if status is None: status = self.default_status if isinstance(status, (int, long)): self.status_code = status else: self.status = status def force_type(cls, response, environ=None): """Enforce that the WSGI response is a response object of the current type. Werkzeug will use the `BaseResponse` internally in many situations like the exceptions. If you call `get_response` on an exception you will get back a regular `BaseResponse` object, even if you are using a custom subclass. This method can enforce a given response type, and it will also convert arbitrary WSGI callables into response objects if an environ is provided:: # convert a Werkzeug response object into an instance of the # MyResponseClass subclass. response = MyResponseClass.force_type(response) # convert any WSGI application into a response object response = MyResponseClass.force_type(response, environ) This is especially useful if you want to post-process responses in the main dispatcher and use functionality provided by your subclass. Keep in mind that this will modify response objects in place if possible! """ if not isinstance(response, BaseResponse): if environ is None: raise TypeError('cannot convert WSGI application into ' 'response objects without an environ') response = BaseResponse(*run_wsgi_app(response, environ)) response.__class__ = cls return response force_type = classmethod(force_type) def from_app(cls, app, environ, buffered=False): """Create a new response object from an application output. This works best if you pass it an application that returns a generator all the time. Sometimes applications may use the `write()` callable returned by the `start_response` function. This tries to resolve such edge cases automatically. But if you don't get the expected output you should set `buffered` to `True` which enforces buffering. """ return cls(*run_wsgi_app(app, environ, buffered)) from_app = classmethod(from_app) def _get_status_code(self): try: return int(self.status.split(None, 1)[0]) except ValueError: return 0 def _set_status_code(self, code): try: self.status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper()) except KeyError: self.status = '%d UNKNOWN' % code status_code = property(_get_status_code, _set_status_code, 'The HTTP Status code as number') del _get_status_code, _set_status_code def _get_data(self): """The string representation of the request body. Whenever you access this property the request iterable is encoded and flattened. This can lead to unwanted behavior if you stream big data. """ if not isinstance(self.response, list): self.response = list(self.response) return ''.join(self.iter_encoded()) def _set_data(self, value): self.response = [value] data = property(_get_data, _set_data, doc=_get_data.__doc__) del _get_data, _set_data def iter_encoded(self, charset=None): """Iter the response encoded with the encoding specified. If no encoding is given the encoding from the class is used. Note that this does not encode data that is already a bytestring. """ charset = charset or self.charset or 'ascii' for item in self.response: if isinstance(item, unicode): yield item.encode(charset) else: yield str(item) def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False): """Sets a cookie. The parameters are the same as in the cookie `Morsel` object in the Python standard library but it accepts unicode data too: - `max_age` should be a number of seconds, or `None` (default) if the cookie should last only as long as the client’s browser session. - `expires` should be a `datetime` object or UNIX timestamp. - Use `domain` if you want to set a cross-domain cookie. For example, ``domain=".example.com"`` will set a cookie that is readable by the domain ``www.example.com``, ``foo.example.com`` etc. Otherwise, a cookie will only be readable by the domain that set it. - `path` limits the cookie to a given path, per default it will span the whole domain. """ self.headers.add('Set-Cookie', dump_cookie(key, value, max_age, expires, path, domain, secure, httponly, self.charset)) def delete_cookie(self, key, path='/', domain=None): """Delete a cookie. Fails silently if key doesn't exist.""" self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain) def header_list(self): """This returns the headers in the target charset as list. It's used in __call__ to get the headers for the response. """ return self.headers.to_list(self.charset) header_list = property(header_list, doc=header_list.__doc__) def is_streamed(self): """If the response is streamed (the response is not a sequence) this property is `True`. In this case streamed means that there is no information about the number of iterations. This is usully `True` if a generator is passed to the response object. This is useful for checking before applying some sort of post filtering that should not take place for streamed responses. """ try: len(self.response) except TypeError: return False return True is_streamed = property(is_streamed, doc=is_streamed.__doc__) def fix_headers(self, environ): """This is automatically called right before the response is started and should fix common mistakes in headers. For example location headers are joined with the root URL here. """ if 'Location' in self.headers: self.headers['Location'] = urlparse.urljoin( get_current_url(environ, root_only=True), self.headers['Location'] ) def close(self): """Close the wrapped response if possible.""" if hasattr(self.response, 'close'): self.response.close() def freeze(self): """Call this method if you want to make your response object ready for pickeling. This buffers the generator if there is one.""" BaseResponse.data.__get__(self) def __call__(self, environ, start_response): """Process this response as WSGI application.""" self.fix_headers(environ) if environ['REQUEST_METHOD'] == 'HEAD': resp = () elif 100 <= self.status_code < 200 or self.status_code in (204, 304): self.headers['Content-Length'] = 0 resp = () else: resp = self.iter_encoded() start_response(self.status, self.header_list) return resp
class BaseResponse(object): """ Base response class. """ charset = 'utf-8' default_mimetype = 'text/plain' def __init__(self, response=None, status=200, headers=None, mimetype=None, content_type=None): """ Response can be any kind of iterable or string. If it's a string it's considered being an iterable with one item which is the string passed. headers can be a list of tuples or a `Headers` object. Special note for `mimetype` and `content_type`. For most mime types `mimetype` and `content_type` work the same, the difference affects only 'text' mimetypes. If the mimetype passed with `mimetype` is a mimetype starting with `text/` it becomes a charset parameter defined with the charset of the response object. In constrast the `content_type` parameter is always added as header unmodified. """ if response is None: self.response = [] elif isinstance(response, basestring): self.response = [response] else: self.response = iter(response) if not headers: self.headers = Headers() elif isinstance(headers, Headers): self.headers = headers else: self.headers = Headers(headers) if content_type is None: if mimetype is None and 'Content-Type' not in self.headers: mimetype = self.default_mimetype if mimetype is not None and mimetype.startswith('text/'): mimetype += '; charset=' + self.charset content_type = mimetype if content_type is not None: self.headers['Content-Type'] = content_type if isinstance(status, (int, long)): self.status_code = status else: self.status = status def from_app(cls, app, environ, buffered=False): """ Create a new response object from an application output. This works best if you pass it an application that returns a generator all the time. Sometimes applications may use the `write()` callable returned by the `start_response` function. This tries to resolve such edge cases automatically. But if you don't get the expected output you should set `buffered` to `True` which enforces buffering. """ return cls(*run_wsgi_app(app, environ, buffered)) from_app = classmethod(from_app) def _get_status_code(self): return int(self.status.split(None, 1)[0]) def _set_status_code(self, code): self.status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper()) status_code = property(_get_status_code, _set_status_code, 'Get the HTTP Status code as number') del _get_status_code, _set_status_code def cache_control(self): def on_update(cache_control): if not cache_control and 'cache-control' in self.headers: del self.headers['cache-control'] elif cache_control: self.headers['Cache-Control'] = cache_control.to_header() value = self.headers.get('Cache-Control') if value is not None: value = parse_cache_control_header(value) return CacheControl(value, on_update) cache_control = lazy_property(cache_control) def write(self, data): """If we have a buffered response this writes to the buffer.""" if not isinstance(self.response, list): raise RuntimeError('cannot write to a streamed response.') self.response.append(data) def writelines(self, lines): """Write lines.""" self.write(''.join(lines)) def _get_response_body(self): """ The string representation of the request body. Whenever you access this property the request iterable is encoded and flattened. This can lead to unwanted behavior if you stream big data. """ if not isinstance(self.response, list): self.response = list(self.response) return ''.join(self.iter_encoded()) def _set_response_body(self, value): """Set a new string as response body.""" self.response = [value] response_body = property(_get_response_body, _set_response_body, doc=_get_response_body.__doc__) del _get_response_body, _set_response_body def iter_encoded(self, charset=None): """ Iter the response encoded with the encoding specified. If no encoding is given the encoding from the class is used. Note that this does not encode data that is already a bytestring. """ charset = charset or self.charset or 'ascii' for item in self.response: if isinstance(item, unicode): yield item.encode(charset) else: yield str(item) def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False): """Set a new cookie.""" try: key = str(key) except UnicodeError: raise TypeError('invalid key %r' % key) c = SimpleCookie() if isinstance(value, unicode): value = value.encode(self.charset) c[key] = value if expires is not None: if not isinstance(expires, basestring): expires = cookie_date(expires) c[key]['expires'] = expires for k, v in (('path', path), ('domain', domain), ('secure', secure), ('max-age', max_age), ('HttpOnly', httponly)): if v is not None and v is not False: c[key][k] = str(v) self.headers.add('Set-Cookie', c[key].output(header='').lstrip()) def delete_cookie(self, key, path='/', domain=None): """Delete a cookie.""" self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain) def header_list(self): """ This returns the headers in the target charset as list. It's used in __call__ to get the headers for the response. """ return self.headers.to_list(self.charset) header_list = property(header_list, doc=header_list.__doc__) def is_streamed(self): """ If the request is streamed (the response is not a sequence) this property is `True`. """ try: len(self.response) except TypeError: return False return True is_streamed = property(is_streamed, doc=is_streamed.__doc__) def fix_headers(self, environ): """ This is automatically called right before the response is started and should fix common mistakes in headers. For example location headers are joined with the root URL here. """ if 'Location' in self.headers: self.headers['Location'] = urlparse.urljoin( get_current_url(environ, host_only=True), self.headers['Location'] ) def make_conditional(self, request_or_environ): """ Make the response conditional to the request. This method works best if an etag was defined for the response already. The `add_etag` method can be used to do that. If called without etag just the date header is set. This does nothing if the request method in the request or enviorn is anything but GET. """ if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'): return environ = getattr(request_or_environ, 'environ', request_or_environ) self.headers['Date'] = http_date() if 'etag' in self.headers: if_none_match = environ.get('HTTP_IF_NONE_MATCH') last_modified = self.headers.get('last-modified') if_modified_since = environ.get('HTTP_IF_MODIFIED_SINCE') # we only set the status code because the request object removes # contents for 304 responses automatically on `__call__` if if_none_match and if_none_match == self.headers['etag'] or \ if_modified_since == last_modified: self.status_code = 304 def add_etag(self, overwrite=False): """Add an etag for the current response if there is none yet.""" if not overwrite and 'etag' in self.headers: return etag = md5(self.response_body).hexdigest() self.headers['ETag'] = etag def close(self): """Close the wrapped response if possible.""" if hasattr(self.response, 'close'): self.response.close() def __call__(self, environ, start_response): """Process this response as WSGI application.""" self.fix_headers(environ) if environ['REQUEST_METHOD'] == 'HEAD': resp = () elif 100 <= self.status_code < 200 or self.status_code in (204, 304): self.headers['Content-Length'] = 0 resp = () else: resp = self.iter_encoded() start_response(self.status, self.header_list) return resp