class StreamingHTTPResponse(BaseHTTPResponse): __slots__ = ( "protocol", "streaming_fn", "status", "content_type", "headers", "chunked", "_cookies", ) def __init__( self, streaming_fn, status=200, headers=None, content_type="text/plain; charset=utf-8", chunked=True, ): self.content_type = content_type self.streaming_fn = streaming_fn self.status = status self.headers = Header(headers or {}) self.chunked = chunked self._cookies = None self.protocol = None async def write(self, data): """Writes a chunk of data to the streaming response. :param data: str or bytes-ish data to be written. """ data = self._encode_body(data) if self.chunked: await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) else: await self.protocol.push_data(data) await self.protocol.drain() async def stream(self, version="1.1", keep_alive=False, keep_alive_timeout=None): """Streams headers, runs the `streaming_fn` callback that writes content to the response body, then finalizes the response body. """ if version != "1.1": self.chunked = False headers = self.get_headers( version, keep_alive=keep_alive, keep_alive_timeout=keep_alive_timeout, ) await self.protocol.push_data(headers) await self.protocol.drain() await self.streaming_fn(self) if self.chunked: await self.protocol.push_data(b"0\r\n\r\n") # no need to await drain here after this write, because it is the # very last thing we write and nothing needs to wait for it. def get_headers(self, version="1.1", keep_alive=False, keep_alive_timeout=None): if self.chunked and version == "1.1": self.headers["Transfer-Encoding"] = "chunked" self.headers.pop("Content-Length", None) return super().get_headers(version, keep_alive, keep_alive_timeout)
class StreamingHTTPResponse(BaseHTTPResponse): __slots__ = ( "protocol", "streaming_fn", "status", "content_type", "headers", "chunked", "_cookies", ) def __init__( self, streaming_fn, status=200, headers=None, content_type="text/plain", chunked=True, ): self.content_type = content_type self.streaming_fn = streaming_fn self.status = status self.headers = Header(headers or {}) self.chunked = chunked self._cookies = None async def write(self, data): """Writes a chunk of data to the streaming response. :param data: bytes-ish data to be written. """ if type(data) != bytes: data = self._encode_body(data) if self.chunked: await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) else: await self.protocol.push_data(data) await self.protocol.drain() async def stream(self, version="1.1", keep_alive=False, keep_alive_timeout=None): """Streams headers, runs the `streaming_fn` callback that writes content to the response body, then finalizes the response body. """ if version != "1.1": self.chunked = False headers = self.get_headers( version, keep_alive=keep_alive, keep_alive_timeout=keep_alive_timeout, ) await self.protocol.push_data(headers) await self.protocol.drain() await self.streaming_fn(self) if self.chunked: await self.protocol.push_data(b"0\r\n\r\n") # no need to await drain here after this write, because it is the # very last thing we write and nothing needs to wait for it. def get_headers(self, version="1.1", keep_alive=False, keep_alive_timeout=None): # This is all returned in a kind-of funky way # We tried to make this as fast as possible in pure python timeout_header = b"" if keep_alive and keep_alive_timeout is not None: timeout_header = b"Keep-Alive: %d\r\n" % keep_alive_timeout if self.chunked and version == "1.1": self.headers["Transfer-Encoding"] = "chunked" self.headers.pop("Content-Length", None) self.headers["Content-Type"] = self.headers.get( "Content-Type", self.content_type) headers = self._parse_headers() if self.status == 200: status = b"OK" else: status = STATUS_CODES.get(self.status) return (b"HTTP/%b %d %b\r\n" b"%b" b"%b\r\n") % ( version.encode(), self.status, status, timeout_header, headers, )
def handle_error(self, request, e): """ Error handler for the API transforms a raised exception into a Sanic response, with the appropriate HTTP status code and body. :param request: The Sanic Request object :type request: sanic.request.Request :param e: the raised Exception object :type e: Exception """ context = restplus.get_context_from_spf(self.spf_reg) app = context.app #got_request_exception.send(app._get_current_object(), exception=e) if not isinstance(e, SanicException) and app.config.get( 'PROPAGATE_EXCEPTIONS', False): exc_type, exc_value, tb = sys.exc_info() if exc_value is e: raise else: raise e include_message_in_response = app.config.get("ERROR_INCLUDE_MESSAGE", True) include_code_in_response = app.config.get("ERROR_INCLUDE_CODE", True) default_data = {} headers = Header() for typecheck, handler in self._own_and_child_error_handlers.items(): if isinstance(e, typecheck): result = handler(e) default_data, code, headers = unpack( result, HTTPStatus.INTERNAL_SERVER_ERROR) break else: if isinstance(e, SanicException): sanic_code = code = e.status_code try: status = e.args[0] assert isinstance(status, (str, bytes)) except (AttributeError, LookupError, AssertionError): if sanic_code is 200: status = b'OK' # x is y comparison only works between -5 and 256 elif sanic_code == 404: status = b'Not Found' elif sanic_code == 500: status = b'Internal Server Error' else: status = ALL_STATUS_CODES.get(int(sanic_code)) code = HTTPStatus(sanic_code, None) if status and isinstance(status, bytes): status = status.decode('ascii') if include_message_in_response: default_data = {'message': getattr(e, 'message', status)} elif self._default_error_handler: result = self._default_error_handler(e) default_data, code, headers = unpack( result, HTTPStatus.INTERNAL_SERVER_ERROR) else: code = HTTPStatus.INTERNAL_SERVER_ERROR status = ALL_STATUS_CODES.get(code.value, str(e)) if status and isinstance(status, bytes): status = status.decode('ascii') if include_message_in_response: default_data = { 'message': status, } if include_message_in_response: default_data['message'] = default_data.get('message', str(e)) if include_code_in_response: default_data['code'] = int(code) data = getattr(e, 'data', default_data) fallback_mediatype = None if code >= HTTPStatus.INTERNAL_SERVER_ERROR: exc_info = sys.exc_info() if exc_info[1] is None or exc_info[0] is None: e_type = e.__class__ e_value = e e_traceback = e.__traceback__ else: e_type, e_value, e_traceback = exc_info context.log(logging.ERROR, "Caught Exception: {}".format(str(e_type))) context.log(logging.ERROR, "Detail: {}".format(str(e_value))) tb = traceback.format_tb(e_traceback) tb = "".join(tb) context.log(logging.ERROR, "Traceback:\n{}".format(tb)) elif code == HTTPStatus.NOT_FOUND and app.config.get("ERROR_404_HELP", False) \ and include_message_in_response: data['message'] = self._help_on_404(request, data.get('message', None)) elif code == HTTPStatus.NOT_ACCEPTABLE and self.default_mediatype is None: # if we are handling NotAcceptable (406), make sure that # make_response uses a representation we support as the # default mediatype (so that make_response doesn't throw # another NotAcceptable error). supported_mediatypes = list(self.representations.keys()) fallback_mediatype = supported_mediatypes[ 0] if supported_mediatypes else "text/plain" # Remove blacklisted headers for header in HEADERS_BLACKLIST: headers.pop(header, None) resp = self.make_response(request, data, code, headers, fallback_mediatype=fallback_mediatype) if code == HTTPStatus.UNAUTHORIZED: resp = self.unauthorized(resp) return resp
def handle_error(self, request, e): ''' Error handler for the API transforms a raised exception into a Flask response, with the appropriate HTTP status code and body. :param Exception e: the raised Exception object ''' context = restplus.get_context_from_spf(self.spf_reg) app = context.app #got_request_exception.send(app._get_current_object(), exception=e) if not isinstance(e, SanicException) and app.propagate_exceptions: exc_type, exc_value, tb = sys.exc_info() if exc_value is e: raise else: raise e include_message_in_response = app.config.get("ERROR_INCLUDE_MESSAGE", True) default_data = {} headers = Header() for typecheck, handler in self._own_and_child_error_handlers.items(): if isinstance(e, typecheck): result = handler(e) default_data, code, headers = unpack( result, HTTPStatus.INTERNAL_SERVER_ERROR) break else: if isinstance(e, SanicException): code = e.status_code if code is 200: status = b'OK' elif code is 404: status = b'Not Found' elif code is 500: status = b'Internal Server Error' else: code = HTTPStatus(code) status = ALL_STATUS_CODES.get(code.value) if status and isinstance(status, bytes): status = status.decode('ascii') if include_message_in_response: default_data = {'message': getattr(e, 'message', status)} elif self._default_error_handler: result = self._default_error_handler(e) default_data, code, headers = unpack( result, HTTPStatus.INTERNAL_SERVER_ERROR) else: code = HTTPStatus.INTERNAL_SERVER_ERROR status = ALL_STATUS_CODES.get(code.value, str(e)) if status and isinstance(status, bytes): status = status.decode('ascii') if include_message_in_response: default_data = { 'message': status, } if include_message_in_response: default_data['message'] = default_data.get('message', str(e)) data = getattr(e, 'data', default_data) fallback_mediatype = None if code >= HTTPStatus.INTERNAL_SERVER_ERROR: exc_info = sys.exc_info() if exc_info[1] is None: exc_info = None context.log(logging.ERROR, exc_info) elif code == HTTPStatus.NOT_FOUND and app.config.get("ERROR_404_HELP", True) \ and include_message_in_response: data['message'] = self._help_on_404(request, data.get('message', None)) elif code == HTTPStatus.NOT_ACCEPTABLE and self.default_mediatype is None: # if we are handling NotAcceptable (406), make sure that # make_response uses a representation we support as the # default mediatype (so that make_response doesn't throw # another NotAcceptable error). supported_mediatypes = list(self.representations.keys()) fallback_mediatype = supported_mediatypes[ 0] if supported_mediatypes else "text/plain" # Remove blacklisted headers for header in HEADERS_BLACKLIST: headers.pop(header, None) resp = self.make_response(request, data, code, headers, fallback_mediatype=fallback_mediatype) if code == HTTPStatus.UNAUTHORIZED: resp = self.unauthorized(resp) return resp