routing_test.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. from tornado.httputil import (
  13. HTTPHeaders,
  14. HTTPMessageDelegate,
  15. HTTPServerConnectionDelegate,
  16. ResponseStartLine,
  17. )
  18. from tornado.routing import (
  19. HostMatches,
  20. PathMatches,
  21. ReversibleRouter,
  22. Router,
  23. Rule,
  24. RuleRouter,
  25. )
  26. from tornado.testing import AsyncHTTPTestCase
  27. from tornado.web import Application, HTTPError, RequestHandler
  28. from tornado.wsgi import WSGIContainer
  29. import typing # noqa: F401
  30. class BasicRouter(Router):
  31. def find_handler(self, request, **kwargs):
  32. class MessageDelegate(HTTPMessageDelegate):
  33. def __init__(self, connection):
  34. self.connection = connection
  35. def finish(self):
  36. self.connection.write_headers(
  37. ResponseStartLine("HTTP/1.1", 200, "OK"),
  38. HTTPHeaders({"Content-Length": "2"}),
  39. b"OK",
  40. )
  41. self.connection.finish()
  42. return MessageDelegate(request.connection)
  43. class BasicRouterTestCase(AsyncHTTPTestCase):
  44. def get_app(self):
  45. return BasicRouter()
  46. def test_basic_router(self):
  47. response = self.fetch("/any_request")
  48. self.assertEqual(response.body, b"OK")
  49. resources = {} # type: typing.Dict[str, bytes]
  50. class GetResource(RequestHandler):
  51. def get(self, path):
  52. if path not in resources:
  53. raise HTTPError(404)
  54. self.finish(resources[path])
  55. class PostResource(RequestHandler):
  56. def post(self, path):
  57. resources[path] = self.request.body
  58. class HTTPMethodRouter(Router):
  59. def __init__(self, app):
  60. self.app = app
  61. def find_handler(self, request, **kwargs):
  62. handler = GetResource if request.method == "GET" else PostResource
  63. return self.app.get_handler_delegate(request, handler, path_args=[request.path])
  64. class HTTPMethodRouterTestCase(AsyncHTTPTestCase):
  65. def get_app(self):
  66. return HTTPMethodRouter(Application())
  67. def test_http_method_router(self):
  68. response = self.fetch("/post_resource", method="POST", body="data")
  69. self.assertEqual(response.code, 200)
  70. response = self.fetch("/get_resource")
  71. self.assertEqual(response.code, 404)
  72. response = self.fetch("/post_resource")
  73. self.assertEqual(response.code, 200)
  74. self.assertEqual(response.body, b"data")
  75. def _get_named_handler(handler_name):
  76. class Handler(RequestHandler):
  77. def get(self, *args, **kwargs):
  78. if self.application.settings.get("app_name") is not None:
  79. self.write(self.application.settings["app_name"] + ": ")
  80. self.finish(handler_name + ": " + self.reverse_url(handler_name))
  81. return Handler
  82. FirstHandler = _get_named_handler("first_handler")
  83. SecondHandler = _get_named_handler("second_handler")
  84. class CustomRouter(ReversibleRouter):
  85. def __init__(self):
  86. super(CustomRouter, self).__init__()
  87. self.routes = {} # type: typing.Dict[str, typing.Any]
  88. def add_routes(self, routes):
  89. self.routes.update(routes)
  90. def find_handler(self, request, **kwargs):
  91. if request.path in self.routes:
  92. app, handler = self.routes[request.path]
  93. return app.get_handler_delegate(request, handler)
  94. def reverse_url(self, name, *args):
  95. handler_path = "/" + name
  96. return handler_path if handler_path in self.routes else None
  97. class CustomRouterTestCase(AsyncHTTPTestCase):
  98. def get_app(self):
  99. router = CustomRouter()
  100. class CustomApplication(Application):
  101. def reverse_url(self, name, *args):
  102. return router.reverse_url(name, *args)
  103. app1 = CustomApplication(app_name="app1")
  104. app2 = CustomApplication(app_name="app2")
  105. router.add_routes(
  106. {
  107. "/first_handler": (app1, FirstHandler),
  108. "/second_handler": (app2, SecondHandler),
  109. "/first_handler_second_app": (app2, FirstHandler),
  110. }
  111. )
  112. return router
  113. def test_custom_router(self):
  114. response = self.fetch("/first_handler")
  115. self.assertEqual(response.body, b"app1: first_handler: /first_handler")
  116. response = self.fetch("/second_handler")
  117. self.assertEqual(response.body, b"app2: second_handler: /second_handler")
  118. response = self.fetch("/first_handler_second_app")
  119. self.assertEqual(response.body, b"app2: first_handler: /first_handler")
  120. class ConnectionDelegate(HTTPServerConnectionDelegate):
  121. def start_request(self, server_conn, request_conn):
  122. class MessageDelegate(HTTPMessageDelegate):
  123. def __init__(self, connection):
  124. self.connection = connection
  125. def finish(self):
  126. response_body = b"OK"
  127. self.connection.write_headers(
  128. ResponseStartLine("HTTP/1.1", 200, "OK"),
  129. HTTPHeaders({"Content-Length": str(len(response_body))}),
  130. )
  131. self.connection.write(response_body)
  132. self.connection.finish()
  133. return MessageDelegate(request_conn)
  134. class RuleRouterTest(AsyncHTTPTestCase):
  135. def get_app(self):
  136. app = Application()
  137. def request_callable(request):
  138. request.connection.write_headers(
  139. ResponseStartLine("HTTP/1.1", 200, "OK"),
  140. HTTPHeaders({"Content-Length": "2"}),
  141. )
  142. request.connection.write(b"OK")
  143. request.connection.finish()
  144. router = CustomRouter()
  145. router.add_routes(
  146. {"/nested_handler": (app, _get_named_handler("nested_handler"))}
  147. )
  148. app.add_handlers(
  149. ".*",
  150. [
  151. (
  152. HostMatches("www.example.com"),
  153. [
  154. (
  155. PathMatches("/first_handler"),
  156. "tornado.test.routing_test.SecondHandler",
  157. {},
  158. "second_handler",
  159. )
  160. ],
  161. ),
  162. Rule(PathMatches("/.*handler"), router),
  163. Rule(PathMatches("/first_handler"), FirstHandler, name="first_handler"),
  164. Rule(PathMatches("/request_callable"), request_callable),
  165. ("/connection_delegate", ConnectionDelegate()),
  166. ],
  167. )
  168. return app
  169. def test_rule_based_router(self):
  170. response = self.fetch("/first_handler")
  171. self.assertEqual(response.body, b"first_handler: /first_handler")
  172. response = self.fetch("/first_handler", headers={"Host": "www.example.com"})
  173. self.assertEqual(response.body, b"second_handler: /first_handler")
  174. response = self.fetch("/nested_handler")
  175. self.assertEqual(response.body, b"nested_handler: /nested_handler")
  176. response = self.fetch("/nested_not_found_handler")
  177. self.assertEqual(response.code, 404)
  178. response = self.fetch("/connection_delegate")
  179. self.assertEqual(response.body, b"OK")
  180. response = self.fetch("/request_callable")
  181. self.assertEqual(response.body, b"OK")
  182. response = self.fetch("/404")
  183. self.assertEqual(response.code, 404)
  184. class WSGIContainerTestCase(AsyncHTTPTestCase):
  185. def get_app(self):
  186. wsgi_app = WSGIContainer(self.wsgi_app)
  187. class Handler(RequestHandler):
  188. def get(self, *args, **kwargs):
  189. self.finish(self.reverse_url("tornado"))
  190. return RuleRouter(
  191. [
  192. (
  193. PathMatches("/tornado.*"),
  194. Application([(r"/tornado/test", Handler, {}, "tornado")]),
  195. ),
  196. (PathMatches("/wsgi"), wsgi_app),
  197. ]
  198. )
  199. def wsgi_app(self, environ, start_response):
  200. start_response("200 OK", [])
  201. return [b"WSGI"]
  202. def test_wsgi_container(self):
  203. response = self.fetch("/tornado/test")
  204. self.assertEqual(response.body, b"/tornado/test")
  205. response = self.fetch("/wsgi")
  206. self.assertEqual(response.body, b"WSGI")
  207. def test_delegate_not_found(self):
  208. response = self.fetch("/404")
  209. self.assertEqual(response.code, 404)