def handle_parser_exception(self, exc: Exception): """ Handles an exception when parsing. This will not call into the app (hence why it is a normal function, and not a coroutine). It will also close the connection when it's done. :param exc: The exception to handle. """ if isinstance(exc, httptools.HttpParserInvalidMethodError): # 405 method not allowed r = MethodNotAllowed() elif isinstance(exc, httptools.HttpParserError): # 400 bad request r = BadRequest() elif isinstance(exc, httptools.HttpParserUpgrade): r = BadRequest(description="Invalid upgrade header.") else: # internal server error r = InternalServerError() # Make a fake environment. new_environ = to_wsgi_environment(headers=self.headers, method="", path="/", http_version="1.0", body=None) new_environ["SERVER_NAME"] = self.component.get_server_name() new_environ["SERVER_PORT"] = str(self.server_port) new_environ["REMOTE_ADDR"] = self.ip self.raw_write(get_formatted_response(r, new_environ)) self.parser = httptools.HttpRequestParser(self) self.close()
def handle_parser_exception(self, exc: Exception): """ Handles an exception when parsing. This will not call into the app (hence why it is a normal function, and not a coroutine). It will also close the connection when it's done. :param exc: The exception to handle. """ if isinstance(exc, httptools.HttpParserInvalidMethodError): # 405 method not allowed r = MethodNotAllowed() elif isinstance(exc, (httptools.HttpParserError, httptools.HttpParserUpgrade)): # 400 bad request r = BadRequest() else: # internal server error r = InternalServerError() # Make a fake environment. new_environ = to_wsgi_environment(headers=self.headers, method="", path="/", http_version="1.0", body=None) new_environ["SERVER_NAME"] = self.component.get_server_name() new_environ["SERVER_PORT"] = str(self.server_port) new_environ["REMOTE_ADDR"] = self.ip self.raw_write(get_formatted_response(r, new_environ)) self.parser = httptools.HttpRequestParser(self) self.close()
async def inject_request(self, headers: dict, url: str, method: str = "GET", body: str = None) -> Response: """ Injects a request into the test client. This will automatically create the correct context. :param headers: The headers to use. :param body: The body to use. :param url: The URL to use. :param method: The method to use. :return: The result. """ if body is not None: body = BytesIO(body.encode()) e = to_wsgi_environment(headers, method, url, http_version="1.1", body=body) e["SERVER_NAME"] = "" e["SERVER_PORT"] = "" r = Request(e) # for testing blueprints, etc # slow but it's a test so oh well self.finalize() return await self.process_request(r, self.base_context)
async def _wait(self): """ The main core of the protocol. This constructs a new Werkzeug request from the headers. """ # Event is set, construct the new fake WSGI environment # Check if the body has data in it by asking it to tell us what position it's seeked to. # If it's > 0, it has data, so we can use it. Otherwise, it doesn't, so it's useless. told = self.body.tell() if told: self.logger.debug("Read {} bytes of data from the connection".format(told)) self.body.seek(0) body = self.body else: body = None version = self.parser.get_http_version() method = self.parser.get_method().decode() new_environ = to_wsgi_environment(headers=self.headers, method=method, path=self.full_url, http_version=version, body=body) new_environ["kyoukai.protocol"] = self new_environ["SERVER_NAME"] = self.component.get_server_name() new_environ["SERVER_PORT"] = str(self.server_port) new_environ["REMOTE_ADDR"] = self.ip new_environ["REMOTE_PORT"] = self.client_port # Construct a Request object. new_r = self.app.request_class(new_environ, False) # Invoke the app. async with self.lock: try: result = await self.app.process_request(new_r, self.parent_context) except Exception: # not good! # write the scary exception text self.logger.exception("Error in Kyoukai request handling!") self._raw_write(CRITICAL_ERROR_TEXT.encode("utf-8")) return else: # Write the response. self.write_response(result, new_environ) finally: if not self.parser.should_keep_alive(): self.close() # unlock the event and remove the waiter self.parser = httptools.HttpRequestParser(self)
async def test_get_formatted_response(): """ Tests getting a formatted werkzeug Response. """ with app.testing_bp() as bp: @bp.route("/") def root(ctx: HTTPRequestContext): return Response("Hello, world!") r = await app.inject_request({}, "/") environ = to_wsgi_environment({}, "GET", "/", "1.1") response = get_formatted_response(r, environ) # Pre-built response assert response == b'HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: ' \ b'13\r\nServer: Kyoukai/%s\r\nX-Powered-By: Kyoukai/%s\r\n\r\nHello, world!' % \ (__version__.encode(), __version__.encode())
def test_route_adding(): with app.testing_bp() as bp: # fake route def _inner(ctx: HTTPRequestContext): pass rtt = bp.wrap_route(_inner) assert rtt.get_endpoint_name(bp) == bp.name + "._inner" rtt = bp.add_route(rtt, "/") bp.finalize() assert bp.finalized assert len(bp.routes) == 1 # test matching of the route environ = to_wsgi_environment({}, "GET", "/", "1.1") environ["SERVER_NAME"] = "" environ["SERVER_PORT"] = "4444" assert bp.match(environ)[0] == rtt assert len(app.root.routes) == 0
async def _wait(self): """ The main core of the protocol. This constructs a new Werkzeug request from the headers. """ # Check if the body has data in it by asking it to tell us what position it's seeked to. # If it's > 0, it has data, so we can use it. Otherwise, it doesn't, so it's useless. told = self.body.tell() if told: self.logger.debug("Read {} bytes of body data from the connection".format(told)) self.body.seek(0) body = self.body else: body = None version = self.parser.get_http_version() method = self.parser.get_method().decode() for header, value in self.headers: # check if a content-encoding has been passed if header == "Content-Encoding" and body is not None: # no special encoding if value == "identity": pass # gzip, decompress as such elif value == "gzip": self.logger.debug("Decoding body data as gzip.") try: decompressed_data = gzip.decompress(body.read()) except zlib.error: self.write(HTTP_INVALID_COMPRESSION) self.close() return body = BytesIO(decompressed_data) # deflate, decompress as such elif value == "deflate": z = zlib.decompressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) try: decompressed_data = z.decompress(body.read()) except zlib.error: self.write(HTTP_INVALID_COMPRESSION) self.close() return body = BytesIO(decompressed_data) else: self.logger.error("Unknown Content-Encoding sent by client: {}".format(value)) new_environ = to_wsgi_environment(headers=self.headers, method=method, path=self.full_url, http_version=version, body=body) new_environ["kyoukai.protocol"] = self new_environ["SERVER_NAME"] = self.component.get_server_name() new_environ["SERVER_PORT"] = str(self.server_port) new_environ["REMOTE_ADDR"] = self.ip new_environ["REMOTE_PORT"] = self.client_port # Construct a Request object. new_r = self.app.request_class(new_environ, False) # Invoke the app. async with self.lock: try: result = await self.app.process_request(new_r, self.parent_context) except Exception: # not good! # write the scary exception text self.logger.exception("Error in Kyoukai request handling!") self._raw_write(CRITICAL_ERROR_TEXT.encode("utf-8")) return else: # Write the response. self.write_response(result, new_environ) finally: if not self.parser.should_keep_alive(): self.close() # unlock the event and remove the waiter self.parser = httptools.HttpRequestParser(self)