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))
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))
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))
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))
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)
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))
async def index_handler(_request: HttpRequest) -> HttpResponse: """Redirect to the session handler""" return HttpResponse(303, [(b'location', b'/session')])
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)