Repository: aio-libs/aiohttp

Issue:
It's impossible to throw web_exceptions.* from router's resolve()
## Long story short

Sometimes it's very useful to have a custom router instead of built-in one. For instance, as it was mentioned in #1373, in order to implement API versioning we may want to check that API version is passed by client. If not, we need to reject the request with some error response (e.g. `412 Precondition Failed`).

Unfortunately, since 1.1 it's no longer possible as any attempt to do so fails with `500 Internal Server Error` on this line (`match_info` is `None` in this case):

https://github.com/KeepSafe/aiohttp/blob/v1.1.5/aiohttp/web_reqrep.py#L736

## Expected behaviour

`web_exceptions.*` can be thrown from router's `resolve()` method and are transformed to appropriate HTTP response code.

## Actual behaviour

Any attempt to throw `web_exceptions.*` lead to the `500 Internal Server Error`.

## Steps to reproduce

Here's an exploit (run with `pytest`):

```python
import asyncio

import aiohttp.abc
import aiohttp.web


class MyRouter(aiohttp.abc.AbstractRouter):
    async def resolve(self, request):
        raise aiohttp.web.HTTPPreconditionFailed()


def test_412_is_returned():
    loop = asyncio.new_event_loop()
    app = aiohttp.web.Application(router=MyRouter(), loop=loop)
    handler = app.make_handler()

    async def test():
        async def get(url):
            server = await loop.create_server(handler, '127.0.0.1', 0)
            address, port = server.sockets[0].getsockname()

            with aiohttp.ClientSession(loop=loop) as session:
                response = await session.get(
                    'http://%s:%s%s' % (address, port, url)
                )

            await handler.finish_connections()
            server.close()

            return response

        assert (await get('/')).status == 412

    loop.run_until_complete(test())

    loop.stop()
    loop.run_forever()
    loop.close()
```

## Your environment

* aiohttp 1.1.5
* python3.5
* macos sierra
It's impossible to throw web_exceptions.* from router's resolve()
## Long story short

Sometimes it's very useful to have a custom router instead of built-in one. For instance, as it was mentioned in #1373, in order to implement API versioning we may want to check that API version is passed by client. If not, we need to reject the request with some error response (e.g. `412 Precondition Failed`).

Unfortunately, since 1.1 it's no longer possible as any attempt to do so fails with `500 Internal Server Error` on this line (`match_info` is `None` in this case):

https://github.com/KeepSafe/aiohttp/blob/v1.1.5/aiohttp/web_reqrep.py#L736

## Expected behaviour

`web_exceptions.*` can be thrown from router's `resolve()` method and are transformed to appropriate HTTP response code.

## Actual behaviour

Any attempt to throw `web_exceptions.*` lead to the `500 Internal Server Error`.

## Steps to reproduce

Here's an exploit (run with `pytest`):

```python
import asyncio

import aiohttp.abc
import aiohttp.web


class MyRouter(aiohttp.abc.AbstractRouter):
    async def resolve(self, request):
        raise aiohttp.web.HTTPPreconditionFailed()


def test_412_is_returned():
    loop = asyncio.new_event_loop()
    app = aiohttp.web.Application(router=MyRouter(), loop=loop)
    handler = app.make_handler()

    async def test():
        async def get(url):
            server = await loop.create_server(handler, '127.0.0.1', 0)
            address, port = server.sockets[0].getsockname()

            with aiohttp.ClientSession(loop=loop) as session:
                response = await session.get(
                    'http://%s:%s%s' % (address, port, url)
                )

            await handler.finish_connections()
            server.close()

            return response

        assert (await get('/')).status == 412

    loop.run_until_complete(test())

    loop.stop()
    loop.run_forever()
    loop.close()
```

## Your environment

* aiohttp 1.1.5
* python3.5
* macos sierra


Discussion:
I'll take a look
I'll take a look

Resolving patch:
diff --git a/aiohttp/abc.py b/aiohttp/abc.py
--- a/aiohttp/abc.py
+++ b/aiohttp/abc.py
@@ -11,9 +11,14 @@ class AbstractRouter(ABC):
     def __init__(self):
         self._frozen = False
 
-    @abstractmethod
     def post_init(self, app):
-        pass  # pragma: no cover
+        """Post init stage.
+
+        It's not an abstract method for sake of backward compatibility
+        but if router wans to be aware about application it should
+        override it.
+
+        """
 
     @property
     def frozen(self):
diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py
--- a/aiohttp/web_reqrep.py
+++ b/aiohttp/web_reqrep.py
@@ -486,7 +486,10 @@ def app(self):
 
     @asyncio.coroutine
     def _prepare_hook(self, response):
-        for app in self.match_info.apps:
+        match_info = self._match_info
+        if match_info is None:
+            return
+        for app in match_info.apps:
             yield from app.on_response_prepare.send(self, response)
 
 


Test patch:
diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py
--- a/tests/test_web_urldispatcher.py
+++ b/tests/test_web_urldispatcher.py
@@ -8,7 +8,7 @@
 
 import pytest
 
-from aiohttp import web
+from aiohttp import abc, web
 from aiohttp.web_urldispatcher import SystemRoute
 
 
@@ -270,3 +270,21 @@ def test_system_route():
     assert "<SystemRoute 201: test>" == repr(route)
     assert 201 == route.status
     assert 'test' == route.reason
+
+
+@asyncio.coroutine
+def test_412_is_returned(loop, test_client):
+
+    class MyRouter(abc.AbstractRouter):
+
+        @asyncio.coroutine
+        def resolve(self, request):
+            raise web.HTTPPreconditionFailed()
+
+    app = web.Application(router=MyRouter(), loop=loop)
+
+    client = yield from test_client(app)
+
+    resp = yield from client.get('/')
+
+    assert resp.status == 412