def test_upsert(): """Test upsert""" headers = [(b'content-type', b'application/json'), (b'vary', b'accept-encoding, user-agent'), (b'cookie', b'one=first; two=second; three=third;')] header.upsert(b'content-type', b'text/plain', headers) assert len(header.find_all(b'content-type', headers)) == 1 assert header.find(b'content-type', headers) == b'text/plain' header.upsert(b'content-encoding', b'gzip', headers) assert len(header.find_all(b'content-encoding', headers)) == 1 assert header.find(b'content-encoding', headers) == b'gzip'
async def get_info(scope, info, matches, content): accept = header.find(b'accept', scope['headers']) if accept != b'application/json': return 500 text = json.dumps(info) headers = [(b'content-type', b'application/json')] return 200, headers, text_writer(text)
async def test_page(scope: Scope, info: Info, _matches: RouteMatches, _content: Content) -> HttpResponse: """A request handler which provides the page to respond to server sent events""" host = header.find(b'host', scope['headers']).decode() fetch_url = f"{scope['scheme']}://{host}/events" html = info['page_template'].substitute(__FETCH_URL__=fetch_url) return 200, [(b'content-type', b'text/html')], text_writer(html)
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 _renew_cookie( self, scope: Scope, token: bytes ) -> Optional[bytes]: scheme = get_scheme(scope).decode('ascii') host = get_host(scope).decode('ascii') referer_url = f'{scheme}://{host}{scope["path"]}' if scope['query_string']: referer_url += '?' + scope['query_string'].decode('utf-8') referer = header.find( b'referer', scope['headers'], referer_url.encode('ascii') ) headers: List[Header] = [ (b'host', host.encode('ascii')), (b'referer', referer), (b'content-length', b'0'), (b'connection', b'close') ] if token is not None: cookie = self.token_manager.cookie_name + b'=' + token headers.append((b'cookie', cookie)) renewal_url = f'{scheme}://{host}{self.token_renewal_path}' logger.debug( 'Renewing cookie at %s with headers %s', renewal_url, headers ) async with HttpClient( renewal_url, method='POST', headers=headers, cafile=self.cafile ) as response: if response['status_code'] == response_code.NO_CONTENT: logger.debug('Cookie renewed') all_set_cookies = header.set_cookie(response['headers']) auth_set_cookies = all_set_cookies.get( self.token_manager.cookie_name) if auth_set_cookies is None: raise RuntimeError('No cookie returned') kwargs = auth_set_cookies[0] set_cookie = encode_set_cookie(**kwargs) return set_cookie elif response['status_code'] == response_code.UNAUTHORIZED: logger.debug( 'Cookie not renewed - client requires authentication') return None else: logger.debug('Cookie not renewed - failed to authenticate') raise Exception()
def _is_not_modified(request_headers: Iterable[Tuple[bytes, bytes]], response_headers: Iterable[Tuple[bytes, bytes]]) -> bool: if request_headers is None or response_headers is None: return False etag = header.find(b'etag', response_headers) last_modified = header.find(b'last-modified', response_headers) assert last_modified is not None if etag == header.find(b'if-none-match', request_headers): return True if not header.find(b'if-modified-since', request_headers): return False last_req_time = header.find(b'if-modified-since', request_headers) assert last_req_time is not None last_req = cast(struct_time, parsedate(last_req_time.decode())) last_modified_struct_time = cast(struct_time, parsedate(last_modified.decode())) return mktime(last_req) >= mktime(last_modified_struct_time)
async def set_info(scope, info, matches, content): content_type = header.find(b'content-type', scope['headers']) if content_type != b'application/json': return 500 text = await text_reader(content) data = json.loads(text) info.update(data) return 204
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 get_info(scope: Scope, info: Info, _matches: RouteMatches, _content: Content) -> HttpResponse: """Handle the GET request""" accept = header.find(b'accept', scope['headers']) if accept != b'application/json': return 500 text = json.dumps(info) headers = [(b'content-type', b'application/json')] return 200, headers, text_writer(text)
async def set_info(scope: Scope, info: Info, _matches: RouteMatches, content: Content) -> HttpResponse: """Handle the POST request""" content_type = header.find(b'content-type', scope['headers']) if content_type != b'application/json': return 500 text = await text_reader(content) data = json.loads(text) info.update(data) return 204
async def login_post( self, scope: Scope, info: Info, matches: RouteMatches, content: Content ) -> HttpResponse: """A login POST request handler :param scope: The ASGI scope :type scope: Scope :param info: The application shared info :type info: Info :param matches: The route matches :type matches: RouteMatches :param content: The ASGI content :type content: Content :return: A login response :rtype: HttpResponse """ try: query = parse_qs(scope['query_string']) redirect = query.get(b'redirect') if not redirect: logger.debug('No redirect') return text_response(response_code.NOT_FOUND, None, 'No redirect') redirect = redirect[0] text = await text_reader(content) body = parse_qs(text) username = body['username'][0] password = body['password'][0] if not await self.authentication_service.is_password_for_user(username, password): raise RuntimeError('Invalid username or password') now = datetime.utcnow() token = self.token_manager.encode(username, now, now) logger.debug('Sending token: %s', token) urlparts = urlparse(redirect) if urlparts.scheme is None or not urlparts.scheme: raise RuntimeError('The redirect URL has no scheme') set_cookie = self.token_manager.make_cookie(token) return response_code.FOUND, [(b'set-cookie', set_cookie), (b'location', redirect)] except: # pylint: disable=bare-except logger.exception('Failed to log in') location = header.find(b'referer', scope['headers']) return response_code.FOUND, [(b'location', location)]
async def websocket_page(scope, info, matches, content): scheme = 'wss' if scope['scheme'] == 'https' else 'ws' if scope['http_version'] in ('2', '2.0'): authority = header.find(b':authority', scope['headers']).decode('ascii') else: host, port = scope['server'] authority = f'{host}:{port}' web_socket_url = f"{scheme}://{authority}/websocket_handler" print(web_socket_url) page = info['html'].replace('WEB_SOCKET_URL', web_socket_url) return 200, [(b'content-type', b'text/html')], text_writer(page)
async def post_form(scope, info, matches, content): content_type = header.find(b'content-type', scope['headers']) if content_type != b'application/x-www-form-urlencoded': return 500 variables = await text_reader(content) values = dict(parse_qsl(variables)) first_name = values['first_name'] last_name = values['last_name'] headers = [ (b'location', b'/get_form'), (b'set-cookie', f'first_name={first_name}'.encode()), (b'set-cookie', f'last_name={last_name}'.encode()), ] return 303, headers
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)
async def post_form( scope: Scope, _info: Info, _matches: RouteMatches, content: Content ) -> HttpResponse: """A response handler that reads the cookies from a posted form.""" content_type = header.find(b'content-type', scope['headers']) if content_type != b'application/x-www-form-urlencoded': return 500 variables = await text_reader(content) values: Dict[str, str] = dict(parse_qsl(variables)) first_name = values['first_name'] last_name = values['last_name'] headers = [ (b'location', b'/get_form'), (b'set-cookie', f'first_name={first_name}'.encode()), (b'set-cookie', f'last_name={last_name}'.encode()), ] return 303, headers
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 test_page( scope: Scope, _info: Info, _matches: RouteMatches, _content: Content ) -> HttpResponse: """Send the page with the example web socket""" scheme = 'wss' if scope['scheme'] == 'https' else 'ws' if scope['http_version'] in ('2', '2.0'): authority = header.find( b':authority', scope['headers']).decode('ascii') else: host, port = scope['server'] authority = f'{host}:{port}' web_socket_url = f"{scheme}://{authority}/test" print(web_socket_url) page = """ <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebSocket Example</title> <style> *, *:before, *:after {{ -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }} html {{ font-family: Helvetica, Arial, sans-serif; font-size: 100%; background: #333; }} #page-wrapper {{ width: 650px; background: #FFF; padding: 1em; margin: 1em auto; border-top: 5px solid #69c773; box-shadow: 0 2px 10px rgba(0,0,0,0.8); }} h1 {{ margin-top: 0; }} #status {{ font-size: 0.9rem; margin-bottom: 1rem; }} .open {{ color: green; }} .closed {{ color: red; }} ul {{ list-style: none; margin: 0; padding: 0; font-size: 0.95rem; }} ul li {{ padding: 0.5rem 0.75rem; border-bottom: 1px solid #EEE; }} ul li:first-child {{ border-top: 1px solid #EEE; }} ul li span {{ display: inline-block; width: 90px; font-weight: bold; color: #999; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 1px; }} .sent {{ background-color: #F7F7F7; }} .received {{}} #message-form {{ margin-top: 1.5rem; }} textarea {{ width: 100%; padding: 0.5rem; font-size: 1rem; border: 1px solid #D9D9D9; border-radius: 3px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1); min-height: 100px; margin-bottom: 1rem; }} button {{ display: inline-block; border-radius: 3px; border: none; font-size: 0.9rem; padding: 0.6rem 1em; color: white; margin: 0 0.25rem; text-align: center; background: #BABABA; border-bottom: 1px solid #999; }} button[type="submit"] {{ background: #86b32d; border-bottom: 1px solid #5d7d1f; }} button:hover {{ opacity: 0.75; cursor: pointer; }} </style> </head> <body> <div id="page-wrapper"> <h1>WebSockets Demo</h1> <div id="status">Connecting...</div> <ul id="messages"></ul> <form id="message-form" action="#" method="post"> <textarea id="message" placeholder="Write your message here..." required></textarea> <button type="submit">Send Message</button> <button type="button" id="close">Close Connection</button> </form> </div> <script language="javascript" type="text/javascript"> window.onload = function() {{ // Get references to elements on the page. var form = document.getElementById('message-form'); var messageField = document.getElementById('message'); var messagesList = document.getElementById('messages'); var socketStatus = document.getElementById('status'); var closeBtn = document.getElementById('close'); // Create a new WebSocket. var socket = new WebSocket('{web_socket_url}'); // Handle any errors that occur. socket.onerror = function(error) {{ console.log('WebSocket Error: ' + error); }}; // Show a connected message when the WebSocket is opened. socket.onopen = function(event) {{ socketStatus.innerHTML = 'Connected to: ' + event.currentTarget.url; socketStatus.className = 'open'; }}; // Handle messages sent by the server. socket.onmessage = function(event) {{ var message = event.data; messagesList.innerHTML += '<li class="received"><span>Received:</span>' + message + '</li>'; }}; // Show a disconnected message when the WebSocket is closed. socket.onclose = function(event) {{ socketStatus.innerHTML = 'Disconnected from WebSocket.'; socketStatus.className = 'closed'; }}; // Send a message when the form is submitted. form.onsubmit = function(e) {{ e.preventDefault(); // Retrieve the message from the textarea. var message = messageField.value; // Send the message through the WebSocket. socket.send(message); // Add the message to the messages list. messagesList.innerHTML += '<li class="sent"><span>Sent:</span>' + message + '</li>'; // Clear out the message field. messageField.value = ''; return false; }}; // Close the WebSocket connection when the close button is clicked. closeBtn.onclick = function(e) {{ e.preventDefault(); // Close the WebSocket. socket.close(); return false; }}; }}; </script> </body> </html> """.format(web_socket_url=web_socket_url) return 200, [(b'content-type', b'text/html')], text_writer(page)
async def test_page(scope, info, matches, content): host = header.find(b'host', scope['headers']).decode() fetch_url = f"{scope['scheme']}://{host}/events" html = info['page_template'].substitute(__FETCH_URL__=fetch_url) return 200, [(b'content-type', b'text/html')], text_writer(html)
def make_url(scope) -> str: host = header.find(b'host', scope['headers'], b'unknown').decode() return f"{scope['scheme']}://{host}{scope['path']}"
def make_url(scope: Scope) -> str: """Make the url from the scope""" host = header.find(b'host', scope['headers'], b'unknown').decode() return f"{scope['scheme']}://{host}{scope['path']}"
async def index(scope: Scope, _info: Info, _matches: RouteMatches, _content: Content) -> HttpResponse: """The Websocket page""" scheme = 'wss' if scope['scheme'] == 'https' else 'ws' if scope['http_version'] in ('2', '2.0'): authority = header.find(b':authority', scope['headers']).decode('ascii') else: host, port = scope['server'] authority = f'{host}:{port}' web_socket_url = f"{scheme}://{authority}/test/websocket" html = """ <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Websocket Example</title> </head> <body> <h1>Websocket Example</h1> <div id="status">Connecting...</div> <ul id="messages"></ul> <form id="message-form" action="#" method="post"> <textarea id="message" placeholder="Write your message here..." required></textarea> <button type="submit">Send Message</button> <button type="button" id="close">Close Connection</button> </form> <script language="javascript" type="text/javascript"> window.onload = function() {{ // Get references to elements on the page. var form = document.getElementById('message-form') var messageField = document.getElementById('message') var messagesList = document.getElementById('messages') var socketStatus = document.getElementById('status') var closeBtn = document.getElementById('close') // Create a new WebSocket. var socket = new WebSocket('{web_socket_url}') // Handle any errors that occur. socket.onerror = function(error) {{ console.log('WebSocket Error: ' + error) }} // Show a connected message when the WebSocket is opened. socket.onopen = function(event) {{ socketStatus.innerHTML = 'Connected to: ' + event.currentTarget.url socketStatus.className = 'open' }} // Handle messages sent by the server. socket.onmessage = function(event) {{ var message = event.data messagesList.innerHTML += '<li class="received"><span>Received:</span>' + message + '</li>' }} // Show a disconnected message when the WebSocket is closed. socket.onclose = function(event) {{ socketStatus.innerHTML = 'Disconnected from WebSocket.' socketStatus.className = 'closed' }} // Send a message when the form is submitted. form.onsubmit = function(e) {{ e.preventDefault() // Retrieve the message from the textarea. var message = messageField.value // Send the message through the WebSocket. socket.send(message) // Add the message to the messages list. messagesList.innerHTML += '<li class="sent"><span>Sent:</span>' + message + '</li>' // Clear out the message field. messageField.value = '' return false }} // Close the WebSocket connection when the close button is clicked. closeBtn.onclick = function(e) {{ e.preventDefault() // Close the WebSocket. socket.close() return false }} }} </script> </body> </html> """.format(web_socket_url=web_socket_url) return 200, [(b'content-type', b'text/html')], text_writer(html)