示例#1
0
 def __init__(self):
     self.asgi: bool = False
     self.body: Optional[bytes] = None
     self.content_type: Optional[str] = None
     self.stream: Http = None
     self.status: int = None
     self.headers = Header({})
     self._cookies: Optional[CookieJar] = None
示例#2
0
    async def create(cls, sanic_app, scope: ASGIScope, receive: ASGIReceive,
                     send: ASGISend) -> "ASGIApp":
        instance = cls()
        instance.sanic_app = sanic_app
        instance.transport = MockTransport(scope, receive, send)
        instance.transport.loop = sanic_app.loop
        setattr(instance.transport, "add_task", sanic_app.loop.create_task)

        headers = Header([(key.decode("latin-1"), value.decode("latin-1"))
                          for key, value in scope.get("headers", [])])
        instance.do_stream = (True if headers.get("expect") == "100-continue"
                              else False)
        instance.lifespan = Lifespan(instance)

        if scope["type"] == "lifespan":
            await instance.lifespan(scope, receive, send)
        else:
            path = (scope["path"][1:]
                    if scope["path"].startswith("/") else scope["path"])
            url = "/".join([scope.get("root_path", ""), quote(path)])
            url_bytes = url.encode("latin-1")
            url_bytes += b"?" + scope["query_string"]

            if scope["type"] == "http":
                version = scope["http_version"]
                method = scope["method"]
            elif scope["type"] == "websocket":
                version = "1.1"
                method = "GET"

                instance.ws = instance.transport.create_websocket_connection(
                    send, receive)
                await instance.ws.accept()
            else:
                pass
                # TODO:
                # - close connection

            request_class = sanic_app.request_class or Request
            instance.request = request_class(
                url_bytes,
                headers,
                version,
                method,
                instance.transport,
                sanic_app,
            )
            instance.request.conn_info = ConnInfo(instance.transport)

            if sanic_app.is_request_stream:
                is_stream_handler = sanic_app.router.is_stream_handler(
                    instance.request)
                if is_stream_handler:
                    instance.request.stream = StreamBuffer(
                        sanic_app.config.REQUEST_BUFFER_QUEUE_SIZE)
                    instance.do_stream = True

        return instance
示例#3
0
def get_cors_headers(options, request_headers, request_method):
    origins_to_set = get_cors_origins(options, request_headers.get('Origin'))
    headers = CIMultiDict()

    if not origins_to_set:  # CORS is not enabled for this route
        return headers

    for origin in origins_to_set:
        # TODO, with CIDict, with will only allow one origin
        # With CIMultiDict it should work with multiple
        headers[ACL_ORIGIN] = origin

    headers[ACL_EXPOSE_HEADERS] = options.get('expose_headers')

    if options.get('supports_credentials'):
        headers[ACL_CREDENTIALS] = 'true'  # case sensative

    # This is a preflight request
    # http://www.w3.org/TR/cors/#resource-preflight-requests
    if request_method == 'OPTIONS':
        acl_request_method = request_headers.get(ACL_REQUEST_METHOD,
                                                 '').upper()

        # If there is no Access-Control-Request-Method header or if parsing
        # failed, do not set any additional headers
        if acl_request_method and acl_request_method in options.get('methods'):

            # If method is not a case-sensitive match for any of the values in
            # list of methods do not set any additional headers and terminate
            # this set of steps.
            headers[ACL_ALLOW_HEADERS] = get_allow_headers(
                options, request_headers.get(ACL_REQUEST_HEADERS))
            headers[ACL_MAX_AGE] = str(options.get(
                'max_age'))  # sanic cannot handle integers in header values.
            headers[ACL_METHODS] = options.get('methods')
        else:
            LOG.info(
                "The request's Access-Control-Request-Method header does not match allowed methods. "
                "CORS headers will not be applied.")

    # http://www.w3.org/TR/cors/#resource-implementation
    if options.get('vary_header'):
        # Only set header if the origin returned will vary dynamically,
        # i.e. if we are not returning an asterisk, and there are multiple
        # origins that can be matched.
        if headers[ACL_ORIGIN] == '*':
            pass
        elif (len(options.get('origins')) > 1 or len(origins_to_set) > 1
              or any(map(probably_regex, options.get('origins')))):
            headers['Vary'] = 'Origin'

    return CIMultiDict((k, v) for k, v in headers.items() if v)
示例#4
0
 def __init__(
     self,
     body=None,
     status=200,
     headers=None,
     content_type=None,
     body_bytes=b"",
 ):
     self.content_type = content_type
     self.body = body_bytes if body is None else self._encode_body(body)
     self.status = status
     self.headers = Header(headers or {})
     self._cookies = None
示例#5
0
 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
示例#6
0
    def __init__(
        self,
        body=None,
        status=200,
        headers=None,
        content_type=None,
    ):
        super().__init__()

        self.content_type = content_type
        self.body = self._encode_body(body)
        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None
示例#7
0
    def __init__(
        self,
        url_bytes: bytes,
        headers: Header,
        version: str,
        method: str,
        transport: TransportProtocol,
        app: Sanic,
        head: bytes = b"",
    ):

        self.raw_url = url_bytes
        try:
            self._parsed_url = parse_url(url_bytes)
        except HttpParserInvalidURLError:
            raise BadURL(f"Bad URL: {url_bytes.decode()}")
        self._id: Optional[Union[uuid.UUID, str, int]] = None
        self._name: Optional[str] = None
        self.app = app

        self.headers = Header(headers)
        self.version = version
        self.method = method
        self.transport = transport
        self.head = head

        # Init but do not inhale
        self.body = b""
        self.conn_info: Optional[ConnInfo] = None
        self.ctx = SimpleNamespace()
        self.parsed_forwarded: Optional[Options] = None
        self.parsed_accept: Optional[AcceptContainer] = None
        self.parsed_credentials: Optional[Credentials] = None
        self.parsed_json = None
        self.parsed_form: Optional[RequestParameters] = None
        self.parsed_files: Optional[RequestParameters] = None
        self.parsed_token: Optional[str] = None
        self.parsed_args: DefaultDict[Tuple[bool, bool, str, str],
                                      RequestParameters] = defaultdict(
                                          RequestParameters)
        self.parsed_not_grouped_args: DefaultDict[Tuple[
            bool, bool, str, str], List[Tuple[str, str]]] = defaultdict(list)
        self.request_middleware_started = False
        self._cookies: Optional[Dict[str, str]] = None
        self._match_info: Dict[str, Any] = {}
        self.stream: Optional[Http] = None
        self.route: Optional[Route] = None
        self._protocol = None
        self.responded: bool = False
示例#8
0
    def __init__(
        self,
        streaming_fn: StreamingFunction,
        status: int = 200,
        headers: Optional[Union[Header, Dict[str, str]]] = None,
        content_type: str = "text/plain; charset=utf-8",
        ignore_deprecation_notice: bool = False,
    ):
        if not ignore_deprecation_notice:
            warn(
                "Use of the StreamingHTTPResponse is deprecated in v21.6, and "
                "will be removed in v21.12. Please upgrade your streaming "
                "response implementation. You can learn more here: "
                "https://sanicframework.org/en/guide/advanced/streaming.html"
                "#response-streaming. If you use the builtin stream() or "
                "file_stream() methods, this upgrade will be be done for you."
            )

        super().__init__()

        self.content_type = content_type
        self.streaming_fn = streaming_fn
        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None
示例#9
0
    def on_headers_complete(self):
        self.request = self.request_class(
            url_bytes=self.url,
            headers=Header(self.headers),
            version=self.parser.get_http_version(),
            method=self.parser.get_method().decode(),
            transport=self.transport,
            app=self.app,
        )
        # Remove any existing KeepAlive handler here,
        # It will be recreated if required on the new request.
        if self._keep_alive_timeout_handler:
            self._keep_alive_timeout_handler.cancel()
            self._keep_alive_timeout_handler = None

        if self.request.headers.get(EXPECT_HEADER):
            self.expect_handler()

        if self.is_request_stream:
            self._is_stream_handler = self.router.is_stream_handler(
                self.request)
            if self._is_stream_handler:
                self.request.stream = StreamBuffer(
                    self.request_buffer_queue_size)
                self.execute_request_handler()
示例#10
0
class HTTPResponse(BaseHTTPResponse):
    __slots__ = ("body", "status", "content_type", "headers", "_cookies")

    def __init__(
        self,
        body=None,
        status=200,
        headers=None,
        content_type=None,
        body_bytes=b"",
    ):
        self.content_type = content_type
        self.body = body_bytes if body is None else self._encode_body(body)
        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None

    def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None):
        body = b""
        if has_message_body(self.status):
            body = self.body
            self.headers["Content-Length"] = self.headers.get(
                "Content-Length", len(self.body))

        return self.get_headers(version, keep_alive, keep_alive_timeout, body)

    @property
    def cookies(self):
        if self._cookies is None:
            self._cookies = CookieJar(self.headers)
        return self._cookies
示例#11
0
 def test_extract_params(self):
     with set_request({'QUERY_STRING': b'test=foo&foo=bar'}):
         uri, http_method, body, headers = extract_params()
         self.assertEquals(uri, 'http://127.0.0.1/?test=foo&foo=bar')
         self.assertEquals(http_method, 'GET')
         self.assertEquals(body, {})
         self.assertEquals(headers, Header({'Host': '127.0.0.1'}))
示例#12
0
    def __init__(
        self,
        streaming_fn,
        status=200,
        headers=None,
        content_type="text/plain; charset=utf-8",
        chunked=True,
    ):
        super().__init__()

        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
示例#13
0
    async def create(
        cls, sanic_app, scope: ASGIScope, receive: ASGIReceive, send: ASGISend
    ) -> "ASGIApp":
        instance = cls()
        instance.sanic_app = sanic_app
        instance.transport = MockTransport(scope, receive, send)
        instance.transport.loop = sanic_app.loop
        instance.stage = Stage.IDLE
        instance.response = None
        setattr(instance.transport, "add_task", sanic_app.loop.create_task)

        headers = Header(
            [
                (key.decode("latin-1"), value.decode("latin-1"))
                for key, value in scope.get("headers", [])
            ]
        )
        instance.lifespan = Lifespan(instance)

        if scope["type"] == "lifespan":
            await instance.lifespan(scope, receive, send)
        else:
            path = (
                scope["path"][1:]
                if scope["path"].startswith("/")
                else scope["path"]
            )
            url = "/".join([scope.get("root_path", ""), quote(path)])
            url_bytes = url.encode("latin-1")
            url_bytes += b"?" + scope["query_string"]

            if scope["type"] == "http":
                version = scope["http_version"]
                method = scope["method"]
            elif scope["type"] == "websocket":
                version = "1.1"
                method = "GET"

                instance.ws = instance.transport.create_websocket_connection(
                    send, receive
                )
            else:
                raise ServerError("Received unknown ASGI scope")

            request_class = sanic_app.request_class or Request
            instance.request = request_class(
                url_bytes,
                headers,
                version,
                method,
                instance.transport,
                sanic_app,
            )
            instance.request.stream = instance
            instance.request_body = True
            instance.request.conn_info = ConnInfo(instance.transport)

        return instance
示例#14
0
    def __init__(
        self,
        body=None,
        status=200,
        headers=None,
        content_type="text/plain",
        body_bytes=b"",
    ):
        self.content_type = content_type

        if body is not None:
            self.body = self._encode_body(body)
        else:
            self.body = body_bytes

        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None
示例#15
0
def set_request(wsgi_environ, app=None, transport=None):
    """
    Test helper context manager that mocks the sanic request
    """
    environ = {}
    headers = Header()
    environ.update(wsgi_environ)
    for k, v in REQUEST_DEFAULTS.items():
        environ.setdefault(k, v)
    url_bytes = b"%s://%s" % (environ['SCHEME'], environ['HOST'])
    headers.setdefault('Host', environ['HOST'].decode('utf-8'))
    if environ['SCHEME'] == b"http" and environ['PORT'] in (80, b"80"):
        pass
    elif environ['SCHEME'] == b"https" and environ['PORT'] in (443, b"443"):
        pass
    else:
        port = environ['PORT']
        if isinstance(port, int):
            port = str(port)
        if isinstance(port, str):
            port = port.encode('latin-1')

        url_bytes = b"%s:%s" % (url_bytes, port)
    if environ['PATH'] == b"":
        pass
    else:
        path = environ['PATH']
        if path == b"/":
            path = b""
        url_bytes = b"%s/%s" % (url_bytes, path)
    if environ['QUERY_STRING'] == b"":
        pass
    else:
        url_bytes = b"%s?%s" % (url_bytes, environ['QUERY_STRING'])
    version = environ['VERSION'].decode('utf-8')
    method = environ['METHOD'].decode('utf-8')
    if app is None:
        app = set_request.app
    if transport is None:
        transport = DummyTransport()
    r = SanicRequest(url_bytes, headers, version, method, transport, app)

    with mock.patch.dict(extract_params.__globals__, {'request': r}):
        yield
示例#16
0
    def __init__(
        self,
        body=None,
        status=200,
        headers=None,
        content_type=None,
        body_bytes=b"",
    ):
        self.content_type = content_type
        self.body = body_bytes if body is None else self._encode_body(body)
        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None

        if body_bytes:
            warnings.warn(
                "Parameter `body_bytes` is deprecated, use `body` instead",
                DeprecationWarning,
            )
示例#17
0
    def __init__(
        self,
        url_bytes: bytes,
        headers: Header,
        version: str,
        method: str,
        transport: TransportProtocol,
        app: Sanic,
        head: bytes = b"",
    ):
        self.raw_url = url_bytes
        # TODO: Content-Encoding detection
        self._parsed_url = parse_url(url_bytes)
        self._id: Optional[Union[uuid.UUID, str, int]] = None
        self._name: Optional[str] = None
        self.app = app

        self.headers = Header(headers)
        self.version = version
        self.method = method
        self.transport = transport
        self.head = head

        # Init but do not inhale
        self.body = b""
        self.conn_info: Optional[ConnInfo] = None
        self.ctx = SimpleNamespace()
        self.parsed_forwarded: Optional[Options] = None
        self.parsed_json = None
        self.parsed_form = None
        self.parsed_files = None
        self.parsed_args: DefaultDict[
            Tuple[bool, bool, str, str], RequestParameters
        ] = defaultdict(RequestParameters)
        self.parsed_not_grouped_args: DefaultDict[
            Tuple[bool, bool, str, str], List[Tuple[str, str]]
        ] = defaultdict(list)
        self.request_middleware_started = False
        self._cookies: Optional[Dict[str, str]] = None
        self._match_info: Dict[str, Any] = {}
        self.stream: Optional[Http] = None
        self.route: Optional[Route] = None
        self._protocol = None
示例#18
0
文件: test_slack.py 项目: attgua/Geco
def prepare_slack_request(headers: Dict[Text, Any]) -> Request:
    request = Request(
        b"/webhooks/slack/webhook",
        headers=Header(headers),
        version="1.1",
        method="POST",
        transport=None,
        app=None,
    )
    request.body = b"""{"foo": "bar"}"""
    return request
示例#19
0
    async def test(request: Request):
        headers = Header()
        cookies = CookieJar(headers)
        cookies["test"] = "modified"
        cookies["test"] = "pass"
        response = await request.respond(content_type="text/csv",
                                         headers=headers)

        await response.send("foo,")
        await asyncio.sleep(0.001)
        await response.send("bar")
示例#20
0
 def __init__(
     self,
     streaming_fn: Callable[[Union[BaseHTTPResponse, ResponseStream]],
                            Coroutine[Any, Any, None], ],
     status: int = 200,
     headers: Optional[Union[Header, Dict[str, str]]] = None,
     content_type: Optional[str] = None,
 ):
     self.streaming_fn = streaming_fn
     self.status = status
     self.headers = headers or Header()
     self.content_type = content_type
     self.request: Optional[Request] = None
     self._cookies: Optional[CookieJar] = None
示例#21
0
文件: http.py 项目: imuxin/iSanic
 def create_empty_request(self):
     """Current error handling code needs a request object that won't exist
     if an error occurred during before a request was received. Create a
     bogus response for error handling use."""
     # FIXME: Avoid this by refactoring error handling and response code
     self.request = self.protocol.request_class(
         url_bytes=self.url.encode() if self.url else b"*",
         headers=Header({}),
         version="1.1",
         method="NONE",
         transport=self.protocol.transport,
         app=self.protocol.app,
     )
     self.request.stream = self
示例#22
0
    def __init__(
        self,
        body: Optional[AnyStr] = None,
        status: int = 200,
        headers: Optional[Union[Header, Dict[str, str]]] = None,
        content_type: Optional[str] = None,
    ):
        super().__init__()

        self.content_type: Optional[str] = content_type
        self.body = self._encode_body(body)
        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None
示例#23
0
    def __init__(
        self,
        streaming_fn,
        status=200,
        headers=None,
        content_type="text/plain; charset=utf-8",
        chunked="deprecated",
    ):
        if chunked != "deprecated":
            warn("The chunked argument has been deprecated and will be "
                 "removed in v21.6")

        super().__init__()

        self.content_type = content_type
        self.streaming_fn = streaming_fn
        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None
示例#24
0
    def __init__(
            self,
            data=None,
            status=200,
            message=None,
            headers=None,
            content_type="application/json"
    ):
        self.content_type = content_type

        body = dict(code=status,msg=message, data=data)

        json_str = json_dumps(body, ensure_ascii=False)

        self.body = self._encode_body(json_str)

        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None
示例#25
0
文件: response.py 项目: rick-xu/sanic
    def __init__(
        self,
        streaming_fn: StreamingFunction,
        status: int = 200,
        headers: Optional[Union[Header, Dict[str, str]]] = None,
        content_type: str = "text/plain; charset=utf-8",
        chunked="deprecated",
    ):
        if chunked != "deprecated":
            warn("The chunked argument has been deprecated and will be "
                 "removed in v21.6")

        super().__init__()

        self.content_type = content_type
        self.streaming_fn = streaming_fn
        self.status = status
        self.headers = Header(headers or {})
        self._cookies = None
示例#26
0
class Request:
    """
    Properties of an HTTP request such as URL, headers, etc.
    """

    __slots__ = (
        "__weakref__",
        "_cookies",
        "_id",
        "_ip",
        "_parsed_url",
        "_port",
        "_protocol",
        "_remote_addr",
        "_socket",
        "_match_info",
        "_name",
        "app",
        "body",
        "conn_info",
        "ctx",
        "head",
        "headers",
        "method",
        "parsed_args",
        "parsed_not_grouped_args",
        "parsed_files",
        "parsed_form",
        "parsed_json",
        "parsed_forwarded",
        "raw_url",
        "request_middleware_started",
        "route",
        "stream",
        "transport",
        "version",
    )

    def __init__(
        self,
        url_bytes: bytes,
        headers: Header,
        version: str,
        method: str,
        transport: TransportProtocol,
        app: Sanic,
        head: bytes = b"",
    ):
        self.raw_url = url_bytes
        # TODO: Content-Encoding detection
        self._parsed_url = parse_url(url_bytes)
        self._id: Optional[Union[uuid.UUID, str, int]] = None
        self._name: Optional[str] = None
        self.app = app

        self.headers = Header(headers)
        self.version = version
        self.method = method
        self.transport = transport
        self.head = head

        # Init but do not inhale
        self.body = b""
        self.conn_info: Optional[ConnInfo] = None
        self.ctx = SimpleNamespace()
        self.parsed_forwarded: Optional[Options] = None
        self.parsed_json = None
        self.parsed_form = None
        self.parsed_files = None
        self.parsed_args: DefaultDict[Tuple[bool, bool, str, str],
                                      RequestParameters] = defaultdict(
                                          RequestParameters)
        self.parsed_not_grouped_args: DefaultDict[Tuple[
            bool, bool, str, str], List[Tuple[str, str]]] = defaultdict(list)
        self.request_middleware_started = False
        self._cookies: Optional[Dict[str, str]] = None
        self._match_info: Dict[str, Any] = {}
        self.stream: Optional[Http] = None
        self.route: Optional[Route] = None
        self._protocol = None

    def __repr__(self):
        class_name = self.__class__.__name__
        return f"<{class_name}: {self.method} {self.path}>"

    @classmethod
    def generate_id(*_):
        return uuid.uuid4()

    async def respond(
        self,
        response: Optional[BaseHTTPResponse] = None,
        *,
        status: int = 200,
        headers: Optional[Union[Header, Dict[str, str]]] = None,
        content_type: Optional[str] = None,
    ):
        # This logic of determining which response to use is subject to change
        if response is None:
            response = (self.stream and self.stream.response) or HTTPResponse(
                status=status,
                headers=headers,
                content_type=content_type,
            )
        # Connect the response
        if isinstance(response, BaseHTTPResponse) and self.stream:
            response = self.stream.respond(response)
        # Run response middleware
        try:
            response = await self.app._run_response_middleware(
                self, response, request_name=self.name)
        except CancelledErrors:
            raise
        except Exception:
            error_logger.exception(
                "Exception occurred in one of response middleware handlers")
        return response

    async def receive_body(self):
        """Receive request.body, if not already received.

        Streaming handlers may call this to receive the full body. Sanic calls
        this function before running any handlers of non-streaming routes.

        Custom request classes can override this for custom handling of both
        streaming and non-streaming routes.
        """
        if not self.body:
            self.body = b"".join([data async for data in self.stream])

    @property
    def name(self):
        if self._name:
            return self._name
        elif self.route:
            return self.route.name
        return None

    @property
    def endpoint(self):
        return self.name

    @property
    def uri_template(self):
        return f"/{self.route.path}"

    @property
    def protocol(self):
        if not self._protocol:
            self._protocol = self.transport.get_protocol()
        return self._protocol

    @property
    def raw_headers(self):
        _, headers = self.head.split(b"\r\n", 1)
        return bytes(headers)

    @property
    def request_line(self):
        reqline, _ = self.head.split(b"\r\n", 1)
        return bytes(reqline)

    @property
    def id(self) -> Optional[Union[uuid.UUID, str, int]]:
        """
        A request ID passed from the client, or generated from the backend.

        By default, this will look in a request header defined at:
        ``self.app.config.REQUEST_ID_HEADER``. It defaults to
        ``X-Request-ID``. Sanic will try to cast the ID into a ``UUID`` or an
        ``int``. If there is not a UUID from the client, then Sanic will try
        to generate an ID by calling ``Request.generate_id()``. The default
        behavior is to generate a ``UUID``. You can customize this behavior
        by subclassing ``Request``.

        .. code-block:: python

            from sanic import Request, Sanic
            from itertools import count

            class IntRequest(Request):
                counter = count()

                def generate_id(self):
                    return next(self.counter)

            app = Sanic("MyApp", request_class=IntRequest)
        """
        if not self._id:
            self._id = self.headers.getone(
                self.app.config.REQUEST_ID_HEADER,
                self.__class__.generate_id(self),  # type: ignore
            )

            # Try casting to a UUID or an integer
            if isinstance(self._id, str):
                try:
                    self._id = uuid.UUID(self._id)
                except ValueError:
                    try:
                        self._id = int(self._id)  # type: ignore
                    except ValueError:
                        ...

        return self._id  # type: ignore

    @property
    def json(self):
        if self.parsed_json is None:
            self.load_json()

        return self.parsed_json

    def load_json(self, loads=json_loads):
        try:
            self.parsed_json = loads(self.body)
        except Exception:
            if not self.body:
                return None
            raise InvalidUsage("Failed when parsing body as json")

        return self.parsed_json

    @property
    def token(self):
        """Attempt to return the auth header token.

        :return: token related to request
        """
        prefixes = ("Bearer", "Token")
        auth_header = self.headers.getone("authorization", None)

        if auth_header is not None:
            for prefix in prefixes:
                if prefix in auth_header:
                    return auth_header.partition(prefix)[-1].strip()

        return auth_header

    @property
    def form(self):
        if self.parsed_form is None:
            self.parsed_form = RequestParameters()
            self.parsed_files = RequestParameters()
            content_type = self.headers.getone("content-type",
                                               DEFAULT_HTTP_CONTENT_TYPE)
            content_type, parameters = parse_content_header(content_type)
            try:
                if content_type == "application/x-www-form-urlencoded":
                    self.parsed_form = RequestParameters(
                        parse_qs(self.body.decode("utf-8")))
                elif content_type == "multipart/form-data":
                    # TODO: Stream this instead of reading to/from memory
                    boundary = parameters["boundary"].encode("utf-8")
                    self.parsed_form, self.parsed_files = parse_multipart_form(
                        self.body, boundary)
            except Exception:
                error_logger.exception("Failed when parsing form")

        return self.parsed_form

    @property
    def files(self):
        if self.parsed_files is None:
            self.form  # compute form to get files

        return self.parsed_files

    def get_args(
        self,
        keep_blank_values: bool = False,
        strict_parsing: bool = False,
        encoding: str = "utf-8",
        errors: str = "replace",
    ) -> RequestParameters:
        """
        Method to parse `query_string` using `urllib.parse.parse_qs`.
        This methods is used by `args` property.
        Can be used directly if you need to change default parameters.

        :param keep_blank_values:
            flag indicating whether blank values in
            percent-encoded queries should be treated as blank strings.
            A true value indicates that blanks should be retained as blank
            strings.  The default false value indicates that blank values
            are to be ignored and treated as if they were  not included.
        :type keep_blank_values: bool
        :param strict_parsing:
            flag indicating what to do with parsing errors.
            If false (the default), errors are silently ignored. If true,
            errors raise a ValueError exception.
        :type strict_parsing: bool
        :param encoding:
            specify how to decode percent-encoded sequences
            into Unicode characters, as accepted by the bytes.decode() method.
        :type encoding: str
        :param errors:
            specify how to decode percent-encoded sequences
            into Unicode characters, as accepted by the bytes.decode() method.
        :type errors: str
        :return: RequestParameters
        """
        if (
                keep_blank_values,
                strict_parsing,
                encoding,
                errors,
        ) not in self.parsed_args:
            if self.query_string:
                self.parsed_args[(keep_blank_values, strict_parsing, encoding,
                                  errors)] = RequestParameters(
                                      parse_qs(
                                          qs=self.query_string,
                                          keep_blank_values=keep_blank_values,
                                          strict_parsing=strict_parsing,
                                          encoding=encoding,
                                          errors=errors,
                                      ))

        return self.parsed_args[(keep_blank_values, strict_parsing, encoding,
                                 errors)]

    args = property(get_args)

    def get_query_args(
        self,
        keep_blank_values: bool = False,
        strict_parsing: bool = False,
        encoding: str = "utf-8",
        errors: str = "replace",
    ) -> list:
        """
        Method to parse `query_string` using `urllib.parse.parse_qsl`.
        This methods is used by `query_args` property.
        Can be used directly if you need to change default parameters.

        :param keep_blank_values:
            flag indicating whether blank values in
            percent-encoded queries should be treated as blank strings.
            A true value indicates that blanks should be retained as blank
            strings.  The default false value indicates that blank values
            are to be ignored and treated as if they were  not included.
        :type keep_blank_values: bool
        :param strict_parsing:
            flag indicating what to do with parsing errors.
            If false (the default), errors are silently ignored. If true,
            errors raise a ValueError exception.
        :type strict_parsing: bool
        :param encoding:
            specify how to decode percent-encoded sequences
            into Unicode characters, as accepted by the bytes.decode() method.
        :type encoding: str
        :param errors:
            specify how to decode percent-encoded sequences
            into Unicode characters, as accepted by the bytes.decode() method.
        :type errors: str
        :return: list
        """
        if (
                keep_blank_values,
                strict_parsing,
                encoding,
                errors,
        ) not in self.parsed_not_grouped_args:
            if self.query_string:
                self.parsed_not_grouped_args[(
                    keep_blank_values, strict_parsing, encoding,
                    errors)] = parse_qsl(
                        qs=self.query_string,
                        keep_blank_values=keep_blank_values,
                        strict_parsing=strict_parsing,
                        encoding=encoding,
                        errors=errors,
                    )
        return self.parsed_not_grouped_args[(keep_blank_values, strict_parsing,
                                             encoding, errors)]

    query_args = property(get_query_args)
    """
    Convenience property to access :meth:`Request.get_query_args` with
    default values.
    """

    @property
    def cookies(self) -> Dict[str, str]:
        """
        :return: Incoming cookies on the request
        :rtype: Dict[str, str]
        """

        if self._cookies is None:
            cookie = self.headers.getone("cookie", None)
            if cookie is not None:
                cookies: SimpleCookie = SimpleCookie()
                cookies.load(cookie)
                self._cookies = {
                    name: cookie.value
                    for name, cookie in cookies.items()
                }
            else:
                self._cookies = {}
        return self._cookies

    @property
    def content_type(self) -> str:
        """
        :return: Content-Type header form the request
        :rtype: str
        """
        return self.headers.getone("content-type", DEFAULT_HTTP_CONTENT_TYPE)

    @property
    def match_info(self):
        """
        :return: matched info after resolving route
        """
        return self._match_info

    # Transport properties (obtained from local interface only)

    @property
    def ip(self) -> str:
        """
        :return: peer ip of the socket
        :rtype: str
        """
        return self.conn_info.client if self.conn_info else ""

    @property
    def port(self) -> int:
        """
        :return: peer port of the socket
        :rtype: int
        """
        return self.conn_info.client_port if self.conn_info else 0

    @property
    def socket(self):
        return self.conn_info.peername if self.conn_info else (None, None)

    @property
    def path(self) -> str:
        """
        :return: path of the local HTTP request
        :rtype: str
        """
        return self._parsed_url.path.decode("utf-8")

    # Proxy properties (using SERVER_NAME/forwarded/request/transport info)

    @property
    def forwarded(self) -> Options:
        """
        Active proxy information obtained from request headers, as specified in
        Sanic configuration.

        Field names by, for, proto, host, port and path are normalized.
        - for and by IPv6 addresses are bracketed
        - port (int) is only set by port headers, not from host.
        - path is url-unencoded

        Additional values may be available from new style Forwarded headers.

        :return: forwarded address info
        :rtype: Dict[str, str]
        """
        if self.parsed_forwarded is None:
            self.parsed_forwarded = (
                parse_forwarded(self.headers, self.app.config)
                or parse_xforwarded(self.headers, self.app.config) or {})
        return self.parsed_forwarded

    @property
    def remote_addr(self) -> str:
        """
        Client IP address, if available.
        1. proxied remote address `self.forwarded['for']`
        2. local remote address `self.ip`

        :return: IPv4, bracketed IPv6, UNIX socket name or arbitrary string
        :rtype: str
        """
        if not hasattr(self, "_remote_addr"):
            self._remote_addr = str(self.forwarded.get("for",
                                                       ""))  # or self.ip
        return self._remote_addr

    @property
    def scheme(self) -> str:
        """
        Determine request scheme.
        1. `config.SERVER_NAME` if in full URL format
        2. proxied proto/scheme
        3. local connection protocol

        :return: http|https|ws|wss or arbitrary value given by the headers.
        :rtype: str
        """
        if "//" in self.app.config.get("SERVER_NAME", ""):
            return self.app.config.SERVER_NAME.split("//")[0]
        if "proto" in self.forwarded:
            return str(self.forwarded["proto"])

        if (self.app.websocket_enabled
                and self.headers.getone("upgrade", "").lower() == "websocket"):
            scheme = "ws"
        else:
            scheme = "http"

        if self.transport.get_extra_info("sslcontext"):
            scheme += "s"

        return scheme

    @property
    def host(self) -> str:
        """
        The currently effective server 'host' (hostname or hostname:port).
        1. `config.SERVER_NAME` overrides any client headers
        2. proxied host of original request
        3. request host header
        hostname and port may be separated by
        `sanic.headers.parse_host(request.host)`.

        :return: the first matching host found, or empty string
        :rtype: str
        """
        server_name = self.app.config.get("SERVER_NAME")
        if server_name:
            return server_name.split("//", 1)[-1].split("/", 1)[0]
        return str(
            self.forwarded.get("host") or self.headers.getone("host", ""))

    @property
    def server_name(self) -> str:
        """
        :return: hostname the client connected to, by ``request.host``
        :rtype: str
        """
        return parse_host(self.host)[0] or ""

    @property
    def server_port(self) -> int:
        """
        The port the client connected to, by forwarded ``port`` or
        ``request.host``.

        Default port is returned as 80 and 443 based on ``request.scheme``.

        :return: port number
        :rtype: int
        """
        port = self.forwarded.get("port") or parse_host(self.host)[1]
        return int(port or (80 if self.scheme in ("http", "ws") else 443))

    @property
    def server_path(self) -> str:
        """
        :return: full path of current URL; uses proxied or local path
        :rtype: str
        """
        return str(self.forwarded.get("path") or self.path)

    @property
    def query_string(self) -> str:
        """
        :return: representation of the requested query
        :rtype: str
        """
        if self._parsed_url.query:
            return self._parsed_url.query.decode("utf-8")
        else:
            return ""

    @property
    def url(self) -> str:
        """
        :return: the URL
        :rtype: str
        """
        return urlunparse(
            (self.scheme, self.host, self.path, None, self.query_string, None))

    def url_for(self, view_name: str, **kwargs) -> str:
        """
        Same as :func:`sanic.Sanic.url_for`, but automatically determine
        `scheme` and `netloc` base on the request. Since this method is aiming
        to generate correct schema & netloc, `_external` is implied.

        :param kwargs: takes same parameters as in :func:`sanic.Sanic.url_for`
        :return: an absolute url to the given view
        :rtype: str
        """
        # Full URL SERVER_NAME can only be handled in app.url_for
        try:
            if "//" in self.app.config.SERVER_NAME:
                return self.app.url_for(view_name, _external=True, **kwargs)
        except AttributeError:
            pass

        scheme = self.scheme
        host = self.server_name
        port = self.server_port

        if (scheme.lower() in ("http", "ws")
                and port == 80) or (scheme.lower() in ("https", "wss")
                                    and port == 443):
            netloc = host
        else:
            netloc = f"{host}:{port}"

        return self.app.url_for(view_name,
                                _external=True,
                                _scheme=scheme,
                                _server=netloc,
                                **kwargs)
