Пример #1
0
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)
Пример #2
0
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,
                )
Пример #3
0
    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
Пример #4
0
    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