使 Sanic 后端支持 CORS

1. 源端消除 Preflight

Preflight(预检请求)是指在跨源发出用户请求前,预先发出一个 OPTIONS 请求,查询服务端是否允许从此源站的请求。

Restful 规范是“罪魁祸首”。因为跨域 fetch 时,application/json 类型的 Content-Type 必然会触发 Preflight。

只有满足简单请求fetch,才不会触发 Preflight:

  • 请求方法为 GET/HEAD/POST
  • 除浏览器自动添加的 header 外,只可以人为设置 Accept/Accept-Language/Content-Language/Content-Type/Range 头部字段
  • Content-Type 只可以取值 text/plain/multipart/form-data/application/x-www-form-urlencoded

所以要解决常见的 restful 式的 Preflight,最简单的解决办法,就是去掉显式的 Content-Type 声明。浏览器会默认消息体格式为 text/plain

2. 后端允许 CORS

当请求头字段 Origin 标识的地址跟服务端地址不同时,即构成 CORS。如果服务端接受从此地址发出的请求,需要添加响应头 Access-Control-Allow-Origin

Access-Control-Allow-Origin 字段值可以是通配符 *

1
Access-Control-Allow-Origin: *

表示此资源对所有访问者开放。字段值也可以是某一个具体的地址,表示只限定此源的访问:

1
Access-Control-Allow-Origin: https://foo.example

具体到 Sanic,可以通过安装 sanic-ext 扩展实现对 CORS 的完整支持:

1
pip install sanic-ext
1
2
3
4
from sanic_ext import Extend

app.config.CORS_ORIGINS = 'https://foo.example'
Extend(app)

简单来说,扩展通过截听路由,为其添加对 OPTIONS 的响应;并动态添加 CORS 必需的响应头,以此实现了 CORS 支持。

回到 Restful 场景,或其它可以在源端避免 Preflight 的任何场景,sanic-ext 则不是必需。仅需在每个响应的头中加入 access-control-allow-origin 字段即可:

1
2
3
@app.on_response
async def on_response(_request, response):
response.headers['access-control-allow-origin'] = '*'

参考

  1. 跨源资源共享(CORS):https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
  2. Cross-origin resource sharing (CORS):https://sanic.dev/en/guide/how-to/cors.html
  3. CORS protection:https://sanic.dev/en/plugins/sanic-ext/http/cors.html#basic-implementation

评论