sanic
sanic copied to clipboard
CORS instructions incorrect for Sanic
Is there an existing issue for this?
- [x] I have searched the existing issues
Describe the bug
I have found the instructions for configuring CORS listed here to be incorrect: https://sanic.dev/en/guide/how-to/cors.html
When following these instructions, I would run into errors like the following:
Main 2024-09-16 00:32:51 -0400 ERROR: Exception occurred while handling uri: 'http://localhost:8080/api/sessions/54b75e23-7ef4-4de1-9953-b562437080ee'
Traceback (most recent call last):
File "handle_request", line 75, in handle_request
from sanic.logging.setup import setup_logging
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'types.SimpleNamespace' object has no attribute 'request_middleware'
self.route
<Route: name=REDACTED.wrapped_handler path=api/sessions/<session_id:str>>
self.route.extra
namespace(ident='REDACTED.wrapped_handler', ignore_body=False, stream=False, hosts=[None], static=False, error_format='', websocket=False)
Main 2024-09-16 00:32:51 -0400 ERROR: Exception occurred in one of response middleware handlers
Traceback (most recent call last):
File "handle_request", line 75, in handle_request
from sanic.logging.setup import setup_logging
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'types.SimpleNamespace' object has no attribute 'request_middleware'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Volumes/workspace/Projects/ontology/oracle/automaton/.venv/lib/python3.11/site-packages/sanic/request/types.py", line 405, in respond
self.route and self.route.extra and self.route.extra.response_middleware
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'types.SimpleNamespace' object has no attribute 'response_middleware'
Specifically, I found that removing the app.router.reset()
and app.router.finalize()
from options.py
fixed this issue:
def setup_options(app: Sanic, _):
# app.router.reset()
needs_options = _compile_routes_needing_options(app.router.routes_all)
for uri, methods in needs_options.items():
app.add_route(
_options_wrapper(options_handler, methods),
uri,
methods=["OPTIONS"],
)
# app.router.finalize()
I'm not sure how critical it is for these to be invoked, but it seems everything is working fine without them.
Additionally, I was able to reduce the cors.py
and options.py
to the following, which are working:
cors.py
from sanic import Request, HTTPResponse
from typing import Iterable
def _add_cors_headers(request: Request, response: HTTPResponse, methods: str) -> None:
response.headers['Access-Control-Allow-Headers'] = "origin, content-type, accept, authorization, x-xsrf-token, x-request-id"
response.headers['Access-Control-Allow-Methods'] = methods
response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin') or '*'
def add_cors_headers(request: Request, response: HTTPResponse):
_add_cors_headers(request, response, request.app.ctx.uri_methods_mapping[request.route.uri])
options.py
from collections import defaultdict
from typing import Dict
from sanic import empty, Request, HTTPResponse, Sanic
from sanic.router import Route
from .cors import _add_cors_headers
def _compile_routes_needing_options(routes: Dict[str, Route]) -> Dict[str, str]:
needs_options = defaultdict(list)
# This is 21.12 and later. You will need to change this for older versions.
for route in routes:
if "OPTIONS" not in route.methods:
needs_options[route.uri].extend(route.methods)
return {
uri: ",".join(methods) for uri, methods in dict(needs_options).items()
}
async def options_handler(request: Request, *args, **kwargs) -> HTTPResponse:
return empty()
def setup_options(app: Sanic, _):
uri_methods_mapping = _compile_routes_needing_options(app.router.routes)
app.ctx.uri_methods_mapping = uri_methods_mapping
for uri, methods in uri_methods_mapping.items():
app.add_route(options_handler, uri, methods = ["OPTIONS"])
I understand this may not suite everyone's needs - especially if folks need custom OPTIONS
endpoints that have more specific logic. Nonetheless, I figured I would share my working configuration.
The following is the requirements.txt
for this project:
aiofiles==24.1.0
annotated-types==0.7.0
anyio==4.4.0
appdirs==1.4.4
distro==1.9.0
fs==2.4.16
groq==0.9.0
h11==0.14.0
html5tagger==1.3.0
httpcore==1.0.5
httptools==0.6.1
httpx==0.27.0
multidict==6.0.5
openai==1.35.13
pydantic-core==2.20.1
pydantic==2.8.2
pygments==2.18.0
pystache==0.6.5
sanic-routing==23.12.0
sanic==24.6.0
setuptools==73.0.1
sniffio==1.3.1
tracerite==1.1.1
ujson==5.10.0
uvloop==0.20.0
websockets==13.0
The following is the requirements-dev.txt
for this project:
build~=1.2.1
coverage~=7.5.3
pytest~=8.2.1
tox~=4.15.0
Code snippet
No response
Expected Behavior
No response
How do you run Sanic?
As a script (app.run
or Sanic.serve
)
Operating System
Linux
Sanic Version
24.6.0
Additional context
No response