Skip to content

Commit c48a976

Browse files
committed
Router refactoring to push Not Allowed and Not Found in middleware processing
1 parent 6e1e1d8 commit c48a976

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ CHANGES
2828
(`MultiDictProxy` and `CIMultiDictProxy`). Previous edition of
2929
multidicts was not a part of public API BTW.
3030

31+
- Router refactoring to push Not Allowed and Not Found in middleware processing
32+
3133

3234
0.13.1 (12-31-2014)
3335
--------------------

aiohttp/web.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,44 @@ def __repr__(self):
12421242
directory=self._directory)
12431243

12441244

1245+
class _NotFoundMatchInfo(UrlMappingMatchInfo):
1246+
1247+
def __init__(self):
1248+
super().__init__({}, None)
1249+
1250+
@property
1251+
def handler(self):
1252+
return self._not_found
1253+
1254+
@property
1255+
def route(self):
1256+
return None
1257+
1258+
@asyncio.coroutine
1259+
def _not_found(self, request):
1260+
raise HTTPNotFound()
1261+
1262+
1263+
class _MethodNotAllowedMatchInfo(UrlMappingMatchInfo):
1264+
1265+
def __init__(self, method, allowed_methods):
1266+
super().__init__({}, None)
1267+
self._method = method
1268+
self._allowed_methods = allowed_methods
1269+
1270+
@property
1271+
def handler(self):
1272+
return self._not_allowed
1273+
1274+
@property
1275+
def route(self):
1276+
return None
1277+
1278+
@asyncio.coroutine
1279+
def _not_allowed(self, request):
1280+
raise HTTPMethodNotAllowed(self._method, self._allowed_methods)
1281+
1282+
12451283
class UrlDispatcher(AbstractRouter, collections.abc.Mapping):
12461284

12471285
DYN = re.compile(r'^\{(?P<var>[a-zA-Z][_a-zA-Z0-9]*)\}$')
@@ -1273,9 +1311,9 @@ def resolve(self, request):
12731311
return UrlMappingMatchInfo(match_dict, route)
12741312
else:
12751313
if allowed_methods:
1276-
raise HTTPMethodNotAllowed(method, allowed_methods)
1314+
return _MethodNotAllowedMatchInfo(method, allowed_methods)
12771315
else:
1278-
raise HTTPNotFound()
1316+
return _NotFoundMatchInfo()
12791317

12801318
def __iter__(self):
12811319
return iter(self._routes)

docs/web.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ other, like displaying *403 Forbidden page* or raising
367367
resource. Also middleware may render errors raised by handler, do
368368
some pre- and post- processing like handling *CORS* and so on.
369369
370-
.. warning::
370+
.. versionchanged:: 0.14
371371
372-
Middleware is executing **after** routing, thus it cannot process
373-
route exceptions.
372+
Middleware accepts route exceptions (:exc:`HTTPNotFound` and
373+
:exc:`HTTPMethodNotAllowed`).

docs/web_reference.rst

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -924,12 +924,26 @@ Router is any object that implements :class:`AbstractRouter` interface.
924924
.. method:: resolve(requst)
925925

926926
A :ref:`coroutine<coroutine>` that returns
927-
:class:`AbstractMatchInfo` for *request* or raises http
928-
exception like :exc:`HTTPNotFound` if there is no registered
929-
route for *request*.
927+
:class:`AbstractMatchInfo` for *request*.
928+
929+
The method never raises exception, but returns
930+
:class:`AbstractMatchInfo` instance with ``None``
931+
:attr:`~AbstractMatchInfo.route` and
932+
:attr:`~AbstractMatchInfo.handler` which raises
933+
:exc:`HTTPNotFound` or :exc:`HTTPMethodNotAllowed` on handler's
934+
execution if there is no registered route for *request*.
935+
936+
*Middlewares* can process that exceptions to render
937+
pretty-looking error page for example.
930938

931939
Used by internal machinery, end user unlikely need to call the method.
932940

941+
.. versionchanged:: 0.14
942+
943+
The method don't raise :exc:`HTTPNotFound` and
944+
:exc:`HTTPMethodNotAllowed` anymore.
945+
946+
933947
.. _aiohttp-web-route:
934948

935949
Route

tests/test_urldispatch.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,12 @@ def test_raise_method_not_allowed(self):
130130
self.router.add_route('POST', '/', handler2)
131131
req = self.make_request('PUT', '/')
132132

133+
match_info = self.loop.run_until_complete(self.router.resolve(req))
134+
self.assertIsNone(match_info.route)
135+
self.assertEqual({}, match_info)
136+
133137
with self.assertRaises(HTTPMethodNotAllowed) as ctx:
134-
self.loop.run_until_complete(self.router.resolve(req))
138+
self.loop.run_until_complete(match_info.handler(req))
135139

136140
exc = ctx.exception
137141
self.assertEqual('PUT', exc.method)
@@ -143,8 +147,12 @@ def test_raise_method_not_found(self):
143147
self.router.add_route('GET', '/a', handler)
144148
req = self.make_request('GET', '/b')
145149

150+
match_info = self.loop.run_until_complete(self.router.resolve(req))
151+
self.assertIsNone(match_info.route)
152+
self.assertEqual({}, match_info)
153+
146154
with self.assertRaises(HTTPNotFound) as ctx:
147-
self.loop.run_until_complete(self.router.resolve(req))
155+
self.loop.run_until_complete(match_info.handler(req))
148156

149157
exc = ctx.exception
150158
self.assertEqual(404, exc.status)
@@ -281,8 +289,11 @@ def test_add_route_with_re_not_match(self):
281289
self.router.add_route('GET', r'/handler/{to:\d+}', handler)
282290

283291
req = self.make_request('GET', '/handler/tail')
292+
match_info = self.loop.run_until_complete(self.router.resolve(req))
293+
self.assertIsNone(match_info.route)
294+
self.assertEqual({}, match_info)
284295
with self.assertRaises(HTTPNotFound):
285-
self.loop.run_until_complete(self.router.resolve(req))
296+
self.loop.run_until_complete(match_info.handler(req))
286297

287298
def test_add_route_with_re_including_slashes(self):
288299
handler = asyncio.coroutine(lambda req: Response(req))

0 commit comments

Comments
 (0)