简体   繁体   English

如何配置 traefik 来处理 CORS 预检请求?

[英]How to configure traefik to handle CORS preflight requests?

I have a very simple FastAPI-Traefik-Docker Setup which you can find here: https://github.com/mshajarrazip/fastapi-traefik-docker-cors我有一个非常简单的 FastAPI-Traefik-Docker 设置,您可以在这里找到它: https://github.com/mshajarrazip/fastapi-traefik-docker-cors

Just do docker-compose up -d to run the FastAPI and traefik services.只需执行docker-compose up -d即可运行 FastAPI 和 traefik 服务。

TLS/SSL is not configured in this setup.此设置中未配置 TLS/SSL。

The app has two endpoints GET "/hello" and POST "/add" which does nothing but return json reponses:该应用程序有两个端点 GET "/hello" 和 POST "/add",除了返回 json 响应外什么都不做:

In app/main.pyapp/main.py

import logging

from app import routes
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

def create_application() -> FastAPI:
    app = FastAPI(title="✨ NAC ML API ✨")

    app.include_router(routes.hello.router)

    # add CORS
    # origins = ["*"]
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"], # Allows all origins
        allow_credentials=True,
        allow_methods=["*"], # Allows all methods
        allow_headers=["*"], # Allows all headers
        )

    return app


app = create_application()

In app/routes/...app/routes/...

from fastapi import APIRouter

router = APIRouter()

@router.get("/hello")
def hello():
    return {
        "message": "Hello!"
    }

@router.post("/add")
def add_to_db():
    return {
        "id": 1, 
        "name": "Salmah",
        "age": 29
    }

Calling these endpoints via curl and Postman works:通过 curl 和 Postman 调用这些端点有效:

curl -X 'GET' http://172.16.239.132:81/hello -H 'Host: basic.api'
curl -X 'POST' http://172.16.239.132:81/add -H 'Host: basic.api'

But not from a simple javascript fetch in a simple html file running on the Chrome browser (from my local computer to the remote server where traefik is running):但不是从一个简单的 javascript 获取在一个简单的 html 文件中运行在 Chrome 浏览器上(从我的本地计算机到运行 traefik 的远程服务器):

<html>
    <Button id="btnAddData" onclick="AddData();"> Add Data </Button>
</html>

<script>
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Host", "basic.api");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  redirect: 'follow'
};

