Skip to content
This repository has been archived by the owner on Feb 25, 2022. It is now read-only.

CORS Support #66

Closed
kinabalu opened this issue Apr 20, 2017 · 29 comments
Closed

CORS Support #66

kinabalu opened this issue Apr 20, 2017 · 29 comments

Comments

@kinabalu
Copy link
Contributor

Using any API from the browser obviously requires support for CORS. Django and Flask have several ways to integrate this, and I'd love to see that in this project. Not sure what the most effective way to enable this is.

In a small API I built just recently, I've got it hardcoded, but it would be great to identify the headers, and turn it on.

@tomchristie
Copy link
Member

Yes, absolutely!
What headers did you end up setting for your particular case?
What configuration would we want of this?

@tomchristie
Copy link
Member

See also: CSP

Are there any other security/access response header types that we'll want to consider, too?

@tomchristie tomchristie changed the title CORS Support CORS & CSP Support Apr 20, 2017
@kinabalu
Copy link
Contributor Author

Here are the settings I use on the server-side:

Access-Control-Allow-Origin
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Credentials
Access-Control-Expose-Headers

The only other one I know of is Access-Control-Max-Age which states how long the browser can cache the preflight.

On the client end the preflight will possibly send Access-Control-Request-Method and Access-Control-Request-Headers

As far as configuration goes, I'm a fan of a decorator on methods, or something more global that you can add to the routes (since we already know what's there)

For Content-Security-Policy headers, it doesn't seem to be pertinent for API pages? If apistar is only for API calls, I don't know that CSP applies in this case.

@tomchristie tomchristie changed the title CORS & CSP Support CORS Support Apr 20, 2017
@tomchristie
Copy link
Member

Okay, I'm gonna restrict this to "CORS Support" for now.

