async def view_graphiql(self, request: HttpRequest) -> HttpResponse:
        """Render the Graphiql view

        Args:
            request (HttpRequest): The request.

        Returns:
            HttpResponse: The response.
        """

        try:
            host = get_host(request)
            scheme = get_scheme(request)
            query_path = f'{scheme}://{host}{self.path_prefix}/graphql'
            ws_scheme = 'ws' if scheme == 'http' else 'wss'
            subscription_path = f'{ws_scheme}://{host}{self.path_prefix}/subscriptions'
            body = make_template(host, query_path, subscription_path)
            headers = [(b'content-type', b'text/html'),
                       (b'content-length', str(len(body)).encode())]
            return HttpResponse(response_code.OK, headers, text_writer(body))

        # pylint: disable=bare-except
        except:
            LOGGER.exception("Failed to handle grahphiql request")

            text = 'Internal server error'
            headers = [(b'content-type', b'text/plain'),
                       (b'content-length', str(len(text)).encode())]
            return HttpResponse(response_code.INTERNAL_SERVER_ERROR, headers,
                                text_writer(text))
    async def handle_subscription_post(self,
                                       request: HttpRequest) -> HttpResponse:
        """Handle a streaming subscription

        Args:
            request (HttpRequest): The request

        Returns:
            HttpResponse: A stream response
        """

        try:
            LOGGER.debug(
                "Received POST streaming subscription request: http_version='%s'.",
                request.scope['http_version'])

            text = await text_reader(request.body)
            body = self.loads(text)

            query: str = body['query']
            variables: Optional[Dict[str, Any]] = body.get('variables')
            operation_name: Optional[str] = body.get('operationName')

            return await self._handle_streaming_subscription(
                request, query, variables, operation_name)

        # pylint: disable=bare-except
        except:
            LOGGER.exception("Failed to handle graphql POST subscription")

            text = 'Internal server error'
            headers = [(b'content-type', b'text/plain'),
                       (b'content-length', str(len(text)).encode())]
            return HttpResponse(response_code.INTERNAL_SERVER_ERROR, headers,
                                text_writer(text))
Exemple #3
0
async def graphql_handler(request: HttpRequest) -> HttpResponse:
    """Handle a graphql request"""
    host = header.find(b'host', request.scope['headers']).decode()
    sse_url = f"{request.scope['scheme']}://{host}/sysmon/graphql"
    html = request.info['page_template'].substitute(sse_url=sse_url)
    return HttpResponse(200, [(b'content-type', b'text/html')],
                        text_writer(html))
Exemple #4
0
    async def __call__(self, request: HttpRequest) -> HttpResponse:
        if request.scope["method"] not in ("GET", "HEAD"):
            return HttpResponse(response_code.METHOD_NOT_ALLOWED,
                                [(b'content-type', b'text/plain')],
                                text_writer("Method Not Allowed"))

        try:
            # Get the path from the scope or the route match.
            path: str = '/' + \
                request.matches.get(
                    self.path_variable,
                    '') if self.path_variable else request.scope["path"]
            if (path == '' or path.endswith('/')) and self.index_filename:
                path += self.index_filename

            relative_path = os.path.normpath(os.path.join(*path.split("/")))
            if relative_path.startswith(".."):
                raise FileNotFoundError()

            rooted_path = os.path.join(self.source_folder, relative_path)

            if self.config_checked:
                check_directory = None
            else:
                check_directory = self.source_folder
                self.config_checked = True

            if check_directory is not None:
                stat_result = await aio_stat(check_directory)
                if not (stat.S_ISDIR(stat_result.st_mode)
                        or stat.S_ISLNK(stat_result.st_mode)):
                    raise FileNotFoundError()

            stat_result = await aio_stat(rooted_path)
            mode = stat_result.st_mode
            if not stat.S_ISREG(mode):
                raise FileNotFoundError()

            return await file_response(request,
                                       200,
                                       rooted_path,
                                       check_modified=True)
        except FileNotFoundError:
            return HttpResponse(response_code.NOT_FOUND,
                                [(b'content-type', b'text/plain')],
                                text_writer("Not Found"))
    async def _handle_streaming_subscription(
            self, request: HttpRequest, query: str,
            variables: Optional[Dict[str, Any]],
            operation_name: Optional[str]) -> HttpResponse:
        # If unspecified default to server sent events as they have better support.
        accept = cast(
            bytes,
            header.find(b'accept', request.scope['headers'],
                        b'text/event-stream'))
        content_type = (b'application/stream+json'
                        if accept == b'application/json' else accept)

        result = await self.subscribe(request, query, variables,
                                      operation_name)

        is_sse = content_type == b'text/event-stream'
        encode = partial(_encode_sse if is_sse else _encode_json, self.dumps)
        nudge = b':\n\n' if is_sse else b'\n'

        # Make an async iterator for the subscription results.
        async def send_events(zero_event: ZeroEvent) -> AsyncIterable[bytes]:
            LOGGER.debug('Streaming subscription started.')

            try:
                zero_event.increment()

                async for val in cancellable_aiter(result,
                                                   self.cancellation_event,
                                                   timeout=self.ping_interval):
                    yield encode(val)
                    yield nudge  # Give the ASGI server a nudge.

            except asyncio.CancelledError:
                LOGGER.debug("Streaming subscription cancelled.")
            except Exception as error:  # pylint: disable=broad-except
                LOGGER.exception("Streaming subscription failed.")
                # If the error is not caught the client fetch will fail, however
                # the status code and headers have already been sent. So rather
                # than let the fetch fail we send a GraphQL response with no
                # data and the error and close gracefully.
                if not isinstance(error, GraphQLError):
                    error = GraphQLError('Execution error',
                                         original_error=error)
                val = ExecutionResult(None, [error])
                yield encode(val)
                yield nudge  # Give the ASGI server a nudge.
            finally:
                zero_event.decrement()

            LOGGER.debug("Streaming subscription stopped.")

        headers = [(b'cache-control', b'no-cache'),
                   (b'content-type', content_type),
                   (b'connection', b'keep-alive')]

        return HttpResponse(response_code.OK, headers,
                            send_events(self.subscription_count))
Exemple #6
0
async def session_handler(request: HttpRequest) -> HttpResponse:
    session = session_data(request)
    now = session.get('now')
    message = f'The time was {now}' if now else 'First time'
    session['now'] = datetime.now()
    headers: List[Tuple[bytes, bytes]] = [(b'content-type', b'text/plain'),
                                          (b'content-length',
                                           str(len(message)).encode('ascii'))]
    return HttpResponse(200, headers, text_writer(message))
Exemple #7
0
async def prometheus_view(_request: HttpRequest) -> HttpResponse:
    """The endpoint for prometheus stats

    Args:
        _request (HttpRequest): The request.

    Returns:
        HttpResponse: The prometheus statistics
    """
    headers = [(header.CONTENT_TYPE, CONTENT_TYPE_LATEST.encode('ascii'))]
    body = generate_latest()
    return HttpResponse(response_code.OK, headers, bytes_writer(body))
    async def _handle_query_or_mutation(
            self, request: HttpRequest, query: str,
            variables: Optional[Dict[str, Any]],
            operation_name: Optional[str]) -> HttpResponse:
        LOGGER.debug("Processing a query or mutation.")

        result = await self.query(request, query, variables, operation_name)

        response: Dict[str, Any] = {'data': result.data}
        if result.errors:
            response['errors'] = [error.formatted for error in result.errors]

        text = self.dumps(response)
        headers = [(b'content-type', b'application/json'),
                   (b'content-length', str(len(text)).encode())]

        return HttpResponse(response_code.OK, headers, text_writer(text))
 def _handle_subscription_redirect(self, request: HttpRequest,
                                   body: Mapping[str, Any]) -> HttpResponse:
     # Handle a subscription by returning 201 (Created) with
     # the url location of the subscription.
     LOGGER.debug("Redirecting subscription request.")
     scheme = request.scope['scheme']
     host = cast(
         bytes,
         header.find(  # type: ignore
             b'host', request.scope['headers'], b'localhost')).decode()
     path = self.path_prefix + '/subscriptions'
     query_string = urlencode({
         name.encode('utf-8'): self.dumps(value).encode('utf-8')
         for name, value in body.items()
     })
     location = f'{scheme}://{host}{path}?{query_string}'.encode('ascii')
     headers = [(b'access-control-expose-headers', b'location'),
                (b'location', location)]
     return HttpResponse(response_code.CREATED, headers)