function AddData(){
  console.log("click");

  fetch("http://172.16.239.132:81/add", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
}
</script>

Response:回复:

Access to fetch at 'http://172.16.239.132:81/add' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

The traefik logs via docker-compose logs -f reverse-proxy : traefik 通过docker-compose logs -f reverse-proxy

reverse-proxy_1  | 10.32.26.13 - - [18/Dec/2022:03:34:45 +0000] "OPTIONS /add HTTP/1.1"  404 19 "-" "-" 9 "-" "-" 0ms

I figured the issue may be because I was running fetch from the local file system, so instead I emulate the OPTIONS request using curl (and this is requesting for GET):我认为问题可能是因为我正在从本地文件系统运行提取,所以我使用 curl 模拟 OPTIONS 请求(这是请求 GET):

curl -i -X OPTIONS \
    -H 'Access-Control-Request-Method: GET' \
    -H 'Access-Control-Request-Headers: Content-Type' \
    -H 'Host: basic.api' \
    "http://172.16.239.132:81/hello"

I get a 405 Method not allowed error:我收到 405 Method not allowed 错误:

HTTP/1.1 405 Method Not Allowed
Allow: GET
Content-Length: 31
Content-Type: application/json
Date: Sun, 18 Dec 2022 03:39:40 GMT
Server: uvicorn

{"detail":"Method Not Allowed"}

Same goes when I set 'Access-Control-Request-Method: OPTIONS' .当我设置'Access-Control-Request-Method: OPTIONS'时也是如此。

This is my traefik setup in docker-compose.yml:这是我在 docker-compose.yml 中的 traefik 设置:

services:
  reverse-proxy:
    image: traefik:v2.9
    ports:
      - ${API_PORT}:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.yml:/etc/traefik/traefik.yml
    labels:
      - "traefik.http.middlewares.cors.headers.accesscontrolallowmethods=*"
      - "traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=*"
      - "traefik.http.middlewares.cors.headers.accesscontrolmaxage=100"
      - "traefik.http.middlewares.cors.headers.addvaryheader=true"

I followed the recommendation here from the official Traefik docs on CORS headers , which obviously isn't all there is I need to know.我遵循了官方 Traefik 文档关于 CORS headers的建议,这显然不是我需要知道的全部。

I'm not a CORS.networking expert so that adds to my current set of difficulties:( I don't know how to solve this issue. I've been at it for five days with various combination of solutions. At first I suspected it was FastAPI's CORSMiddleware module that was buggy - but the CORSMiddleware set up in the FastAPI code is working as I tested it without traefik and it worked fine.我不是 CORS.networking 专家,所以这增加了我目前的一系列困难:( 我不知道如何解决这个问题。我已经用各种解决方案组合解决了五天。起初我怀疑是 FastAPI 的 CORSMiddleware 模块有问题——但在 FastAPI 代码中设置的 CORSMiddleware 正在运行,因为我在没有 traefik 的情况下对其进行了测试,并且运行良好。

I'm sure it's a common issue among newbies to traefik but everywhere I googled, I couldn't find a straightforward answer (or maybe the answer is already there I just don't understand it).我确定这是 traefik 新手的一个常见问题,但我在谷歌上到处搜索,都找不到直接的答案(或者答案可能已经存在,我只是不明白)。 Plus, all their setup are not as simple as my current setup (they have TLS/SSL configured and everything).另外,他们所有的设置都不像我当前的设置那么简单(他们配置了 TLS/SSL 等等)。

I don't think the issue is FastAPI so much as it is traefik itself.我认为问题不在于 FastAPI,而在于 traefik 本身。 When calling the endpoints via fetch, I don't think the requests even reaches the FastAPI app.通过获取调用端点时,我认为请求甚至没有到达 FastAPI 应用程序。 So any solutions should probably apply at the traefik reverse proxy and hence, should be applicable to any other deployments regardless if it's FastAPI or not.因此,任何解决方案都应该适用于 traefik 反向代理,因此,无论是否为 FastAPI,都应适用于任何其他部署。

Please help:( I'm sure it will help a lot of beginners too.请帮忙:(我相信它也会帮助很多初学者。

As it turns out, if there is an error somewhere in traefik's configuration eg the labels on your service is not set correctly, you will always get the CORS error (bad config = no response to preflight request = CORS error).事实证明,如果 traefik 的配置某处存在错误,例如您的服务上的标签设置不正确,您将始终收到 CORS 错误(错误的配置 = 没有响应预检请求 = CORS 错误)。 For FastAPI, the in-built CORSMiddleware should work fine even without configuring CORS at traefik level.对于 FastAPI,即使不在 traefik 级别配置 CORS,内置的 CORSMiddleware 也应该可以正常工作。

My mistake was not realising the error is setting up the labels for my FastAPI service.我的错误是没有意识到错误是为我的 FastAPI 服务设置标签。

Somehow, changing from this:不知何故,从这个改变:

api:
  ...
  labels: # new
    - "traefik.enable=true"
    - "traefik.http.routers.to_api.rule=Host(`basic.api`) && PathPrefix(`/basic/`)"

to this ( && -> || ):为此( && -> || ):

api:
  ...
  labels: # new
    - "traefik.enable=true"
    - "traefik.http.routers.to_api.rule=Host(`basic.api`) || PathPrefix(`/basic/`)"

Fixes the issue - my requests are now routed correctly to FastAPI for its CORS middleware to handle the preflights.解决了这个问题——我的请求现在被正确地路由到 FastAPI,以便它的 CORS 中间件来处理预检。 But still, ||但是, || is not what I really intended - either I have a misconception of the rules or it is a bug?这不是我真正想要的——要么我对规则有误解,要么这是一个错误? I have no answer at the moment.我现在没有答案。

To enable CORS for your backend service in the docker-compose.yml file, you can add the following labels to the backend service:要在 docker-compose.yml 文件中为您的后端服务启用 CORS,您可以在后端服务中添加以下标签:

  # Enable CORS headers
  - "traefik.http.middlewares.cors.headers.accesscontrolallowmethods=*"
  - "traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=*"
  - "traefik.http.middlewares.cors.headers.accesscontrolmaxage=100"
  - "traefik.http.middlewares.cors.headers.addvaryheader=true"

These labels will configure the CORS middleware to allow any HTTP method, any origin, and a maximum age of 100 seconds for the CORS headers.这些标签将配置 CORS 中间件以允许任何 HTTP 方法、任何来源以及 CORS 标头的最长 100 秒寿命。 The addvaryheader flag will also add a Vary header to the response to indicate that the response may vary based on the Origin header. addvaryheader 标志还将向响应添加 Vary header,以指示响应可能会根据 Origin header 而有所不同。

Once you have added these labels to the backend service, you can apply the CORS middleware to your routers by adding the middleware to the router's middlewares label. For example:将这些标签添加到后端服务后,您可以通过将中间件添加到路由器的中间件 label 来将 CORS 中间件应用到路由器。例如:

  # Use the CORS middleware for the app-https router
  - traefik.http.routers.app-https.middlewares=cors

This will apply the CORS middleware to the app-https router, allowing the backend service to accept CORS requests from the specified origins.这会将 CORS 中间件应用到 app-https 路由器,允许后端服务接受来自指定来源的 CORS 请求。 In my case it was https. However;就我而言,它是 https。但是; I think you should add this for http我认为你应该为 http 添加这个

  - traefik.http.routers.my-router.middlewares=cors

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM