Tornado routes HTTP requests to appropriate handlers using `Router` class implementations.
-Any `Router` implementation can be used directly as a ``request_callback`` for
-`~.httpserver.HTTPServer` (this is possible because `Router` implements
-`~.httputil.HTTPServerConnectionDelegate`):
+`Router` interface extends `~.httputil.HTTPServerConnectionDelegate` to provide additional
+routing capabilities. This also means that any `Router` implementation can be used directly
+as a ``request_callback`` for `~.httpserver.HTTPServer` constructor.
+
+`Router` subclass must implement a ``find_handler`` method to provide a suitable
+`~.httputil.HTTPMessageDelegate` instance to handle the request:
.. code-block:: python
class CustomRouter(Router):
def find_handler(self, request, **kwargs):
- # some routing logic providing a suitable HTTPMessageDelegate implementation
- return HTTPMessageDelegateImplementation()
+ # some routing logic providing a suitable HTTPMessageDelegate instance
+ return MessageDelegate(request.connection)
+
+ class MessageDelegate(HTTPMessageDelegate):
+ def __init__(self, connection):
+ self.connection = connection
+
+ def finish(self):
+ self.connection.write_headers(
+ ResponseStartLine("HTTP/1.1", 200, "OK"),
+ HTTPHeaders({"Content-Length": "2"}),
+ b"OK")
+ self.connection.finish()
router = CustomRouter()
server = HTTPServer(router)
-`Router.find_handler` is the only method that you must implement to provide routing
-logic in its simplest case. This method must return an instance of
-`~.httputil.HTTPMessageDelegate` that will be used to process the request.
+The main responsibility of `Router` implementation is to provide a mapping from a request
+to `~.httputil.HTTPMessageDelegate` instance that will handle this request. In the example above
+we can see that routing is possible even without instantiating an `~.web.Application`.
+
+For routing to `~.web.RequestHandler` implementations we need an `~.web.Application` instance.
+`~.web.Application.get_handler_delegate` provides a convenient way to create
+`~.httputil.HTTPMessageDelegate` for a given request and `~.web.RequestHandler`.
+
+Here is a simple example of how we can we route to `~.web.RequestHandler` subclasses
+by HTTP method:
+
+.. code-block:: python
+
+ resources = {}
+
+ class GetResource(RequestHandler):
+ def get(self, path):
+ if path not in resources:
+ raise HTTPError(404)
+
+ self.finish(resources[path])
+
+ class PostResource(RequestHandler):
+ def post(self, path):
+ resources[path] = self.request.body
+
+ class HTTPMethodRouter(Router):
+ def __init__(self, app):
+ self.app = app
+
+ def find_handler(self, request, **kwargs):
+ handler = GetResource if request.method == "GET" else PostResource
+ return self.app.get_handler_delegate(request, handler, path_args=[request.path])
+
+ router = HTTPMethodRouter(Application())
+ server = HTTPServer(router)
`ReversibleRouter` interface adds the ability to distinguish between the routes and
reverse them to the original urls using route's name and additional arguments.
`~.web.Application` is itself an implementation of `ReversibleRouter` class.
-`RuleRouter` and `ReversibleRuleRouter` provide an interface for creating rule-based
-routing configurations. For example, `RuleRouter` can be used to route between applications:
+`RuleRouter` and `ReversibleRuleRouter` are implementations of `Router` and `ReversibleRouter`
+interfaces and can be used for creating rule-based routing configurations.
-.. code-block:: python
+Rules are instances of `Rule` class. They contain a `Matcher`, which provides the logic for
+determining whether the rule is a match for a particular request and a target, which can be
+one of the following.
- app1 = Application([
- (r"/app1/handler1", Handler1),
- # other handlers ...
- ])
+1) An instance of `~.httputil.HTTPServerConnectionDelegate`:
- app2 = Application([
- (r"/app2/handler1", Handler1),
- # other handlers ...
- ])
+.. code-block:: python
router = RuleRouter([
- Rule(PathMatches("/app1.*"), app1),
- Rule(PathMatches("/app2.*"), app2)
+ Rule(PathMatches("/handler"), ConnectionDelegate()),
+ # ... more rules
])
- server = HTTPServer(router)
+ class ConnectionDelegate(HTTPServerConnectionDelegate):
+ def start_request(self, server_conn, request_conn):
+ return MessageDelegate(request_conn)
-Implementations of `~.httputil.HTTPServerConnectionDelegate` and old-style callables can also be used as
-rule targets:
+2) A callable accepting a single argument of `~.httputil.HTTPServerRequest` type:
.. code-block:: python
router = RuleRouter([
- Rule(PathMatches("/callable"), request_callable), # def request_callable(request): ...
- Rule(PathMatches("/delegate"), HTTPServerConnectionDelegateImpl())
+ Rule(PathMatches("/callable"), request_callable)
])
- server = HTTPServer(router)
+ def request_callable(request):
+ request.write(b"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nOK")
+ request.finish()
-You can use nested routers as targets as well:
+3) Another `Router` instance:
.. code-block:: python
Rule(PathMatches("/router.*"), CustomRouter())
])
- server = HTTPServer(router)
-
-And of course a nested `RuleRouter` would be a valid thing:
+Of course a nested `RuleRouter` or a `~.web.Application` is allowed:
.. code-block:: python
server = HTTPServer(router)
-Rules are instances of `Rule` class. They contain some target (`~.web.Application` instance,
-`~.httputil.HTTPServerConnectionDelegate` implementation, a callable or a nested `Router`) and
-provide the basic routing logic defining whether this rule is a match for a particular request.
-This routing logic is implemented in `Matcher` subclasses (see `HostMatches`, `PathMatches`
-and others).
+In the example below `RuleRouter` is used to route between applications:
+
+.. code-block:: python
-`~URLSpec` is simply a subclass of a `Rule` with `PathMatches` matcher and is preserved for
-backwards compatibility.
+ app1 = Application([
+ (r"/app1/handler", Handler1),
+ # other handlers ...
+ ])
+
+ app2 = Application([
+ (r"/app2/handler", Handler2),
+ # other handlers ...
+ ])
+
+ router = RuleRouter([
+ Rule(PathMatches("/app1.*"), app1),
+ Rule(PathMatches("/app2.*"), app2)
+ ])
+
+ server = HTTPServer(router)
+
+For more information on application-level routing see docs for `~.web.Application`.
"""
from __future__ import absolute_import, division, print_function, with_statement
"""Rule-based router implementation."""
def __init__(self, rules=None):
- """Constructs a router with an ordered list of rules::
+ """Constructs a router from an ordered list of rules::
RuleRouter([
- Rule(PathMatches("/handler"), SomeHandler),
+ Rule(PathMatches("/handler"), Target),
# ... more rules
])
You can also omit explicit `Rule` constructor and use tuples of arguments::
RuleRouter([
- (PathMatches("/handler"), SomeHandler),
+ (PathMatches("/handler"), Target),
])
`PathMatches` is a default matcher, so the example above can be simplified::
RuleRouter([
- ("/handler", SomeHandler),
+ ("/handler", Target),
])
+ In the examples above, ``Target`` can be a nested `Router` instance, an instance of
+ `~.httputil.HTTPServerConnectionDelegate` or an old-style callable, accepting a request argument.
+
:arg rules: a list of `Rule` instances or tuples of `Rule`
constructor arguments.
"""
whether the rule should be considered a match for a specific
request.
:arg target: a Rule's target (typically a ``RequestHandler`` or
- `~.httputil.HTTPServerConnectionDelegate` subclass or even a nested `Router`).
+ `~.httputil.HTTPServerConnectionDelegate` subclass or even a nested `Router`,
+ depending on routing implementation).
:arg dict target_kwargs: a dict of parameters that can be useful
at the moment of target instantiation (for example, ``status_code``
for a ``RequestHandler`` subclass). They end up in
class URLSpec(Rule):
- """Specifies mappings between URLs and handlers."""
+ """Specifies mappings between URLs and handlers.
+
+ .. versionchanged: 4.5
+ `URLSpec` is now a subclass of a `Rule` with `PathMatches` matcher and is preserved for
+ backwards compatibility.
+ """
def __init__(self, pattern, handler, kwargs=None, name=None):
"""Parameters:
from __future__ import absolute_import, division, print_function, with_statement
from tornado.httputil import HTTPHeaders, HTTPMessageDelegate, HTTPServerConnectionDelegate, ResponseStartLine
-from tornado.routing import HostMatches, PathMatches, ReversibleRouter, Rule, RuleRouter
+from tornado.routing import HostMatches, PathMatches, ReversibleRouter, Router, Rule, RuleRouter
from tornado.testing import AsyncHTTPTestCase
-from tornado.web import Application, RequestHandler
+from tornado.web import Application, HTTPError, RequestHandler
from tornado.wsgi import WSGIContainer
-def get_named_handler(handler_name):
+class BasicRouter(Router):
+ def find_handler(self, request, **kwargs):
+
+ class MessageDelegate(HTTPMessageDelegate):
+ def __init__(self, connection):
+ self.connection = connection
+
+ def finish(self):
+ self.connection.write_headers(
+ ResponseStartLine("HTTP/1.1", 200, "OK"), HTTPHeaders({"Content-Length": "2"}), b"OK"
+ )
+ self.connection.finish()
+
+ return MessageDelegate(request.connection)
+
+
+class BasicRouterTestCase(AsyncHTTPTestCase):
+ def get_app(self):
+ return BasicRouter()
+
+ def test_basic_router(self):
+ response = self.fetch("/any_request")
+ self.assertEqual(response.body, b"OK")
+
+
+resources = {}
+
+
+class GetResource(RequestHandler):
+ def get(self, path):
+ if path not in resources:
+ raise HTTPError(404)
+
+ self.finish(resources[path])
+
+
+class PostResource(RequestHandler):
+ def post(self, path):
+ resources[path] = self.request.body
+
+
+class HTTPMethodRouter(Router):
+ def __init__(self, app):
+ self.app = app
+
+ def find_handler(self, request, **kwargs):
+ handler = GetResource if request.method == "GET" else PostResource
+ return self.app.get_handler_delegate(request, handler, path_args=[request.path])
+
+
+class HTTPMethodRouterTestCase(AsyncHTTPTestCase):
+ def get_app(self):
+ return HTTPMethodRouter(Application())
+
+ def test_http_method_router(self):
+ response = self.fetch("/post_resource", method="POST", body="data")
+ self.assertEqual(response.code, 200)
+
+ response = self.fetch("/get_resource")
+ self.assertEqual(response.code, 404)
+
+ response = self.fetch("/post_resource")
+ self.assertEqual(response.code, 200)
+ self.assertEqual(response.body, b"data")
+
+
+def _get_named_handler(handler_name):
class Handler(RequestHandler):
def get(self, *args, **kwargs):
if self.application.settings.get("app_name") is not None:
return Handler
-FirstHandler = get_named_handler("first_handler")
-SecondHandler = get_named_handler("second_handler")
+FirstHandler = _get_named_handler("first_handler")
+SecondHandler = _get_named_handler("second_handler")
class CustomRouter(ReversibleRouter):
class _ApplicationRouter(ReversibleRuleRouter):
- """Routing implementation used by `Application`.
+ """Routing implementation used internally by `Application`.
- Provides a binding between `Application` and `RequestHandler` implementations.
+ Provides a binding between `Application` and `RequestHandler`.
This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:
* it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and
- * it allows to use a list/tuple of rules as `~.routing.Rule` target. This list is
- substituted with an `ApplicationRouter`, instantiated with current application and
- the list of routes.
+ * it allows to use a list/tuple of rules as `~.routing.Rule` target.
+ ``process_rule`` implementation will substitute this list with an appropriate
+ `_ApplicationRouter` instance.
"""
def __init__(self, application, rules=None):
def get_handler_delegate(self, request, target_class, target_kwargs=None,
path_args=None, path_kwargs=None):
+ """Returns `~.httputil.HTTPMessageDelegate` that can serve a request
+ for application and `RequestHandler` subclass.
+
+ :arg httputil.HTTPServerRequest request: current HTTP request.
+ :arg RequestHandler target_class: a `RequestHandler` class.
+ :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.
+ :arg list path_args: positional arguments for ``target_class`` HTTP method that
+ will be executed while handling a request (``get``, ``post`` or any other).
+ :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.
+ """
return _HandlerDelegate(
self, request, target_class, target_kwargs, path_args, path_kwargs)