Exemple #10
0
    def _add_session_key_cookie_to_response(
            self,
            session_key: str,
            request: HttpRequest,
            response: HttpResponse
    ) -> HttpResponse:
        set_cookie_header = self._make_set_cookie_header(request, session_key)

        headers = self._add_set_cookie_header(
            response.headers or [],
            set_cookie_header
        )

        return HttpResponse(
            response.status,
            headers,
            response.body,
            response.pushes
        )
    async def handle_graphql(self, request: HttpRequest) -> HttpResponse:
        """A request handler for graphql queries

        Args:
            scope (Scope): The Request

        Returns:
            HttpResponse: The HTTP response to the query request
        """

        try:
            body = await self._get_query_document(request)

            query: str = body['query']
            variables: Optional[Dict[str, Any]] = body.get('variables')
            operation_name: Optional[str] = body.get('operationName')

            query_document = graphql.parse(query)

            if not has_subscription(query_document):
                return await self._handle_query_or_mutation(
                    request, query, variables, operation_name)

            # The subscription method is determined by the `allow` header.
            allow = header.find(b'allow', request.scope['headers'], b'GET')
            if allow == b'GET':
                return self._handle_subscription_redirect(request, body)

            return await self._handle_streaming_subscription(
                request, query, variables, operation_name)

        # pylint: disable=bare-except
        except:
            LOGGER.exception("Failed to handle graphql query request")

            text = 'Internal server error'
            headers = [(b'content-type', b'text/plain'),
                       (b'content-length', str(len(text)).encode())]
            return HttpResponse(response_code.INTERNAL_SERVER_ERROR, headers,
                                text_writer(text))
    async def handle_subscription_get(self,
                                      request: HttpRequest) -> HttpResponse:
        """Handle a streaming subscription

        Args:
            request (HttpRequest): The request

        Returns:
            HttpResponse: The streaming response
        """

        try:
            LOGGER.debug(
                "Received GET streaming subscription request: http_version='%s'.",
                request.scope['http_version'])

            body = {
                name.decode('utf-8'): self.loads(value[0].decode('utf-8'))
                for name, value in cast(
                    Dict[bytes, List[bytes]],
                    parse_qs(request.scope['query_string'])).items()
            }

            query: str = body['query']
            variables: Optional[Dict[str, Any]] = body.get('variables')
            operation_name: Optional[str] = body.get('operationName')

            return await self._handle_streaming_subscription(
                request, query, variables, operation_name)

        # pylint: disable=bare-except
        except:
            LOGGER.exception("Failed to handle graphql GET subscription")

            text = 'Internal server error'
            headers = [(b'content-type', b'text/plain'),
                       (b'content-length', str(len(text)).encode())]
            return HttpResponse(response_code.INTERNAL_SERVER_ERROR, headers,
                                text_writer(text))
async def set_info(request: HttpRequest) -> HttpResponse:
    text = await text_reader(request.body)
    data = json.loads(text)
    request.info.update(data)
    return HttpResponse(204)
async def get_info(request: HttpRequest) -> HttpResponse:
    text = json.dumps(request.info)
    return HttpResponse(200, [(b'content-type', b'application/json')], text_writer(text))
Exemple #15
0
async def index_handler(_request: HttpRequest) -> HttpResponse:
    """Redirect to the session handler"""
    return HttpResponse(303, [(b'location', b'/session')])
Exemple #16
0
async def file_response(
        request: HttpRequest,
        status: int,
        path: str,
        headers: Optional[List[Tuple[bytes, bytes]]] = None,
        content_type: Optional[str] = None,
        filename: Optional[str] = None,
        check_modified: Optional[bool] = False) -> HttpResponse:
    """A utility method to create a file response.

    Args:
        scope (Scope): The ASGI scope.
        status (int): The HTTP status code.
        path (str): The path to the file.
        headers (Optional[Headers], optional): The headers. Defaults to None.
        content_type (Optional[str], optional): The content type.. Defaults to
            None.
        filename (Optional[str], optional): The filename. Defaults to None.
        check_modified (Optional[bool], optional): If True check for
            modifications to the file. Defaults to False.

    Raises:
        RuntimeError: If the path was not a file.

    Returns:
        HttpResponse: The HTTP response
    """
    try:
        stat_result = await aiofiles.os.stat(path)
        mode = stat_result.st_mode
        if not stat.S_ISREG(mode):
            raise RuntimeError(f"File at path {path} is not a file.")

        if not headers:
            headers = []

        if content_type is None:
            content_type = guess_type(filename or path)[0] or "text/plain"
        headers.append((b'content-type', content_type.encode()))

        headers.append((b'content-length', str(stat_result.st_size).encode()))
        headers.append(
            (b'last-modified', formatdate(stat_result.st_mtime,
                                          usegmt=True).encode()))
        headers.append((b'etag', _stat_to_etag(stat_result).encode()))

        if filename is not None:
            content_disposition = f'attachment; filename="{filename}"'
            headers.append(
                (b"content-disposition", content_disposition.encode()))

        if check_modified and _is_not_modified(request.scope['headers'],
                                               headers):
            return HttpResponse(
                response_code.NOT_MODIFIED,
                [(name, value)
                 for name, value in headers if name in NOT_MODIFIED_HEADERS],
                None)

        return HttpResponse(
            status, headers,
            None if request.scope['method'] == 'HEAD' else file_writer(path))

    except FileNotFoundError:
        return HttpResponse(
            response_code.INTERNAL_SERVER_ERROR,
            [(b'content-type', b'text/plain')],
            text_writer(f"File at path {path} does not exist."))
    except RuntimeError:
        return HttpResponse(response_code.INTERNAL_SERVER_ERROR)