You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI子应用嵌入iframe时混合内容错误的排查与解决

问题描述

我在Google Cloud Run上部署了基于FastAPI的NiceGUI Web应用,主应用通过HTTPS提供服务,但将Chainlit子应用以iframe形式嵌入主应用(Chainlit已挂载为FastAPI子应用)。加载主应用时出现混合内容错误,Chainlit端点无法加载,错误提示:

Mixed Content: The page at 'https://main-app.a.run.app/' was loaded over HTTPS, but requested an insecure frame 'http://main-app.a.run.app/chat/'. This request has been blocked; the content must be served over HTTPS.

手动在浏览器调试工具修改iframe URL再改回后,iframe能正常加载。请问:导致子应用默认使用HTTP的原因是什么?如何确保其始终通过HTTPS提供服务以避免混合内容错误?

我的实现代码如下:

from nicegui import context, app, ui

import chainlit as cl
from chainlit.utils import mount_chainlit


ui.html(f'<iframe id="chat_frame" src="{CHAINLIT_BASE_URL}" style="width: 100%; height: 100%; border: none;"></iframe>').classes('w-full h-full')

@app.middleware("http")
async def add_security_headers(request, call_next):
    response = await call_next(request)
    
    # Content Security Policy header for iframe embedding
    response.headers['Content-Security-Policy'] = f"frame-ancestors 'self' {URL_REMOTE};"

    return response

mount_chainlit(app=app, target="chainlit_app.py", path="/chat")  # Mount the Chainlit app

ui.run(title=TITLE, favicon=FAVICON, storage_secret=API_KEY,
       host=BASE_URL, port=PORT)

原因分析

  • Google Cloud Run内部通过反向代理与应用通信,应用接收到的请求协议是HTTP,Chainlit默认读取这个内部协议生成资源URL,而非外部用户访问的HTTPS,导致iframe请求HTTP资源。
  • 如果CHAINLIT_BASE_URL是硬编码的HTTP地址,或者没有根据当前请求动态生成,也会直接引发混合内容问题。
  • 手动修改URL再改回后能加载,是因为浏览器此时会强制沿用当前页面的HTTPS协议重新请求,相当于修正了协议不匹配的问题。

解决方法

  1. 配置应用信任反向代理
    在ui.run()之前添加中间件,让FastAPI/Starlette识别反向代理传递的协议头,强制所有请求使用HTTPS:

    from fastapi.middleware.trustedhost import TrustedHostMiddleware
    from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
    
    # 信任Cloud Run反向代理,允许转发的协议头生效
    app.add_middleware(TrustedHostMiddleware, allowed_hosts=["*"])
    app.state.trusted_proxies = ["*"]  # 生产环境建议指定Cloud Run的代理IP段,提升安全性
    app.add_middleware(HTTPSRedirectMiddleware)  # 自动将HTTP请求重定向到HTTPS
    
  2. 动态生成Chainlit的HTTPS地址
    避免硬编码CHAINLIT_BASE_URL,从当前请求上下文动态获取协议和主机地址:

    @ui.page('/')
    def main_page():
        # 从NiceGUI的请求上下文获取当前协议和主机
        request = context.get_client().request
        chainlit_url = f"{request.url.scheme}://{request.url.hostname}/chat"
        ui.html(f'<iframe id="chat_frame" src="{chainlit_url}" style="width: 100%; height: 100%; border: none;"></iframe>').classes('w-full h-full')
    
  3. 配置Chainlit的服务地址
    通过环境变量强制Chainlit使用HTTPS地址:
    在Cloud Run部署配置中添加环境变量:

    CHAINLIT_SERVER_URL=https://main-app.a.run.app/chat
    

    或者在代码中动态设置:

    import os
    request = context.get_client().request
    os.environ["CHAINLIT_SERVER_URL"] = f"{request.url.scheme}://{request.url.hostname}/chat"
    
  4. 完善Content-Security-Policy头
    更新CSP规则,明确允许HTTPS的iframe资源:

    @app.middleware("http")
    async def add_security_headers(request, call_next):
        response = await call_next(request)
        host = request.url.hostname
        # 允许当前域名的HTTPS iframe资源
        response.headers['Content-Security-Policy'] = f"frame-ancestors 'self' {URL_REMOTE}; frame-src 'self' https://{host};"
        return response
    

内容的提问来源于stack exchange,提问作者John Szatmari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 06:17:02