示例#27
0
class BaseHTTPResponse:
    """
    The base class for all HTTP Responses
    """

    def __init__(self):
        self.asgi: bool = False
        self.body: Optional[bytes] = None
        self.content_type: Optional[str] = None
        self.stream: Http = None
        self.status: int = None
        self.headers = Header({})
        self._cookies: Optional[CookieJar] = None

    def _encode_body(self, data: Optional[AnyStr]):
        if data is None:
            return b""
        return (
            data.encode() if hasattr(data, "encode") else data  # type: ignore
        )

    @property
    def cookies(self) -> CookieJar:
        """
        The response cookies. Cookies should be set and written as follows:

        .. code-block:: python

                response.cookies["test"] = "It worked!"
                response.cookies["test"]["domain"] = ".yummy-yummy-cookie.com"
                response.cookies["test"]["httponly"] = True

        `See user guide
        <https://sanicframework.org/guide/basics/cookies.html>`_

        :return: the cookie jar
        :rtype: CookieJar
        """
        if self._cookies is None:
            self._cookies = CookieJar(self.headers)
        return self._cookies

    @property
    def processed_headers(self) -> Iterator[Tuple[bytes, bytes]]:
        """
        Obtain a list of header tuples encoded in bytes for sending.

        Add and remove headers based on status and content_type.

        :return: response headers
        :rtype: Tuple[Tuple[bytes, bytes], ...]
        """
        # TODO: Make a blacklist set of header names and then filter with that
        if self.status in (304, 412):  # Not Modified, Precondition Failed
            self.headers = remove_entity_headers(self.headers)
        if has_message_body(self.status):
            self.headers.setdefault("content-type", self.content_type)
        # Encode headers into bytes
        return (
            (name.encode("ascii"), f"{value}".encode(errors="surrogateescape"))
            for name, value in self.headers.items()
        )

    async def send(
        self,
        data: Optional[Union[AnyStr]] = None,
        end_stream: Optional[bool] = None,
    ) -> None:
        """
        Send any pending response headers and the given data as body.

        :param data: str or bytes to be written
        :param end_stream: whether to close the stream after this block
        """
        if data is None and end_stream is None:
            end_stream = True
        if end_stream and not data and self.stream.send is None:
            return
        data = (
            data.encode()  # type: ignore
            if hasattr(data, "encode")
            else data or b""
        )
        await self.stream.send(data, end_stream=end_stream)
