Bokeh 服务端核心组件详解:bokeh.server.server 中 BaseServer、Server 与 bind_sockets 的完整指南
2026/9/13 23:18:05 网站建设 项目流程

Bokeh 服务端核心组件详解:bokeh.server.server 中 BaseServer、Server 与 bind_sockets 的完整指南

【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh

本文围绕 Bokeh API 参考文档 server.rst 展开,系统讲解bokeh.server.server模块提供的两个公开类BaseServerServer以及辅助函数bind_sockets。读完后,你将掌握如何在 Python 中以编程方式(而非bokeh serve命令行)启动、配置和关停一个 Bokeh 服务器:包括监听地址/端口/Unix socket、多进程 worker、SSL 终结、WebSocket 来源白名单、会话(Session)查询,以及如何通过Server.from_settings让程序化创建的服务端遵循与bokeh serve相同的环境变量约定。

模块定位:bokeh.server.server 是什么

模块源码 的文档字符串说明了该模块的职责:提供基于 TornadoHTTPServerBokehTornado应用的服务端对象,用于承载 Bokeh Server 应用(Server Application)。模块内公开的对象在__all__中被明确限定为三个(server.py#L84-L88):

  • BaseServer:轻量级协调类。它不替你创建任何组件,而是要求你显式提供并协调好运行 Bokeh 服务器所需的三件套:一个 TornadoIOLoop、一个BokehTornado应用实例、一个已经绑定到该应用的 TornadoHTTPServer。适合你已经在自行管理事件循环、需要精细控制进程/循环结构的场景。
  • Server:高层便捷类。只需要传入 Bokeh 的Application实例(或应用路径到应用的映射、甚至一个可调用对象),它会自动创建并协调上述底层 Tornado 组件。它还提供了工厂方法Server.from_settings,可从 Bokeh 全局配置系统(bokeh.settings.settings)自动读取认证、SSL、会话签名、Cookie 等配置,与bokeh serve命令的环境变量约定保持一致。
  • bind_sockets:开发辅助函数,将 socket 绑定到指定端口,也兼容操作系统自动分配端口的情况(见下文)。

模块文档同时指向用户指南中的ug_server_introduction章节获取 Bokeh server 的整体背景信息。需要说明的边界是:Server这个高层类存在明确限制——不能同时设置显式的io_loopnum_procs > 1,若要在这两个维度上同时自由,必须改用BaseServer手动协调三个组件。

Server:一次讲全构造参数与全部可配置项

Server的构造函数签名为(server.py#L420-L421):

def __init__(self, applications, io_loop=None, http_server_kwargs=None, **kwargs)

applications:三种传入形态

applications参数接受三种形态,这是编写多应用服务器的关键:

  1. URL 路径到Application的映射dict[str, Application]):每个应用由一个对应 URL 的路径标识,如"/""/myapp"
  2. 单个Application:会被自动映射到根路径"/"
  3. 一个可调用对象:作为便捷方式,会用FunctionHandler为它创建一个Application

Application本质上是 Document 的工厂:每个 Session 会初始化一个全新的 Document。单元测试 test_server__server.py#L285-L297 中的test_server_applications_callable_arg直接验证了可调用对象形态:把modify_doc传给服务器后请求url,取回会话并断言session.document.title == "Hello, world!",并验证了{"/foo": modify_doc}映射形态下/foo路径的行为一致。

http_server_kwargs 与 io_loop

  • io_loop(可选):显式指定运行 Bokeh Server 代码的 TornadoIOLoop;为None时使用IOLoop.current()。源码中有一个值得注意的实现细节:IOLoop 只能在HTTPServer.start()调用之后再引用(代码注释指向 issue #5524,见 server.py#L530-L532)。
  • http_server_kwargs(可选):原样传递给tornado.httpserver.HTTPServer的额外参数,例如max_buffer_size用于限制最大上传体积。

_ServerOpts:全部服务端选项一览

其余关键字参数由私有的_ServerOpts(Options)类解析(server.py#L668),API 文档中的选项即来源于此。以下按源码逐项继承其说明,并补充默认值与实现事实:

参数类型/默认值说明(继承自源码 help 文本)
num_procsint,默认1HTTP 服务器启动的 worker 进程数。若同时配置了显式io_loop,则只能取 1;取 0 表示自动检测 CPU 核数。注意受 Tornado 限制,Windows 不支持num_procs > 1——官方建议在这种情况下运行多个 Bokeh server 实例放在负载均衡器之后。
addressstr \| None服务器监听 HTTP 请求的地址。
portint,默认DEFAULT_SERVER_PORT监听端口。该默认值来自全局配置:resources.py#L75 中DEFAULT_SERVER_PORT = settings.default_server_port(),而 settings.py#L817 显示其底层默认值为5006,且支持环境变量BOKEH_DEFAULT_SERVER_PORT覆盖。
unix_socketstr \| None要绑定的 Unix socket。与portaddress、SSL 选项等其他网络参数不兼容,且 Windows 上不可用。
prefixstr,默认""用于所有 Bokeh server 路径的 URL 前缀。测试test_prefix(test_server__server.py#L152-L158)验证了传prefix="foo"server.prefix == "/foo"(注意自动补了前导斜杠)。
indexstr \| None用于索引页/的 Jinja2 模板路径。
allow_websocket_originlist[str] \| None允许连接 WebSocket 的主机列表。当使用bokeh.embed.server_document等机制把 Bokeh 应用嵌入外部网站时通常需要设置;为None时默认使用"localhost"
use_xheadersbool,默认False是否让 Bokeh server 用X-Real-IpX-Forwarded-ForX-SchemeX-Forwarded-Proto请求头(若提供)覆盖所有请求的远端 IP 与 URI scheme/协议。这是放在反向代理后面部署的关键开关。
ssl_certfilestr \| NoneSSL 终结所用的证书文件路径。
ssl_keyfilestr \| NoneSSL 终结所用的私钥文件路径。
ssl_passwordstr \| None必要时用于解密 SSL keyfile 的密码。
websocket_max_message_sizeint,默认DEFAULT_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES设置 Tornado 的websocket_max_message_size值,该常量定义于 tornado.py。

构造期的参数校验(server.py#L466-L477)值得单独强调,因为它们直接抛RuntimeError

  • num_procs > 1且显式传了io_loop:报错提示改用BaseServer协调显式 IOLoop 与多进程 HTTPServer;
  • num_procs > 1且平台为 Windows:直接拒绝;
  • unix_socket非空且平台为 Windows:直接拒绝。

此外还有两条从源码结构看值得了解的隐含约束:使用unix_socket_address/_port会被置为None(server.py#L490-L493);而num_procs != 1时,源码会断言所有已注册应用都满足application_context.application.safe_to_fork,即用户应用代码不能在启动多进程之前执行过,否则视为不安全操作(server.py#L515-L518)。

一个可运行的完整示例

综合上述参数,一个典型的程序化服务器如下:

from bokeh.application import Application from bokeh.server.server import Server def modify_doc(doc): doc.title = "Hello, Bokeh Server" # 单应用 -> 自动映射到 "/"; 也可用 {"path": Application(...}} 多应用 app = Application(modify_doc) server = Server( app, address="0.0.0.0", port=5006, # 默认即 DEFAULT_SERVER_PORT (5006) allow_websocket_origin=["localhost:8000", "my-website.com"], use_xheaders=True, # 放在 Nginx 等反向代理后时开启 # ssl_certfile="cert.pem", # ssl_keyfile="key.pem", # num_procs=2, ) server.show("/") # 仅本地测试时使用,生产部署不应调用 server.run_until_shutdown()

Server.from_settings:与 bokeh serve 环境变量约定对齐的工厂方法

Server.from_settings(server.py#L536-L638)是类方法,作用为:未显式以关键字参数传入的配置,自动从全局settings模块补齐,从而使程序化创建的服务端与bokeh serve命令遵循相同的环境变量约定。其参数与默认来源如下:

参数未传入时的默认来源
applications/io_loop/http_server_kwargsServer.__init__
auth_providersettings.auth_module()指向的路径加载为AuthModule;未配置路径时使用NullAuth
secret_keysettings.secret_key_bytes()
sign_sessionssettings.sign_sessions()
ssl_certfile/ssl_keyfile/ssl_passwordsettings.ssl_certfile()/settings.ssl_keyfile()/settings.ssl_password()
cookie_secretsettings.cookie_secret()
xsrf_cookiessettings.xsrf_cookies()
ico_pathsettings.ico_path()(自定义 favicon.ico文件路径)

其余关键字参数原样转发给Server。该方法会抛ValueError的情况是:sign_sessions=True但拿不到secret_key(server.py#L623-L624)。

单元测试对该方法做了两条直接验证(test_server__server.py#L361-L405):

  • test__from_settings_uses_envvars:mock 掉settings后断言Server.from_settings(Application())确实把auth_providerAuthModule实例)、sign_sessionssecret_key、SSL 三件套、cookie_secretxsrf_cookies全部透传进了Server.__init__
  • test__from_settings_kwarg_overrides_envvar:显式传入的auth_provider=null_auth会覆盖环境变量级别的设置。

即优先级明确:显式关键字参数 > settings/环境变量

BaseServer:手动协调 IOLoop、BokehTornado 与 HTTPServer

当你需要把 Bokeh 服务器嵌入自己已有的事件循环(例如与其他 asyncio 服务共存、或在测试中精确控制循环生命周期)时,使用BaseServer。其构造函数(server.py#L123)要求三个参数:

  • io_loop:运行 Bokeh Tornado 应用的 TornadoIOLoop
  • tornado_appBokehTornado实例,即生成 Bokeh Document 与 Session 的服务端机制本体;
  • http_server:处理 HTTP 请求的 TornadoHTTPServer必须已经在使用tornado_app创建时就配置好

BaseServer的构造过程只做一件事:在这三个对象上调用self._tornado.initialize(io_loop)BokehTornado初始化到给定循环上。最小用法(与 test_server__server.py#L267-L283 的test_base_server完全一致):

from bokeh.application import Application from bokeh.server.server import BaseServer from bokeh.server.tornado import BokehTornado from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop loop = IOLoop() loop.make_current() app = BokehTornado(Application()) httpserver = HTTPServer(app) httpserver.start() server = BaseServer(loop, app, httpserver) server.start() # ... 你的代码 ... httpserver.stop() server.stop() loop.close()

生命周期方法逐个拆解

  • start():把 Bokeh Server 及其后台任务安装到IOLoop上。源码文档明确强调:此方法不阻塞、也不影响IOLoop的状态,循环的启停必须由你自己负责——它适用于你本就在显式管理IOLoop的情形。
  • stop(wait=True):停止并移除所有 Bokeh Server 的IOLoop回调,并停止其配置的HTTPServerwait控制是否等待有序清理。这里有一个从源码结构看很有意思的并发细节(server.py#L175-L214):同步调用无法阻塞服务器自身正在运行的事件循环,因此当检测到调用发生在服务器所运行循环的线程内时,清理会被调度为任务并立即返回,此时需要await wait_until_stopped()作为完成屏障;异步调用方则应直接使用stop_async()
  • stop_async():从异步代码中停止服务器并等待有序的应用清理。当你后续的工作依赖于所有会话与生命周期钩子都执行完毕时,应使用它而不是stop。与stop一样,只能调用一次。
  • wait_until_stopped():等待由stop在"同一事件循环"内调度的清理任务完成,即上面提到的完成屏障。
  • unlisten():停止监听端口,调用后服务器不再可用。文档注明该方法主要用于测试。
  • run_until_shutdown():在Server层最常用的"启动并常驻"方式。它会:若尚未start()则先启动;通过atexit.register安装进程退出钩子;在非 Windows 平台上为事件循环注册SIGTERM信号处理器(_sigterm会打印Received signal SIGTERM, shutting down并让loop.start()返回);然后调用self._loop.start()进入阻塞。捕获KeyboardInterrupt(即 Ctrl-C)后打印Interrupted, shutting down并执行self.stop()。也就是说,它同时响应 Ctrl-C 与 SIGTERM 两种关停方式(server.py#L250-L275)。

会话与运行信息访问

BaseServer还提供了一组查询属性/方法,常用于运维脚本或测试:

  • get_session(app_path, session_id):按应用路径与会话 ID 取一个活动会话,返回ServerSession
  • get_sessions(app_path=None):取所有应用当前活动的会话;给定app_path时只取该应用的。测试test_get_sessions(test_server__server.py#L168-L212)验证了完整行为:每发起一次 HTTP 请求,对应路径的会话数加一;get_sessions()不带参数时汇总全部应用;对不存在的app_path则抛ValueError
  • show(app_path, browser=None, new="tab"):在浏览器窗口或标签页中打开应用。app_path必须以/开头(否则抛ValueError);new可选"tab""window"browser可指定如"firefox"等(语义同标准库webbrowser)。源码文档明确提醒:此方法适合本地测试,生产部署中不应调用
  • port/address属性BaseServer的实现是从HTTPServer实际绑定的 socket 中读取真实监听值(server.py#L366-L384),而Server子类则直接返回构造期记录的_port/_address——在使用unix_socketServer.port/addressNone,并有专门的unix_socket属性。Server.port的返回值类型是int | None,这保证了bind_sockets在端口为 0(由 OS 自动分配)时仍能给出真实端口;
  • prefix/index属性:透传BokehTornado上配置的 URL 前缀与索引模板路径。

bind_sockets:端口绑定的开发辅助函数

bind_sockets(address, port)(server.py#L94-L103)实现很短,但行为上有两个要点:

def bind_sockets(address: str | None, port: int) -> tuple[list[socket.socket], int]: '''Bind sockets to one port, including when the OS selects the port.''' sockets = netutil.bind_sockets(port=port or 0, address=address) ... return sockets, actual_port
  1. 支持port=0让操作系统自动选择端口:绑定后会从所有 socket 的getsockname()中反解出真实端口(actual_port),保证所有 socket 落在同一端口上(否则断言失败);
  2. 显式端口时做一致性断言:如果调用方指定了端口,会断言实际端口必须与之一致。

Server.__init__在走 TCP/IP 路径时正是调用bind_sockets(opts.address, opts.port)完成绑定的(server.py#L499),这也是测试中常用port=0启动临时服务器而不产生端口冲突的原因(如test_server_applications_callable_arg)。

SSL 终结与 WebSocket 来源白名单的实现细节

SSL 终结:当ssl_certfile有值时,Server会在日志中记录Configuring for SSL termination,然后创建ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)load_cert_chain(certfile=..., keyfile=..., password=...),把上下文放入http_server_kwargs['ssl_options'](server.py#L483-L488)。单元测试test_ssl_args_plumbing(test_server__server.py#L251-L265)用 mock 验证了 cert/key/password 三个参数确实被原样传给load_cert_chain

WebSocket 来源白名单allow_websocket_origin会经create_hosts_allowlist(定义于 util.py)转换为允许的主机列表并作为extra_websocket_origins传给BokehTornado;非 Unix socket 场景下白名单中会带上实际端口号(server.py#L494-L502)。这解释了为什么嵌入外部站点时必须显式列出页面所在域名。

X-Forwarded 处理use_xheaders通过http_server_kwargs.setdefault('xheaders', ...)注入HTTPServer,测试test_use_xheaders断言server._http.xheaders is True(test_server__server.py#L246-L249)。

相关模块与延伸阅读

bokeh.server.server只是 Bokeh 服务端的入口层,从源码的导入与引用关系看,可以沿以下路径继续深入(均为仓库内相对路径):

  • src/bokeh/server/tornado.py:BokehTornado应用本体,负责路由、Document/Session 生成与websocket_max_message_size等机制;
  • src/bokeh/server/session.py:ServerSessionget_session(s)的返回类型;
  • src/bokeh/server/auth_provider.py:NullAuthAuthModuleAuthProviderfrom_settings认证参数的类型来源;
  • src/bokeh/command/subcommands/serve.py:bokeh serve命令行子命令,其选项与_ServerOpts的对应关系;
  • 对应 API 参考页:server.rst 由 Sphinxautomodule指令渲染本模块全部成员文档,本文对其内容逐项继承并结合源码扩充;同目录还有 tornado.rst、session.rst 等参考页。

小结

bokeh.server.server用两个层次清晰的类覆盖了 Bokeh 服务端的两种使用姿势:Server面向绝大多数场景——传一个Application(或路径映射、可调用对象)加上一组_ServerOpts选项(端口默认 5006、num_procs多进程、SSL、WebSocket 白名单、URL 前缀等),必要时用from_settings对齐bokeh serve的环境变量约定,最后run_until_shutdown()常驻并以 Ctrl-C/SIGTERM 优雅退出;BaseServer则面向需要自管IOLoop的嵌入场景,配合start/stop/stop_async/wait_until_stopped/unlisten提供精确的生命周期控制。约束(Windows 不支持多进程、unix_socket与网络参数互斥、sign_sessions必须有secret_key)在源码中均以显式异常表达,测试套件 tests/unit/bokeh/server/test_server__server.py 对其中大部分行为提供了可直接复现的验证用例。

【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询