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))
Example #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))
Example #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"))
Example #5
0
def _make_error_response(error: HTTPError) -> HttpResponse:
    if isinstance(error.reason, str):
        content: Optional[Content] = text_writer(error.reason)
    elif isinstance(error.reason, bytes):
        content = bytes_writer(error.reason)
    else:
        content = None

    return error.code, cast(Optional[Headers], error.headers), content, None
    async def _read(self, _scope: Scope, _info: Info, matches: RouteMatches,
                    _content: Content) -> HttpResponse:
        try:
            id_: Optional[int] = matches.get('id')
            if id_ is None:
                raise RuntimeError('Invalid id')

            entry = await self._repository.read_by_id(id_, None)
            if entry is None:
                return 404

            return (200, [(b'content-type', b'application/json')],
                    text_writer(json.dumps(entry, cls=DateEncoder)))
        except:  # pylint: disable=bare-except
            return 500
    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))
    async def _read_between(self, scope: Scope, _info: Info,
                            _matches: RouteMatches,
                            _content: Content) -> HttpResponse:
        try:
            query = dict(parse_qsl(scope['query_string'] or b''))

            end_date = _parse_date(query.get(b'to'), datetime.utcnow())
            start_date = _parse_date(query.get(b'from'),
                                     end_date - timedelta(5))
            limit = _parse_int(query.get(b'limit'), 20)

            entries = await self._repository.read_between(
                start_date, end_date, ['title', 'description'], limit)

            return (200, [(b'content-type', b'application/json')],
                    text_writer(json.dumps(entries, cls=DateEncoder)))
        except:  # pylint: disable=bare-except
            return 500
    async def _create(self, scope: Scope, _info: Info, _matches: RouteMatches,
                      content: Content) -> HttpResponse:
        try:
            if not _is_form_data(scope):
                raise RuntimeError("Invalid content type")

            text = await text_reader(content)
            args = dict(parse_qsl(text))

            id_ = await self._repository.create(**args)

            return (200, [(b'content-type', b'application/json')],
                    text_writer(
                        json.dumps({
                            'id': id_,
                            'read': f'{self._path}/{id_}'
                        })))
        except:  # pylint: disable=bare-except
            return 500
    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 get_info(request: HttpRequest) -> HttpResponse:
    text = json.dumps(request.info)
    return HttpResponse(200, [(b'content-type', b'application/json')], text_writer(text))
Example #13
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)