示例#28
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)
示例#29
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
示例#30
0
def make_mocked_request(method: str,
                        path: bytes,
                        headers=None,
                        *args,
                        version=(1, 1),
                        closing=False,
                        app=None,
                        writer=sentinel,
                        protocol=sentinel,
                        transport=sentinel,
                        payload=sentinel,
                        sslcontext=None,
                        client_max_size=1024**2,
                        loop=None,
                        stream=False):
    """Creates mocked web.Request testing purposes.
    Useful in unit tests, when spinning full web server is overkill or
    specific conditions and errors are hard to trigger.
    """

    task = mock.Mock()
    if loop is None:
        loop = mock.Mock()
        loop.create_future.return_value = ()

    if version < (1, 1):
        closing = True

    if headers:
        headers = MultiDict(headers)
        raw_hdrs = tuple(
            (k.encode('utf-8'), v.encode('utf-8')) for k, v in headers.items())
    else:
        headers = MultiDict()
        raw_hdrs = ()

    chunked = 'chunked' in headers.get('Transfer-Encoding', '').lower()

    # message = RawRequestMessage(
    #     method, path, version, headers,
    #     raw_hdrs, closing, False, False, chunked, URL(path))
    if app is None:
        app = _create_app_mock()

    if transport is sentinel:
        transport = _create_transport(sslcontext)

    if protocol is sentinel:
        protocol = mock.Mock()
        protocol.transport = transport

    if writer is sentinel:
        writer = mock.Mock()
        writer.write_headers = make_mocked_coro(None)
        writer.write = make_mocked_coro(None)
        writer.write_eof = make_mocked_coro(None)
        writer.drain = make_mocked_coro(None)
        writer.transport = transport

    protocol.transport = transport
    protocol.writer = writer

    if payload is sentinel:
        payload = b""

    if sanic_19_6 > sanic_version:
        req = Request(path, headers, version, method, transport=transport)
        req.app = app
    else:
        req = Request(path,
                      headers,
                      version,
                      method,
                      transport=transport,
                      app=app)
    if stream:
        mock_stream = mock.Mock()
        mock_stream.read = make_mocked_coro(payload)
        req.stream = mock_stream
    else:
        req.body_push(payload)
        req.body_finish()
    # req = Request(message, payload,
    #               protocol, writer, task, loop,
    #               client_max_size=client_max_size)

    return req