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

如何通过Bokeh Server配置Tornado解决OpenShift部署的CORS跨域问题

问题描述
  • 部署环境:OpenShift/K8s
  • 应用架构:Flask服务运行在0.0.0.0:8080,将请求转发至0.0.0.0:5006的Bokeh Server
  • 现象:后台运行正常,但浏览器无法显示Bokeh组件,控制台抛出CORS相关错误
  • 具体报错:

Access to XMLHttpRequest at 'http://0.0.0.0:5006/bkapp/autoload.js?bokeh-autoload-element=de42b2ca-6eb9-40fc-97d8-6b5b52f88d33&bokeh-app-path=/bkapp&bokeh-absolute-url=http://0.0.0.0:5006/bkapp' from origin '' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

应用代码

from threading import Thread
import panel as pn
from flask import Flask, render_template
from tornado.ioloop import IOLoop
from bokeh.embed import server_document
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
from bokeh.server.server import Server
from bokeh.themes import Theme
from bokeh.io import show
from bokeh.models import Button, CustomJS
app = Flask(__name__)


def bkapp(doc):
    doc.add_root(Button(label="Foo", button_type="success"))
    doc.theme = Theme(filename="theme.yaml")


@app.route('/', methods=['GET', 'OPTIONS', 'HEAD'])
def bkapp_page():
    script = server_document('http://0.0.0.0:5006/bkapp')
    return render_template("embed.html", script=script, template="Flask")


def bk_worker():
    # Can't pass num_procs > 1 in this configuration. If you need to run multiple
    # processes, see e.g. flask_gunicorn_embed.py
    server = Server({'/bkapp': bkapp}, io_loop=IOLoop(), allow_websocket_origin=["*"])
    server.start()
    server.io_loop.start()

Thread(target=bk_worker).start()

if __name__ == '__main__':
    print('Opening single process Flask app with embedded Bokeh application on http://0.0.0.0:8080/')
    print()
    print('Multiple connections may block the Bokeh app in this configuration!')
    print('See "flask_gunicorn_embed.py" for one way to run multi-process')
    app.run(host='0.0.0.0', port=8080)

嵌入页面模板(templates/embed.html)

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Embedding a Bokeh Server With {{ framework }}</title>
</head>

<body>
  <div>
    This Bokeh app below served by a Bokeh server that has been embedded
    in another web app framework. For more information see the section
    <a  target="_blank" href="https://docs.bokeh.org/en/latest/docs/user_guide/server.html#embedding-bokeh-server-as-a-library">Embedding Bokeh Server as a Library</a>
    in the User's Guide.
  </div>
  {{ script|safe }}
</body>
</html>
解决方案

方案一:直接修改Bokeh Server的Tornado配置添加CORS头

通过自定义Tornado的RequestHandler,为Bokeh Server的所有HTTP请求注入CORS响应头:

  1. 定义CORS处理基类
from tornado.web import RequestHandler

class CORSHandler(RequestHandler):
    def prepare(self):
        # 允许所有来源,可替换为OpenShift应用的具体外部域名
        self.set_header("Access-Control-Allow-Origin", "*")
        # 允许的请求方法
        self.set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD")
        # 允许的请求头
        self.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
        # 预检请求缓存时长
        self.set_header("Access-Control-Max-Age", 86400)
        
        # 处理OPTIONS预检请求,直接返回200
        if self.request.method == "OPTIONS":
            self.finish()
  1. 修改Bokeh Server初始化逻辑
    更新bk_worker函数,替换默认的请求处理器为带CORS的版本:
def bk_worker():
    from bokeh.server.views import autoload_js, static_handler
    
    # 替换Bokeh核心路由的处理器
    extra_patterns = [
        (r"/bkapp/autoload.js", CORSHandler, dict(handler=autoload_js)),
        (r"/bkapp/static/(.*)", CORSHandler, dict(handler=static_handler)),
    ]
    
    server = Server(
        {'/bkapp': bkapp}, 
        io_loop=IOLoop(), 
        allow_websocket_origin=["*"],
        extra_patterns=extra_patterns
    )
    
    # 给所有注册的Tornado处理器添加CORS头逻辑
    for handler in server._tornado_app.handlers[0][1]:
        original_prepare = handler.prepare
        def new_prepare(self):
            CORSHandler.prepare(self)
            original_prepare(self)
        handler.prepare = new_prepare
    
    server.start()
    server.io_loop.start()

方案二:通过Flask反向代理避免跨域

浏览器直接请求Flask服务,由Flask转发请求到Bokeh Server,彻底规避跨域问题:

  1. 修改Flask路由中的server_document地址
@app.route('/', methods=['GET', 'OPTIONS', 'HEAD'])
def bkapp_page():
    # 使用Flask代理的路径,而非Bokeh的直接地址
    script = server_document('/bkapp_proxy')
    return render_template("embed.html", script=script, template="Flask")
  1. 添加Flask代理路由
from flask import request
import requests

@app.route('/bkapp_proxy/<path:path>')
def bkapp_proxy(path):
    # 容器内部访问Bokeh Server的地址
    bokeh_url = f'http://localhost:5006/bkapp/{path}'
    resp = requests.request(
        method=request.method,
        url=bokeh_url,
        headers={k: v for k, v in request.headers.items() if k != 'Host'},
        data=request.get_data(),
        cookies=request.cookies,
        allow_redirects=False
    )
    
    # 过滤掉不需要转发的响应头
    excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
    headers = [(k, v) for k, v in resp.raw.headers.items() if k.lower() not in excluded_headers]
    
    return app.make_response((resp.content, resp.status_code, headers))

关键注意点

  • 不要在server_document中使用0.0.0.0:5006,这是容器内部地址,浏览器无法直接访问,需替换为外部可访问的域名或代理路径
  • allow_websocket_origin设置为["*"]仅用于测试,生产环境建议指定具体的域名

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:47:04