FastApi (Starlette) + NGINX代理:请求对象中的URL方案不正确?

huangapple go评论84阅读模式
英文:

FastApi (Starlette) + NGINX Proxy: URL scheme in Request Object not correct?

问题

  • Starlette 在 Request 对象中如何设置 "url" 属性,特别是在 NGINX 代理后面运行时?
  • 如何在 NGINX 层面上操作 request.url,设置代理头不会修改它吗?
  • 如何通过 Starlette 中间件操作 request.url,我做的方式似乎没有任何效果?

设置如下:

我有一个 FastAPI 应用程序在 NGINX 代理后运行。通过浏览器,我通过 HTTPS 发送请求到 NGINX (https://www.example.com),但无论我做什么,Starlette 侧的 request.url 总是 http://www.example.com

我创建了一个小的 FastAPI 端点以进行演示

@app.get("/test")
def some_test(request: Request):
    return {"request.url": request.url,
            "request['headers']": request["headers"],
            "request.client": request.client}

在下面的截图中,我展示了我得到的内容(域名已匿名化; 设置为 xyz):
FastApi (Starlette) + NGINX代理:请求对象中的URL方案不正确?

  1. 我在浏览器中调用 https://www.example.com/test
  2. 在控制台中,我可以看到请求正确发送到 https://www.example.com/test
  3. 但是当查看 Starlette 的 Request.url 时,它显示为 http://www.example.com/test

这种行为是否正确?在截图中,我还打印了 hostx-forwarded-protox-forwarded-schema,我将它们设置为 NGINX 配置的 HTTPS,但是 Request 对象并没有受到任何修改。

我应该在 NGINX 或 FastAPI/Starlette 层面上采取什么措施来纠正这个问题?

  • 我已经尝试了设置各种命令,比如
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Scheme $scheme;
    

    但是都没有成功。

  • 我尝试编写了一个自定义的 Starlette 中间件(我知道这样做有点不雅观,但我想尝试一下,以更好地理解发生了什么)
    from fastapi import Request
    from starlette.datastructures import URL
    
    @app.middleware("http")
    async def some_middleware(request: Request, call_next):
    
        if "/test" in str(request.url):
            print(f"before {request.url}")
            request._url = URL(str(request.url).replace("http", "https"))
            print(f"after {request.url}")
    
        response = await call_next(request)
        return response
    

    但是这也没有修改响应中显示的 request.url

英文:

I'd like to know the following

  • How does Starlette set the "url" property in the Request object - especially when operated behind a NGINX proxy.
  • How can request.url be manipulated on the NGINX level -- setting proxy headers doesn't modify it at all?
  • How can request.url be manipulated via a Starlette Middleware -- somehow what I did doesn't have any effect?

The setting is like this:

I have a FastAPI app running behind a NGINX proxy. Via the browser, I send requests to NGINX via HTTPS (https://www.example.com) , but whatever I do, request.url on Starlette's side is always http://www.example.com.

I made a small FastAPI endpoint for demonstration purposes

@app.get("/test")
def some_test(request: Request):
    return {"request.url": request.url,
            "request['headers']": request["headers"],
            "request.client": request.client}

and in the following screenshots I show what I get (domain is anonymized; set to xyz):
FastApi (Starlette) + NGINX代理:请求对象中的URL方案不正确?

  1. I call in the browser https://www.example.com/test
  2. In the console, I see that properly the request is issues to https://www.example.com/test
  3. But when looking at Starlette's Request.url, it says http://www.example.com/test

Is this behaviour how it should be? In the screenshot I also print the host and x-forwarded-proto and x-forwarded-schema which I set the NGINX config to HTTPS, but the object Request isn't modified at all by that.

What can I do to correct this on the NGINX or FastAPI/Starlette level?

  • I already tried setting various commands like
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Scheme $scheme;
    

    with no success at all.

  • I tried to write a custom Starlette Middleware (I know that's quite ugly, but I wanted to try this out to better understand, what's going on)
    from fastapi import Request
    from starlette.datastructures import URL
    
    @app.middleware("http")
    async def some_middlware(request: Request, call_next):
    
        if "/test" in str(request.url):
            print(f"before {request.url}")
            request._url = URL(str(request.url).replace("http", "https"))
            print(f"after {request.url}")
    
        response = await call_next(request)
        return response
    

    but this also doesn't modify the request.url shown in the response.

答案1

得分: 0

现在模式的代理工作了。

问题是:

在我的Dockerfile之前看起来像这样:

ENTRYPOINT ["uvicorn", "main:app", "--proxy-headers", "--forwarded-allow-ips='*'", "--host", "0.0.0.0"]

--forwarded-allow-ips 参数中的引号不起作用。相反,入口点应该是:

ENTRYPOINT ["uvicorn", "main:app", "--proxy-headers", "--forwarded-allow-ips=* ", "--host", "0.0.0.0"]

提示:为了更清晰一些,不应该将*作为IP地址允许,而是应该指定IP地址。

英文:

Now the proxying of the schema works.

The problem was,the following:

Before my Dockerfile looked like

ENTRYPOINT ["uvicorn", "main:app", "--proxy-headers", "--forwarded-allow-ips='*'", "--host", "0.0.0.0"]

The quotation marks in the --forwarded-allow-ips parameter do not work. Instead the entrypoint should read

ENTRYPOINT ["uvicorn", "main:app", "--proxy-headers", "--forwarded-allow-ips=*", "--host", "0.0.0.0"]

Hint: To be a bit cleaner, one should not allow * as an IP address, instead you should specify the IP address.

huangapple
  • 本文由 发表于 2023年7月13日 00:40:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76672798.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定