def test_cookies(self): h = Headers() cookies = SimpleCookie({'bla': 'foo', 'pippo': 'pluto'}) self.assertEqual(len(cookies), 2) for c in cookies.values(): v = c.OutputString() h.add_header('Set-Cookie', v) h = str(h) self.assertTrue( h in ('Set-Cookie: bla=foo\r\nSet-Cookie: pippo=pluto\r\n\r\n', 'Set-Cookie: pippo=pluto\r\nSet-Cookie: bla=foo\r\n\r\n'))
class WsgiResponse: """A WSGI response. Instances are callable using the standard WSGI call and, importantly, iterable:: response = WsgiResponse(200) A :class:`WsgiResponse` is an iterable over bytes to send back to the requesting client. .. attribute:: status_code Integer indicating the HTTP status, (i.e. 200) .. attribute:: response String indicating the HTTP status (i.e. 'OK') .. attribute:: status String indicating the HTTP status code and response (i.e. '200 OK') .. attribute:: content_type The content type of this response. Can be ``None``. .. attribute:: headers The :class:`.Headers` container for this response. .. attribute:: environ The dictionary of WSGI environment if passed to the constructor. .. attribute:: cookies A python :class:`SimpleCookie` container of cookies included in the request as well as cookies set during the response. """ _iterated = False _started = False DEFAULT_STATUS_CODE = 200 def __init__(self, status=None, content=None, response_headers=None, content_type=None, encoding=None, environ=None, can_store_cookies=True): self.environ = environ self.status_code = status or self.DEFAULT_STATUS_CODE self.encoding = encoding self.cookies = SimpleCookie() self.headers = Headers(response_headers, kind='server') self.content = content self._can_store_cookies = can_store_cookies if content_type is not None: self.content_type = content_type @property def started(self): return self._started @property def iterated(self): return self._iterated @property def path(self): if self.environ: return self.environ.get('PATH_INFO', '') @property def method(self): if self.environ: return self.environ.get('REQUEST_METHOD') @property def connection(self): if self.environ: return self.environ.get('pulsar.connection') @property def content(self): return self._content @content.setter def content(self, content): if not self._iterated: if content is None: content = () else: if isinstance(content, str): if not self.encoding: # use utf-8 if not set self.encoding = 'utf-8' content = content.encode(self.encoding) if isinstance(content, bytes): content = (content, ) self._content = content else: raise RuntimeError('Cannot set content. Already iterated') def _get_content_type(self): return self.headers.get('content-type') def _set_content_type(self, typ): if typ: self.headers['content-type'] = typ else: self.headers.pop('content-type', None) content_type = property(_get_content_type, _set_content_type) @property def response(self): return responses.get(self.status_code) @property def status(self): return '%s %s' % (self.status_code, self.response) def __str__(self): return self.status def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self) @property def is_streamed(self): """Check if the response is streamed. A streamed response is an iterable with no length information. In this case streamed means that there is no information about the number of iterations. This is usually `True` if a generator is passed to the response object. """ try: len(self.content) except TypeError: return True return False def can_set_cookies(self): if self.status_code < 400: return self._can_store_cookies def length(self): if not self.is_streamed: return reduce(lambda x, y: x + len(y), self.content, 0) def start(self, start_response): assert not self._started self._started = True return start_response(self.status, self.get_headers()) def __iter__(self): if self._iterated: raise RuntimeError('WsgiResponse can be iterated once only') self._started = True self._iterated = True if self.is_streamed: return wsgi_encoder(self.content, self.encoding or 'utf-8') else: return iter(self.content) def close(self): """Close this response, required by WSGI """ if self.is_streamed: if hasattr(self.content, 'close'): self.content.close() def set_cookie(self, key, **kwargs): """ Sets a cookie. ``expires`` can be a string in the correct format or a ``datetime.datetime`` object in UTC. If ``expires`` is a datetime object then ``max_age`` will be calculated. """ set_cookie(self.cookies, key, **kwargs) def delete_cookie(self, key, path='/', domain=None): set_cookie(self.cookies, key, max_age=0, path=path, domain=domain, expires='Thu, 01-Jan-1970 00:00:00 GMT') def get_headers(self): """The list of headers for this response """ headers = self.headers if has_empty_content(self.status_code): headers.pop('content-type', None) headers.pop('content-length', None) self._content = () else: if not self.is_streamed: cl = 0 for c in self.content: cl += len(c) if cl == 0 and self.content_type in JSON_CONTENT_TYPES: self._content = (b'{}', ) cl = len(self._content[0]) headers['Content-Length'] = str(cl) ct = self.content_type # content type encoding available if self.encoding: ct = ct or 'text/plain' if 'charset=' not in ct: ct = '%s; charset=%s' % (ct, self.encoding) if ct: headers['Content-Type'] = ct if self.method == HEAD: self._content = () if self.can_set_cookies(): for c in self.cookies.values(): headers.add_header('Set-Cookie', c.OutputString()) return list(headers) def has_header(self, header): return header in self.headers __contains__ = has_header def __setitem__(self, header, value): self.headers[header] = value def __getitem__(self, header): return self.headers[header]
class WsgiResponse: """A WSGI response. Instances are callable using the standard WSGI call and, importantly, iterable:: response = WsgiResponse(200) A :class:`WsgiResponse` is an iterable over bytes to send back to the requesting client. .. attribute:: status_code Integer indicating the HTTP status, (i.e. 200) .. attribute:: response String indicating the HTTP status (i.e. 'OK') .. attribute:: status String indicating the HTTP status code and response (i.e. '200 OK') .. attribute:: content_type The content type of this response. Can be ``None``. .. attribute:: headers The :class:`.Headers` container for this response. .. attribute:: environ The dictionary of WSGI environment if passed to the constructor. .. attribute:: cookies A python :class:`SimpleCookie` container of cookies included in the request as well as cookies set during the response. """ _iterated = False _started = False DEFAULT_STATUS_CODE = 200 def __init__(self, status=None, content=None, response_headers=None, content_type=None, encoding=None, environ=None, can_store_cookies=True): self.environ = environ self.status_code = status or self.DEFAULT_STATUS_CODE self.encoding = encoding self.cookies = SimpleCookie() self.headers = Headers(response_headers, kind='server') self.content = content self._can_store_cookies = can_store_cookies if content_type is not None: self.content_type = content_type @property def started(self): return self._started @property def iterated(self): return self._iterated @property def path(self): if self.environ: return self.environ.get('PATH_INFO', '') @property def method(self): if self.environ: return self.environ.get('REQUEST_METHOD') @property def connection(self): if self.environ: return self.environ.get('pulsar.connection') @property def content(self): return self._content @content.setter def content(self, content): if not self._iterated: if content is None: content = () else: if isinstance(content, str): if not self.encoding: # use utf-8 if not set self.encoding = 'utf-8' content = content.encode(self.encoding) if isinstance(content, bytes): content = (content,) self._content = content else: raise RuntimeError('Cannot set content. Already iterated') def _get_content_type(self): return self.headers.get('content-type') def _set_content_type(self, typ): if typ: self.headers['content-type'] = typ else: self.headers.pop('content-type', None) content_type = property(_get_content_type, _set_content_type) @property def response(self): return responses.get(self.status_code) @property def status(self): return '%s %s' % (self.status_code, self.response) def __str__(self): return self.status def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self) @property def is_streamed(self): """Check if the response is streamed. A streamed response is an iterable with no length information. In this case streamed means that there is no information about the number of iterations. This is usually `True` if a generator is passed to the response object. """ try: len(self.content) except TypeError: return True return False def can_set_cookies(self): if self.status_code < 400: return self._can_store_cookies def length(self): if not self.is_streamed: return reduce(lambda x, y: x+len(y), self.content, 0) def start(self, start_response): assert not self._started self._started = True return start_response(self.status, self.get_headers()) def __iter__(self): if self._iterated: raise RuntimeError('WsgiResponse can be iterated once only') self._started = True self._iterated = True if self.is_streamed: return wsgi_encoder(self.content, self.encoding or 'utf-8') else: return iter(self.content) def close(self): """Close this response, required by WSGI """ if self.is_streamed: if hasattr(self.content, 'close'): self.content.close() def set_cookie(self, key, **kwargs): """ Sets a cookie. ``expires`` can be a string in the correct format or a ``datetime.datetime`` object in UTC. If ``expires`` is a datetime object then ``max_age`` will be calculated. """ set_cookie(self.cookies, key, **kwargs) def delete_cookie(self, key, path='/', domain=None): set_cookie(self.cookies, key, max_age=0, path=path, domain=domain, expires='Thu, 01-Jan-1970 00:00:00 GMT') def get_headers(self): """The list of headers for this response """ headers = self.headers if has_empty_content(self.status_code): headers.pop('content-type', None) headers.pop('content-length', None) self._content = () else: if not self.is_streamed: cl = 0 for c in self.content: cl += len(c) if cl == 0 and self.content_type in JSON_CONTENT_TYPES: self._content = (b'{}',) cl = len(self._content[0]) headers['Content-Length'] = str(cl) ct = self.content_type # content type encoding available if self.encoding: ct = ct or 'text/plain' if 'charset=' not in ct: ct = '%s; charset=%s' % (ct, self.encoding) if ct: headers['Content-Type'] = ct if self.method == HEAD: self._content = () if self.can_set_cookies(): for c in self.cookies.values(): headers.add_header('Set-Cookie', c.OutputString()) return list(headers) def has_header(self, header): return header in self.headers __contains__ = has_header def __setitem__(self, header, value): self.headers[header] = value def __getitem__(self, header): return self.headers[header]
class WsgiResponse(WsgiResponseGenerator): """A WSGI response wrapper initialized by a WSGI request middleware. Instances are callable using the standard WSGI call:: response = WsgiResponse(200) response(environ, start_response) A :class:`WsgiResponse` is an iterable over bytes to send back to the requesting client. .. attribute:: status_code Integer indicating the HTTP status, (i.e. 200) .. attribute:: response String indicating the HTTP status (i.e. 'OK') .. attribute:: status String indicating the HTTP status code and response (i.e. '200 OK') .. attribute:: environ The dictionary of WSGI environment if passed to the constructor. """ _started = False DEFAULT_STATUS_CODE = 200 def __init__( self, status=None, content=None, response_headers=None, content_type=None, encoding=None, environ=None, start_response=None, ): super(WsgiResponse, self).__init__(environ, start_response) self.status_code = status or self.DEFAULT_STATUS_CODE self.encoding = encoding self.cookies = SimpleCookie() self.headers = Headers(response_headers, kind="server") self.content = content if content_type is not None: self.content_type = content_type @property def started(self): return self._started @property def path(self): if self.environ: return self.environ.get("PATH_INFO", "") @property def method(self): if self.environ: return self.environ.get("REQUEST_METHOD") @property def connection(self): if self.environ: return self.environ.get("pulsar.connection") def _get_content(self): return self._content def _set_content(self, content): if not self._started: if content is None: content = () elif ispy3k: # what a f*****g pain if isinstance(content, str): content = bytes(content, "latin-1") else: # pragma nocover if isinstance(content, unicode): content = bytes(content, "latin-1") if isinstance(content, bytes): content = (content,) self._content = content else: raise RuntimeError("Cannot set content. Already iterated") content = property(_get_content, _set_content) def _get_content_type(self): return self.headers.get("content-type") def _set_content_type(self, typ): if typ: self.headers["content-type"] = typ else: self.headers.pop("content-type", None) content_type = property(_get_content_type, _set_content_type) def __call__(self, environ, start_response, exc_info=None): """Make sure the headers are set.""" if not exc_info: for rm in self.middleware: try: rm(environ, self) except: LOGGER.error("Exception in response middleware", exc_info=True) start_response(self.status, self.get_headers(), exc_info=exc_info) return self def start(self): self.__call__(self.environ, self.start_response) def length(self): if not self.is_streamed: return reduce(lambda x, y: x + len(y), self.content, 0) @property def response(self): return responses.get(self.status_code) @property def status(self): return "%s %s" % (self.status_code, self.response) def __str__(self): return self.status def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self) @property def is_streamed(self): """If the response is streamed (the response is not an iterable with length information) this property is `True`. In this case streamed means that there is no information about the number of iterations. This is usually `True` if a generator is passed to the response object.""" return is_streamed(self.content) def __iter__(self): if self._started: raise RuntimeError("WsgiResponse can be iterated only once") self._started = True if self.is_streamed: return wsgi_iterator(self.content, self.encoding) else: return iter(self.content) def __len__(self): return len(self.content) def set_cookie(self, key, **kwargs): """ Sets a cookie. ``expires`` can be a string in the correct format or a ``datetime.datetime`` object in UTC. If ``expires`` is a datetime object then ``max_age`` will be calculated. """ set_cookie(self.cookies, key, **kwargs) def delete_cookie(self, key, path="/", domain=None): set_cookie(self.cookies, key, max_age=0, path=path, domain=domain, expires="Thu, 01-Jan-1970 00:00:00 GMT") def get_headers(self): headers = self.headers if has_empty_content(self.status_code, self.method): headers.pop("content-type", None) headers.pop("content-length", None) elif not self.is_streamed: cl = 0 for c in self.content: cl += len(c) headers["Content-Length"] = str(cl) for c in self.cookies.values(): headers["Set-Cookie"] = c.OutputString() return list(headers)
class WsgiResponse(object): '''A WSGI response. Instances are callable using the standard WSGI call and, importantly, iterable:: response = WsgiResponse(200) A :class:`WsgiResponse` is an iterable over bytes to send back to the requesting client. .. attribute:: status_code Integer indicating the HTTP status, (i.e. 200) .. attribute:: response String indicating the HTTP status (i.e. 'OK') .. attribute:: status String indicating the HTTP status code and response (i.e. '200 OK') .. attribute:: content_type The content type of this response. Can be ``None``. .. attribute:: headers The :class:`pulsar.utils.httpurl.Headers` container for this response. .. attribute:: environ The dictionary of WSGI environment if passed to the constructor. ''' _started = False DEFAULT_STATUS_CODE = 200 def __init__(self, status=None, content=None, response_headers=None, content_type=None, encoding=None, environ=None): self.environ = environ self.status_code = status or self.DEFAULT_STATUS_CODE self.encoding = encoding self.cookies = SimpleCookie() self.headers = Headers(response_headers, kind='server') self.content = content if content_type is not None: self.content_type = content_type @property def started(self): return self._started @property def path(self): if self.environ: return self.environ.get('PATH_INFO', '') @property def method(self): if self.environ: return self.environ.get('REQUEST_METHOD') @property def connection(self): if self.environ: return self.environ.get('pulsar.connection') def _get_content(self): return self._content def _set_content(self, content): if not self._started: if content is None: content = () elif ispy3k: if isinstance(content, str): content = content.encode(self.encoding or 'utf-8') else: # pragma nocover if isinstance(content, unicode): content = content.encode(self.encoding or 'utf-8') if isinstance(content, bytes): content = (content,) self._content = content else: raise RuntimeError('Cannot set content. Already iterated') content = property(_get_content, _set_content) def _get_content_type(self): return self.headers.get('content-type') def _set_content_type(self, typ): if typ: self.headers['content-type'] = typ else: self.headers.pop('content-type', None) content_type = property(_get_content_type, _set_content_type) def length(self): if not self.is_streamed: return reduce(lambda x, y: x+len(y), self.content, 0) @property def response(self): return responses.get(self.status_code) @property def status(self): return '%s %s' % (self.status_code, self.response) def __str__(self): return self.status def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self) @property def is_streamed(self): """If the response is streamed (the response is not an iterable with length information) this property is `True`. In this case streamed means that there is no information about the number of iterations. This is usually `True` if a generator is passed to the response object.""" return is_streamed(self.content) def __iter__(self): if self._started: raise RuntimeError('WsgiResponse can be iterated once only') self._started = True if is_streamed(self.content): return wsgi_encoder(self.content, self.encoding or 'utf-8') else: return iter(self.content) def __len__(self): return len(self.content) def set_cookie(self, key, **kwargs): """ Sets a cookie. ``expires`` can be a string in the correct format or a ``datetime.datetime`` object in UTC. If ``expires`` is a datetime object then ``max_age`` will be calculated. """ set_cookie(self.cookies, key, **kwargs) def delete_cookie(self, key, path='/', domain=None): set_cookie(self.cookies, key, max_age=0, path=path, domain=domain, expires='Thu, 01-Jan-1970 00:00:00 GMT') def get_headers(self): headers = self.headers if has_empty_content(self.status_code, self.method): headers.pop('content-type', None) headers.pop('content-length', None) self._content = () else: if not self.is_streamed: cl = 0 for c in self.content: cl += len(c) if cl == 0 and self.content_type in JSON_CONTENT_TYPES: self._content = (b'{}',) cl = len(self._content[0]) headers['Content-Length'] = str(cl) if not self.content_type: headers['Content-Type'] = 'text/plain' for c in self.cookies.values(): headers['Set-Cookie'] = c.OutputString() return list(headers) def has_header(self, header): return header in self.headers __contains__ = has_header def __setitem__(self, header, value): self.headers[header] = value def __getitem__(self, header): return self.headers[header]