def test_non_ssl_request(self): # Make sure the server closes the connection when it gets a non-ssl # connection, rather than waiting for a timeout or otherwise # misbehaving. with ExpectLog(gen_log, '(SSL Error|uncaught exception)'): with ExpectLog(gen_log, 'Uncaught exception', required=False): self.http_client.fetch(self.get_url("/").replace( 'https:', 'http:'), self.stop, request_timeout=3600, connect_timeout=3600) response = self.wait() self.assertEqual(response.code, 599)
def test_chunked_with_content_length(self): # Make sure the invalid headers are detected with ExpectLog(gen_log, ("Malformed HTTP message from None: Response " "with both Transfer-Encoding and Content-Length")): response = self.fetch('/chunkwithcl') self.assertEqual(response.code, 599)
def test_connection_refused(self): # When a connection is refused, the connect callback should not # be run. (The kqueue IOLoop used to behave differently from the # epoll IOLoop in this respect) cleanup_func, port = refusing_port() self.addCleanup(cleanup_func) stream = IOStream(socket.socket(), self.io_loop) self.connect_called = False def connect_callback(): self.connect_called = True self.stop() stream.set_close_callback(self.stop) # log messages vary by platform and ioloop implementation with ExpectLog(gen_log, ".*", required=False): stream.connect(("127.0.0.1", port), connect_callback) self.wait() self.assertFalse(self.connect_called) self.assertTrue(isinstance(stream.error, socket.error), stream.error) if sys.platform != 'cygwin': _ERRNO_CONNREFUSED = (errno.ECONNREFUSED,) if hasattr(errno, "WSAECONNREFUSED"): _ERRNO_CONNREFUSED += (errno.WSAECONNREFUSED,) # cygwin's errnos don't match those used on native windows python self.assertTrue(stream.error.args[0] in _ERRNO_CONNREFUSED)
def test_read_until_regex_max_bytes(self): server, client = self.make_iostream_pair() client.set_close_callback(lambda: self.stop("closed")) try: # Extra room under the limit client.read_until_regex(b"def", self.stop, max_bytes=50) server.write(b"abcdef") data = self.wait() self.assertEqual(data, b"abcdef") # Just enough space client.read_until_regex(b"def", self.stop, max_bytes=6) server.write(b"abcdef") data = self.wait() self.assertEqual(data, b"abcdef") # Not enough space, but we don't know it until all we can do is # log a warning and close the connection. with ExpectLog(gen_log, "Unsatisfiable read"): client.read_until_regex(b"def", self.stop, max_bytes=5) server.write(b"123456") data = self.wait() self.assertEqual(data, "closed") finally: server.close() client.close()
def test_large_headers(self): with ExpectLog(gen_log, "Unsatisfiable read", required=False): response = self.fetch("/", headers={'X-Filler': 'a' * 1000}) # 431 is "Request Header Fields Too Large", defined in RFC # 6585. However, many implementations just close the # connection in this case, resulting in a 599. self.assertIn(response.code, (431, 599))
def test_large_body_streaming_chunked(self): with ExpectLog(gen_log, '.*chunked body too large'): response = self.fetch( '/streaming', method='PUT', body_producer=lambda write: write(b'a' * 10240)) self.assertEqual(response.code, 599)
def test_error_in_on_message(self): ws = yield self.ws_connect('/error_in_on_message') ws.write_message('hello') with ExpectLog(app_log, "Uncaught exception"): response = yield ws.read_message() self.assertIs(response, None) yield self.close(ws)
def test_gzip_unsupported(self): # Gzip support is opt-in; without it the server fails to parse # the body (but parsing form bodies is currently just a log message, # not a fatal error). with ExpectLog(gen_log, "Unsupported Content-Encoding"): response = self.post_gzip('foo=bar') self.assertEquals(json_decode(response.body), {})
def test_unix_socket_bad_request(self): # Unix sockets don't have remote addresses so they just return an # empty string. with ExpectLog(gen_log, "Malformed HTTP message from"): self.stream.write(b"garbage\r\n\r\n") self.stream.read_until_close(self.stop) response = self.wait() self.assertEqual(response, b"")
def test_websocket_network_fail(self): sock, port = bind_unused_port() sock.close() with self.assertRaises(IOError): with ExpectLog(gen_log, ".*"): yield websocket_connect('ws://127.0.0.1:%d/' % port, io_loop=self.io_loop, connect_timeout=3600)
def test_malformed_first_line(self): with ExpectLog(gen_log, '.*Malformed HTTP request line'): self.stream.write(b'asdf\r\n\r\n') # TODO: need an async version of ExpectLog so we don't need # hard-coded timeouts here. self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop) self.wait()
def test_error_logging(self): # No stack traces are logged for SSL errors. with ExpectLog(gen_log, 'SSL Error') as expect_log: self.http_client.fetch( self.get_url("/").replace("https:", "http:"), self.stop) response = self.wait() self.assertEqual(response.code, 599) self.assertFalse(expect_log.logged_stack)
def test_error_logging(self): # No stack traces are logged for SSL errors (in this case, # failure to validate the testing self-signed cert). # The SSLError is exposed through ssl.SSLError. with ExpectLog(gen_log, '.*') as expect_log: response = self.fetch("/", validate_cert=True) self.assertEqual(response.code, 599) self.assertIsInstance(response.error, ssl.SSLError) self.assertFalse(expect_log.logged_stack)
def test_exception_logging(self): """Uncaught exceptions get logged by the IOLoop.""" # Use a NullContext to keep the exception from being caught by # AsyncTestCase. with NullContext(): self.io_loop.add_callback(lambda: 1 / 0) self.io_loop.add_callback(self.stop) with ExpectLog(app_log, "Exception in callback"): self.wait()
def test_handshake_fail(self): server_future = self.server_start_tls(_server_ssl_options()) # Certificates are verified with the default configuration. client_future = self.client_start_tls(server_hostname="localhost") with ExpectLog(gen_log, "SSL Error"): with self.assertRaises(ssl.SSLError): yield client_future with self.assertRaises((ssl.SSLError, socket.error)): yield server_future
def test_204_invalid_content_length(self): # 204 status with non-zero content length is malformed with ExpectLog(gen_log, ".*Response with code 204 should not have body"): response = self.fetch("/?error=1") if not self.http1: self.skipTest("requires HTTP/1.x") if self.http_client.configured_class != SimpleAsyncHTTPClient: self.skipTest("curl client accepts invalid headers") self.assertEqual(response.code, 599)
def test_line_does_not_end_with_correct_line_break(self): data = b'''\ --1234 Content-Disposition: form-data; name="files"; filename="ab.txt" Foo--1234--'''.replace(b"\n", b"\r\n") args = {} files = {} with ExpectLog(gen_log, "Invalid multipart/form-data"): parse_multipart_form_data(b"1234", data, args, files) self.assertEqual(files, {})
def test_missing_headers(self): data = b'''\ --1234 Foo --1234--'''.replace(b"\n", b"\r\n") args = {} files = {} with ExpectLog(gen_log, "multipart/form-data missing headers"): parse_multipart_form_data(b"1234", data, args, files) self.assertEqual(files, {})
def test_unsupported_auth_mode(self): # curl and simple clients handle errors a bit differently; the # important thing is that they don't fall back to basic auth # on an unknown mode. with ExpectLog(gen_log, "uncaught exception", required=False): with self.assertRaises((ValueError, HTTPError)): response = self.fetch("/auth", auth_username="******", auth_password="******", auth_mode="asdf") response.rethrow()
def test_multiple_errors(self): def fail(message): raise Exception(message) self.io_loop.add_callback(lambda: fail("error one")) self.io_loop.add_callback(lambda: fail("error two")) # The first error gets raised; the second gets logged. with ExpectLog(app_log, "multiple unhandled exceptions"): with self.assertRaises(Exception) as cm: self.wait() self.assertEqual(str(cm.exception), "error one")
def test_invalid_content_length(self): with ExpectLog(gen_log, '.*Only integer Content-Length is allowed'): self.stream.write(b"""\ POST /echo HTTP/1.1 Content-Length: foo bar """.replace(b"\n", b"\r\n")) self.stream.read_until_close(self.stop) self.wait()
def test_invalid_content_disposition(self): data = b'''\ --1234 Content-Disposition: invalid; name="files"; filename="ab.txt" Foo --1234--'''.replace(b"\n", b"\r\n") args = {} files = {} with ExpectLog(gen_log, "Invalid multipart/form-data"): parse_multipart_form_data(b"1234", data, args, files) self.assertEqual(files, {})
def test_exception_logging_native_coro(self): """The IOLoop examines exceptions from awaitables and logs them.""" namespace = exec_test( globals(), locals(), """ async def callback(): self.io_loop.add_callback(self.stop) 1 / 0 """) with NullContext(): self.io_loop.add_callback(namespace["callback"]) with ExpectLog(app_log, "Exception in callback"): self.wait()
def test_spawn_callback(self): # An added callback runs in the test's stack_context, so will be # re-arised in wait(). self.io_loop.add_callback(lambda: 1 / 0) with self.assertRaises(ZeroDivisionError): self.wait() # A spawned callback is run directly on the IOLoop, so it will be # logged without stopping the test. self.io_loop.spawn_callback(lambda: 1 / 0) self.io_loop.add_callback(self.stop) with ExpectLog(app_log, "Exception in callback"): self.wait()
def test_content_disposition_header_without_name_parameter(self): data = b"""\ --1234 Content-Disposition: form-data; filename="ab.txt" Foo --1234--""".replace(b"\n", b"\r\n") args = {} files = {} with ExpectLog(gen_log, "multipart/form-data value missing name"): parse_multipart_form_data(b"1234", data, args, files) self.assertEqual(files, {})
def test_exception_logging_future(self): """The IOLoop examines exceptions from Futures and logs them.""" with NullContext(): @gen.coroutine def callback(): self.io_loop.add_callback(self.stop) 1 / 0 self.io_loop.add_callback(callback) with ExpectLog(app_log, "Exception in callback"): self.wait()
def test_timeout(self): stream = IOStream(socket.socket()) try: yield stream.connect(('127.0.0.1', self.get_http_port())) # Use a raw stream because AsyncHTTPClient won't let us read a # response without finishing a body. stream.write(b'PUT /streaming?body_timeout=0.1 HTTP/1.0\r\n' b'Content-Length: 42\r\n\r\n') with ExpectLog(gen_log, 'Timeout reading body'): response = yield stream.read_until_close() self.assertEqual(response, b'') finally: stream.close()
def test_check_hostname(self): # Test that server_hostname parameter to start_tls is being used. # The check_hostname functionality is only available in python 2.7 and # up and in python 3.4 and up. server_future = self.server_start_tls(_server_ssl_options()) client_future = self.client_start_tls( ssl.create_default_context(), server_hostname='127.0.0.1') with ExpectLog(gen_log, "SSL Error"): with self.assertRaises(ssl.SSLError): # The client fails to connect with an SSL error. yield client_future with self.assertRaises(Exception): # The server fails to connect, but the exact error is unspecified. yield server_future
def test_malformed_body(self): # parse_qs is pretty forgiving, but it will fail on python 3 # if the data is not utf8. On python 2 parse_qs will work, # but then the recursive_unicode call in EchoHandler will # fail. if str is bytes: return with ExpectLog(gen_log, 'Invalid x-www-form-urlencoded body'): response = self.fetch( '/echo', method="POST", headers={'Content-Type': 'application/x-www-form-urlencoded'}, body=b'\xe9') self.assertEqual(200, response.code) self.assertEqual(b'{}', response.body)
def test_uncaught_exception_log(self): @gen.coroutine def f(): yield gen.moment 1 / 0 g = f() with ExpectLog( app_log, "(?s)Future.* exception was never retrieved:" ".*ZeroDivisionError"): yield gen.moment yield gen.moment del g gc.collect() # for PyPy