However API Star really will be a general purpose framework, just with a primary focus on APIs. (Example, we'll be serving up API docs, which requires us to have both HTML templating and static files support) Yeah we could potentially think about alternative names, but it's tricky! 🤔 💭

@audiolion
Copy link
Contributor

how about Tom ^^

I was a bit confused when you started adding templates, ORM, etc. because APIStar made me think it was a faster rest_framework that was more loosely coupled and included mypy typings

@kinabalu
Copy link
Contributor Author

Yeah I get that, we're actually using it to serve images for another API we're building, had to drop into WSGI support to achieve that.

How would you go about adding CORS support in an "apistar"-kinda way?

@tomchristie
Copy link
Member

At the moment, returning 'Response' (You shouldn't need to drop to WSGI now, right?)

One obvious option for now might be introducing an ['HTTP']['DEFAULT_HEADERS'] settings. How does that sound as a starting point?

@kinabalu
Copy link
Contributor Author

Sorry, WSGI was for the images (not CORS), because it seems like apistar wants to utf8 encode the Response.

CORS isn't a default header, we need to add support for every endpoint to allow an HTTP OPTIONS with these headers.

So if POST /foo the browser will send an OPTIONS /foo first, and is expecting the CORS headers

@tomchristie
Copy link
Member

tomchristie commented Apr 21, 2017

So, OPTIONS requests will currently result in raising a MethodNotAllowed, get handled by our exception handler function, and result in a 405 "Method not allowed" response.

Dealing with OPTIONS requests somewhere around there is probably a sensible thing to do, eg. We could raise a different kind of exception specific to OPTIONS requests and handle that accordingly in the exception handler.

Happy to see either (1) the code that @kinabalu is currently using to work around the lack of support, or (2) a pull request with a first stab and handling this.

I'd probably suggest looking to https://github.com/ottoyiu/django-cors-headers/blob/master/corsheaders/middleware.py when it comes to implementation.

@tomchristie
Copy link
Member

I'm going to put a newcomer label on this one, because I think it's something that someone could at least take a first go at...

  • Where the Router raisesMethodNotAllowed we should first check for method=='OPTIONS' and raise a different exception in that case.
  • In the exception handler ensure that we catch that different case, and handle it explicitly.
  • Include Settings in the exception handler function annotations, and use the app settings to set some request headers in the OPTIONS case. Presumably return 200 OK if allowed, not sure what's an appropriate response when eg. CORS requests are not allowed by the server.

@kinabalu
Copy link
Contributor Author

Happy to take this up, having a bit of trouble figuring out how to inject settings though. Even just importing into the routing.py throws an: AttributeError: module 'apistar.app' has no attribute 'App'

@tomchristie
Copy link
Member

Circular import dependency maybe?

@kinabalu
Copy link
Contributor Author

Definitely could be, here's the full stack trace... if it was a circular import dependency how would that get resolved:

Traceback (most recent call last):
  File "venv/bin/apistar", line 11, in <module>
    load_entry_point('apistar', 'console_scripts', 'apistar')()
  File "./venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 560, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "./venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2648, in load_entry_point
    return ep.load()
  File "./venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2302, in load
    return self.resolve()
  File "./venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2308, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "./venv/src/apistar/apistar/__init__.py", line 8, in <module>
    from apistar.app import App
  File "./venv/src/apistar/apistar/app.py", line 7, in <module>
    from apistar import commands, pipelines, routing, schema
  File "./venv/src/apistar/apistar/routing.py", line 13, in <module>
    from apistar.settings import Settings
  File "./venv/src/apistar/apistar/settings.py", line 5, in <module>
    class Settings(dict):
  File "./venv/src/apistar/apistar/settings.py", line 7, in Settings
    def build(cls, app: app.App):
AttributeError: module 'apistar.app' has no attribute 'App'

@tomchristie
Copy link
Member

Take a look at App.init. You'll see that there's already a couple of other imports that we defer. Might need to do the same with 'routing'

@kinabalu
Copy link
Contributor Author

Looking at that, I'm assuming you mean Settings and Templates... if that's the case the only issue is the type annotation parameter which references routing.Route. Removing that to test, does remove the circular dependency error though

@tomchristie
Copy link
Member

  1. What's the traceback look like after you've removed 'routing' from the import at the top of the module?
  2. Try deleting any .pyc / pycache files.

@kinabalu
Copy link
Contributor Author

    Traceback (most recent call last):
    File "venv/bin/apistar", line 11, in <module>
    load_entry_point('apistar', 'console_scripts', 'apistar')()
    File "venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 560, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
    File "venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2648, in load_entry_point
    return ep.load()
    File "venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2302, in load
    return self.resolve()
    File "venv/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2308, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
    File "venv/src/apistar/apistar/__init__.py", line 8, in <module>
    from apistar.app import App
    File "venv/src/apistar/apistar/app.py", line 13, in <module>
    class App(object):
    File "venv/src/apistar/apistar/app.py", line 23, in App
    settings: Dict[str, Any] = None) -> None:
    NameError: name 'routing' is not defined

@tomchristie
Copy link
Member

tomchristie commented Apr 22, 2017

It looks like you don't have it imported at all in this case ^.

Feel free to point me at a gist of what you have, or make a failing pull request and I'll help you get that properly resolved.

@tomchristie
Copy link
Member

Righty, that's a bit fiddly ATM, due to a circular annotation dependency.
I'll come back to looking at this soon enough. In the meantime #17 is a similar problem area but ought to be simpler to resolve, if anyone want to tackle that.

@tomchristie tomchristie modified the milestone: 0.2 Release Apr 24, 2017
@tomchristie
Copy link
Member

As a workaround in the interm I'd suggest using a WSGI middleware to resolve this.

Something like...

app.wsgi = CORSMiddleWare(app.wsgi)

There's a couple of existing examples, one package here https://github.com/may-day/wsgicors and a snippet here pallets/werkzeug#131 (comment)

@tomchristie tomchristie removed this from the 0.2 Release milestone Aug 17, 2017
@tomchristie tomchristie removed this from the 0.2 Release milestone Aug 17, 2017
allanice001 added a commit to allanice001/apistar that referenced this issue Aug 21, 2017
Very quick and dirty hack to fix encode#66
@elensalt
Copy link

Hello all,
I would like to add CORS support to asyncio. Even if I added the required headers, I am getting an error of 405 Method Not Allowed for the OPTIONS request. Any suggestions on how to handle this?

@maziarz
Copy link
Contributor

maziarz commented Sep 10, 2017

Is there a short term workaround for this in 0.2.x?

@kinabalu
Copy link
Contributor Author

Just upgraded one of my projects to 0.3.x and had to change the middleware, thought this might be of use to someone. I have no idea if this is the recommended way, but it "works":

class CORSMiddleware(App):
    """Add Cross-origin resource sharing headers to every request."""

    def __init__(self, origin='*', **kwargs):
        super().__init__(**kwargs)
        self.origin = origin

    def __call__(self, environ, start_response):

        def add_cors_headers(status, headers, exc_info=None):
            headers = Headers(headers)
            headers.add("Access-Control-Allow-Origin", self.origin)
            headers.add("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization")
            headers.add("Access-Control-Allow-Credentials", "true")
            headers.add("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE")

            return start_response(status, headers.to_list())

        if environ.get("REQUEST_METHOD") == "OPTIONS":
            add_cors_headers("200 OK", [("Content-Type", "text/plain")])
            return [b'200 OK']

        return super().__call__(environ, add_cors_headers)

app = CORSMiddleware(
    origin='*',
    routes=routes,
    settings=settings,
    commands=commands,
)

@castaneai
Copy link

works with wsgicors

from apistar.frameworks.wsgi import WSGIApp as App
from wsgicors import CORS

class CORSApp(App):
    def __call__(self, environ, start_response):
        cors = CORS(super().__call__, headers='*', methods='*', maxage='180', origin='*')
        return cors(environ, start_response)

app = CORSApp(routes=routes, settings=settings)

@ratulotron
Copy link

Hi! I tried methods by both @kinabalu and @castaneai but none of them worked :( Is there any other fix for this CORS problem I would really appreciate some help soon ^_^

@kinabalu
Copy link
Contributor Author

@ratulotron it would be better if you showed a sample project where you're trying to use it, I've tried both methods and they appear to work fine.

@Bogdanp
Copy link
Contributor

Bogdanp commented Mar 8, 2018

I made a little package to add CORS functionality to a WSGI App here: https://github.com/Bogdanp/apistar_cors . It uses wsgicors under the hood.

@tomchristie
Copy link
Member

Event hooks will be coming in #400. We'll actually have proper, sensible, nice answers for basic functionality like CORS. ✨

@tomchristie
Copy link
Member

Closing this off given that 0.6 is moving to a framework-agnostic suite of API tools, and will no longer include the server. See https://discuss.apistar.org/t/api-star-as-a-framework-independant-tool/614 and #624.

For functionality like CORS support I'd like to see us building out ASGI middleware, rather than framework-specific APIs that end up having to be implemented and re-implemented.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants