aiohttp-cors icon indicating copy to clipboard operation
aiohttp-cors copied to clipboard

Problems with aiohttp 3.0.1

Open kamikaze opened this issue 6 years ago • 29 comments

This code works fine with aiohttp 2.3.10:

def setup_routes(app):
    app.router.add_routes(routes)
    setup_swagger(app,
                  api_base_url='/',
                  swagger_url='/api/doc',
                  description='API testing interface',
                  title='API',
                  api_version='2.0.0')

    cors = aiohttp_cors.setup(app, defaults={
        "*": aiohttp_cors.ResourceOptions(
            allow_credentials=True,
            expose_headers="*",
            allow_headers="*",
        )
    })

    for route in list(app.router.routes()):
        if not isinstance(route.resource, StaticResource):  # <<< WORKAROUND
            cors.add(route)

curl -H "Origin: http://example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: X-Requested-With" -X OPTIONS --verbose localhost:8080/api/users

*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> OPTIONS /api/users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Origin: http://example.com
> Access-Control-Request-Method: POST
> Access-Control-Request-Headers: X-Requested-With
> 
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: http://example.com
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: POST
< Access-Control-Allow-Headers: X-REQUESTED-WITH
< Content-Length: 0
< Content-Type: application/octet-stream
< Date: Wed, 14 Feb 2018 23:19:55 GMT
< Server: Python/3.6 aiohttp/2.3.10
<

But following (fixed OPTIONS error, adding routes differently) code fails for aiohttp 3.0.1:

    # This code fails with:
    # RuntimeError: Added route will never be executed, method OPTIONS is already registered
    # for route in list(app.router.routes()):
    #     if not isinstance(route.resource, StaticResource):  # <<< WORKAROUND
    #         cors.add(route)

    for resource in app.router.resources():
        cors.add(resource)

Same cURL request results in:

*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> OPTIONS /api/users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Origin: http://example.com
> Access-Control-Request-Method: POST
> Access-Control-Request-Headers: X-Requested-With
> 
< HTTP/1.1 403 Forbidden
< Content-Type: text/plain; charset=utf-8
< Content-Length: 99
< Date: Wed, 14 Feb 2018 23:21:01 GMT
< Server: Python/3.6 aiohttp/3.0.1
< 
* Connection #0 to host localhost left intact
CORS preflight request failed: request method 'POST' is not allowed for 'http://example.com' origin

kamikaze avatar Feb 14 '18 23:02 kamikaze

Actually with the following code CORS doesn't work even for 2.3.10:

    for resource in app.router.resources():
        cors.add(resource)

kamikaze avatar Feb 14 '18 23:02 kamikaze

Route definition:

@routes.post('/api/users')
async def create_user(request: Request):
    return web.json_response(status=web.HTTPCreated.status_code)

kamikaze avatar Feb 14 '18 23:02 kamikaze

We have the issue here, have you found a workaround?

asyd avatar Feb 23 '18 12:02 asyd

Guys, the library has several technical debts. I've converted all tests to pytest fixtures usage and replaced yield from with async/await syntax. Next thing is getting rid of special flag for aiohttp.web.View support in favor of isinstance check. After getting this done the code will be ready to adopting to aiohttp 3. I expect to find a time for it in a week.

asvetlov avatar Feb 23 '18 15:02 asvetlov

Ok thanks for your answer!

asyd avatar Feb 23 '18 15:02 asyd

Fixed by #160

asvetlov avatar Mar 05 '18 13:03 asvetlov

I hate to be that guy, but will there be a new release to PyPI? I see there was a commit to bump the version, but I don't see it published yet. Thanks :)

mheppner avatar Mar 06 '18 15:03 mheppner

Thanks for remember. Yesterday travis was overwhelmed, I forgot to initiate a new release building procedure. Done

asvetlov avatar Mar 06 '18 15:03 asvetlov

Having following error:

CORS preflight request failed: request method 'POST' is not allowed for 'http://example.com' origin

with: aiohttp==3.0.9 aiohttp-cors==0.7.0

same code works for aiohttp 2 and aiohttp-cors 0.6.0. Is there real example of adding routes to cors that do work and was tested?

kamikaze avatar Mar 18 '18 21:03 kamikaze

https://github.com/aio-libs/aiohttp-cors/blob/master/aiohttp_cors/urldispatcher_router_adapter.py#L296

Here resource_config.method_config is an empty dict. So if not setting default allow_methods to '*' globally - requests do not work with CORS

kamikaze avatar Mar 18 '18 22:03 kamikaze

btw. Adding to cors with both methods doesn't help:

    for resource in app.router.resources():
        cors.add(resource)
    for route in list(app.router.routes()):
        if not isinstance(route.resource, StaticResource):  # <<< WORKAROUND
            cors.add(route)

kamikaze avatar Mar 18 '18 22:03 kamikaze

The same for me. Rolled back to aiohttp 2 and aiohttp_cors 0.6.0

valentinmk avatar Apr 16 '18 09:04 valentinmk

Same here. aiohttp 2.0 + aiohttp-cors 0.6.0 seems to be a good solution for now.

valentinradu avatar Apr 18 '18 09:04 valentinradu

Guys, if somebody wants to provide a PR -- you are welcome. I'm convinced that the problem exists but have no estimation for the fix.

asvetlov avatar Apr 18 '18 10:04 asvetlov

@asvetlov I'll try to look into it over the weekend. Btw, for me, this only happens on localhost, all other places work fine.

valentinradu avatar Apr 18 '18 10:04 valentinradu

Cool!

asvetlov avatar Apr 18 '18 10:04 asvetlov

I could make some progress to solve this problem. This problem happens when you tried to name all routes as it recommended by Swagger notation. And I do not find any fast solution to fix it. Script to reproduce this problem:

import asyncio
import aiohttp
import aiohttp_cors
from aiohttp.web_runner import GracefulExit


async def handler(request):
    return aiohttp.web.Response(
        text="Hello!",
        headers={
            "X-Custom-Server-Header": "Custom data",
        })

app = aiohttp.web.Application()

cors = aiohttp_cors.setup(app, defaults={
    "http://request-from": aiohttp_cors.ResourceOptions(
            allow_credentials=True,
            expose_headers="*",
            allow_headers="*",
        ),
    })

routes = [{
    'method': 'GET',
    'path': '/test',
    'handler': handler,
    'name': 'test-good'
    }, {
    'method': 'POST',
    'path': '/test',
    'handler': handler,
    'name': 'test-good'
    }, {
    'method': 'GET',
    'path': '/test-bad',
    'handler': handler,
    'name': 'test-bad-get'
    }, {
    'method': 'POST',
    'path': '/test-bad',
    'handler': handler,
    'name': 'test-bad-post'
    }, ]
for route in routes:
    cors.add(
        app.router.add_route(
            method=route['method'],
            path=route['path'],
            handler=route['handler'],
            name=route['name']
        )
    )
loop = asyncio.get_event_loop()
handler = app._make_handler()
server = loop.run_until_complete(
    loop.create_server(
        handler,
        host='localhost',
        port='8081'
    )
)

try:
    loop.run_forever()
except (GracefulExit, KeyboardInterrupt):
    pass
finally:
    loop.stop()

GET and POST for /test works perfect. GET and POST for /test-bad isn't work as expected.

For self._resource_config at https://github.com/aio-libs/aiohttp-cors/blob/6efa4b5ed41bd8ae272b092d90c6302fd65b78c7/aiohttp_cors/urldispatcher_router_adapter.py#L281 we will get some thing like that:

{
  <PlainResource 'test-bad-get'  /test-bad>: instance(_ResourceConfig):
    default_config: {
    },
    method_config: {
      'GET': {
      },
    }
  <PlainResource 'test-bad-post'  /test-bad>: instance(_ResourceConfig):
    default_config: {
    },
    method_config: {
      'POST': {
      },
    }
  <PlainResource 'test-good'  /test>: instance(_ResourceConfig):
    default_config: {
    },
    method_config: {
      'GET': {
      },
      'POST': {
      },
    }
}

But for OPTION request to url /test-bad resource nether with Access-Control-Request-Method set to 'GET' or 'POST' at https://github.com/aio-libs/aiohttp-cors/blob/6efa4b5ed41bd8ae272b092d90c6302fd65b78c7/aiohttp_cors/urldispatcher_router_adapter.py#L280 will return every time:

<PlainResource 'test-bad-get'  /test-bad>

So we try to find method POST in <PlainResource 'test-bad-get' /test-bad> node of self._resource_config and couldn't as it designed.

valentinmk avatar May 18 '18 12:05 valentinmk

That's interesting. Technically test-bad-get and test-bad-post are the same resource which always returns the same URL in url_for(). Perhaps aiohttp should raise a warning on trying to add the same URL pattern under different names.

Would be nice to fix aiohttp-cors to correctly process this case as well maybe but I not sure how complex the fix is.

asvetlov avatar May 22 '18 09:05 asvetlov

I have this problem also. I'm keeping with aiohttp 2.3.10. Thanks.

jersobh avatar Jun 01 '18 18:06 jersobh

aiohttp-3.3.2 + aiohttp-cors-0.7.0 works fine too! <3

valentinradu avatar Jun 15 '18 21:06 valentinradu

Hey guys ! I think i have the same issue. @valentinmk did you find a workaround ?

TTRh avatar May 09 '19 17:05 TTRh

@TTRh I used the above mentioned versions and it worked. Unfortunately I was not able to make it work with the latest version.

valentinradu avatar May 09 '19 17:05 valentinradu

That sucks... :/

jersobh avatar May 09 '19 17:05 jersobh

Hum only rollbacking to aiohttp 2 worked for me :/

TTRh avatar May 09 '19 17:05 TTRh

def setup_routes(app):
    app.router.add_routes(routes)
    setup_swagger(app,
                  api_base_url='/',
                  swagger_url='/api/doc',
                  description='API testing interface',
                  title='API',
                  api_version='2.0.0')

    cors = aiohttp_cors.setup(app, defaults={
        "*": aiohttp_cors.ResourceOptions(
            allow_credentials=True,
            expose_headers="*",
            allow_headers="*",
        )
    })

    for route in list(app.router.routes()):
        if not isinstance(route.resource, StaticResource):  # <<< WORKAROUND
            cors.add(route)

This code worked for me with aiohttp 3.5.4, aiohttp-cors 0.7.0

atalaybaysal avatar Sep 05 '19 11:09 atalaybaysal

up) same problems with aiohttp 3.6.2 and aiohttp-cors 0.7.0

makusudi avatar Jan 24 '20 15:01 makusudi

Pull Request for aiohttp-cors is welcome!

asvetlov avatar Jan 24 '20 15:01 asvetlov

I'm faced with this issue with POST requests and CORS also, but everything works fine with this code:

from aiohttp_cors import setup as cors_setup, ResourceOptions

...

routes = [
    web.get("/", handle),
    web.post("/something", another_handle),
]

app.router.add_routes(routes)

cors = cors_setup(
    app,
    defaults={
        "*": ResourceOptions(
            allow_credentials=True, expose_headers="*", allow_headers="*",
        )
    },
)

for route in list(app.router.routes()):
    cors.add(route)

Versions: aiohttp 3.6.2 and aiohttp-cors 0.7.0

Maybe this will help someone else with this issue.

kirzharov avatar Jul 17 '20 16:07 kirzharov

from aiohttp_cors import setup as cors_setup, ResourceOptions

thanks it's work for me

leiyugithub avatar Aug 06 '20 09:08 leiyugithub