|
30 | 30 | from tornado.test.util import abstract_base_test |
31 | 31 | from tornado.web import Application, RequestHandler, stream_request_body |
32 | 32 |
|
33 | | -from contextlib import closing |
| 33 | +from contextlib import closing, contextmanager |
34 | 34 | import datetime |
35 | 35 | import gzip |
36 | 36 | import logging |
@@ -634,6 +634,38 @@ def test_invalid_content_length(self): |
634 | 634 | ) |
635 | 635 | yield stream.read_until_close() |
636 | 636 |
|
| 637 | + @gen_test |
| 638 | + def test_invalid_methods(self): |
| 639 | + # RFC 9110 distinguishes between syntactically invalid methods and those that are |
| 640 | + # valid but unknown. The former must give a 400 status code, while the latter should |
| 641 | + # give a 405. |
| 642 | + test_cases = [ |
| 643 | + ("FOO", 405, None), |
| 644 | + ("FOO,BAR", 400, ".*Malformed HTTP request line"), |
| 645 | + ] |
| 646 | + for method, code, log_msg in test_cases: |
| 647 | + if log_msg is not None: |
| 648 | + expect_log = ExpectLog(gen_log, log_msg, level=logging.INFO) |
| 649 | + else: |
| 650 | + |
| 651 | + @contextmanager |
| 652 | + def noop_context(): |
| 653 | + yield |
| 654 | + |
| 655 | + expect_log = noop_context() # type: ignore |
| 656 | + with ( |
| 657 | + self.subTest(method=method), |
| 658 | + closing(IOStream(socket.socket())) as stream, |
| 659 | + expect_log, |
| 660 | + ): |
| 661 | + yield stream.connect(("127.0.0.1", self.get_http_port())) |
| 662 | + stream.write(utf8(f"{method} /echo HTTP/1.1\r\n\r\n")) |
| 663 | + resp = yield stream.read_until(b"\r\n\r\n") |
| 664 | + self.assertTrue( |
| 665 | + resp.startswith(b"HTTP/1.1 %d" % code), |
| 666 | + f"expected status code {code} in {resp!r}", |
| 667 | + ) |
| 668 | + |
637 | 669 |
|
638 | 670 | class XHeaderTest(HandlerBaseTestCase): |
639 | 671 | class Handler(RequestHandler): |
|
0 commit comments