部署场景#

要使您的应用程序成为用户友好的服务,您必须部署您的工作。本小节探讨部署的各个方面。

独立 Bokeh 服务器#

您可以让 Bokeh 服务器在网络上运行,供用户直接与您的应用程序交互。对于本地网络部署,这可能是一个简单的解决方案,前提是运行服务器的硬件功能与您的应用程序要求和预期的用户数量相匹配。

但是,如果您有身份验证、扩展或正常运行时间要求,您将必须考虑更复杂的部署配置。

将 Bokeh 服务器集成到其他 Web 服务中#

Bokeh 服务器经常用于创建嵌入在较大的父应用程序中的绘图和仪表板。例如,在金融环境中,这可能涉及将 Bokeh 支持的趋势线合并到基于 Web 的交易平台中,这些趋势线绘制了随时间变化的账户余额。在供应链环境中,可以将 Bokeh 视图集成到现有的库存管理系统中,以交互方式监控商店的商品供应。

为了满足此用例,bokeh.embed 模块提供了 server_document()server_session() 方法。有关其用法的详细讨论和示例,请参阅 Bokeh 应用程序。简而言之,这些方法返回 HTML 脚本标记的文本,该标记从 Bokeh 服务器加载视图,并将视图添加到放置脚本标记的任何页面的 DOM 中。

如果您要集成的父服务不是基于 Python 的,您仍然可以通过 server_document / server_session 方法与 Bokeh 集成。但是,您需要通过调用一个小的、长期运行的 Python 脚本来执行此操作,该脚本通过任何标准形式的 IPC 返回这些方法的文本内容。

为了允许您的父应用程序显示嵌入式 Bokeh 视图,您必须配置父应用程序以允许跨域请求到您的 Bokeh 服务器实例。这可以通过将您的 Bokeh 服务器的公共主机名和端口号添加到父服务的 内容安全策略 (CSP)script-srcconnect-src 指令中来实现,适用于 HTTP(S) 和 WS(S) 协议。配置 CSP 的确切过程将取决于构建父服务所用的工具包或框架,因此请参考该软件的特定文档。作为最后的手段,CSP 标头也可以被反向代理覆盖。

警告

不要在通过 HTTPS 运行父服务时通过 HTTP 运行 Bokeh 服务器,反之亦然。Bokeh 的加载器代码通过 window.location.protocol 确定在客户端加载资源的协议,因此如果父服务的协议与您的 Bokeh 服务器实例的协议不匹配,则加载器脚本对 Bokeh 服务器的请求将失败。

如果您的父应用程序是基于 Python 的,并且您不介意将您的 Bokeh 服务器应用程序紧密集成到父应用程序的代码库中,Bokeh 还支持通过父应用程序启动的线程运行其底层的 Tornado Web 服务器。实用示例链接在 Bokeh 服务器 API 下。此方法仍然需要使用 server_documentserver_session,但可以简化 CSP 配置以及 Bokeh 服务器应用程序的部署。

SSH 隧道#

要在具有受限访问权限的主机上运行 Bokeh 服务器的独立实例,请使用 SSH “隧道” 连接到服务器。

在最简单的情况下,用户从另一个位置(例如没有中间机器的笔记本电脑)访问 Bokeh 服务器。

远程主机上像往常一样运行服务器。

bokeh serve

接下来,在本地机器上发出以下命令,以建立到远程主机的 SSH 隧道

ssh -NfL localhost:5006:localhost:5006 user@remote.host

user 替换为远程主机上的用户名,将 remote.host 替换为托管 Bokeh 服务器的系统的主机名或 IP 地址。远程系统可能会提示您输入登录凭据。连接后,您将能够导航到 localhost:5006,就像 Bokeh 服务器在本地机器上运行一样。

稍微复杂一点的情况涉及服务器和本地机器之间的网关。在这种情况下,必须从服务器到网关建立反向隧道,并使用另一个隧道连接网关和本地机器。

在将要运行 Bokeh 服务器的远程主机上发出以下命令

nohup bokeh server &
ssh -NfR 5006:localhost:5006 user@gateway.host

user 替换为网关上的用户名,将 gateway.host 替换为网关的主机名或 IP 地址。网关可能会提示您输入登录凭据。

要设置本地机器和网关之间的隧道,请在本地机器上运行以下命令

ssh -NfL localhost:5006:localhost:5006 user@gateway.host

再次,将 user 替换为网关上的用户名,将 gateway.host 替换为网关的主机名或 IP 地址。

您现在应该能够通过导航到 localhost:5006 从本地机器访问 Bokeh 服务器。您甚至可以从本地机器上运行的 Jupyter 笔记本电脑设置客户端连接。

注意

我们计划扩展本节,提供有关其他工具和配置的更多指导。如果您有其他 Web 部署场景的经验,并希望在此处贡献您的知识,请在 https://discourse.bokeh.org 上与我们联系

SSL 终止#

您可以配置 Bokeh 服务器以终止 SSL 连接并直接提供安全的 HTTPS 和 WSS 会话。为此,您必须提供 --ssl-certfile 参数,其值为包含证书以及建立证书真实性所需的任意数量的 CA 证书 的单个 PEM 文件的路径。

bokeh serve --ssl-certfile /path/to/cert.pem

您还可以通过设置环境变量 BOKEH_SSL_CERTFILE 来提供证书文件的路径。

如果私钥单独存储,您可以通过设置 --ssl-keyfile 命令行参数或设置 BOKEH_SSL_KEYFILE 环境变量来提供其位置。如果私钥需要密码,请通过设置 BOKEH_SSL_PASSWORD 环境变量来提供密码。

或者,您可能希望在代理后面运行 Bokeh 服务器,并让代理终止 SSL 连接。有关详细信息,请参阅下一小节。

基本反向代理设置#

要向公共互联网提供 Web 应用程序,您可能希望将您的应用程序托管在内部网络上,并通过一些专用 HTTP 服务器代理连接到它。本小节提供有关如何配置一些常见的反向代理的指导。

Nginx#

Nginx 是一种非常常见的 HTTP 和反向代理服务器。以下是 server 配置节的示例

server {
    listen 80 default_server;
    server_name _;

    access_log  /tmp/bokeh.access.log;
    error_log   /tmp/bokeh.error.log debug;

    location / {
        proxy_pass http://127.0.0.1:5100;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
    }

}

上面的 server 代码块设置 Nginx 以将端口 80 上到 127.0.0.1 的传入连接代理到端口 5100 上到 127.0.0.1。为了在此配置中工作,您需要使用一些命令行选项来配置 Bokeh 服务器。特别是,使用 --port 让 Bokeh 服务器监听端口 5100。

bokeh serve myapp.py --port 5100

上面的基本服务器代码块未配置任何特殊处理静态资源(例如 Bokeh JS 和 CSS 文件)。这意味着 Bokeh 服务器直接提供这些文件。

虽然这是一个可行的选项,但它要求 Bokeh 服务器执行额外的工作,而这些工作最好由 Nginx 处理。要使用 Nginx 提供静态资产,请将以下子代码块添加到上面的代码中,并将您的静态资产路径替换为 /path/to/bokeh/server/static

location /static {
    alias /path/to/bokeh/server/static;
}

确保运行 Nginx 的帐户具有访问 Bokeh 资源的权限。或者,您可以在部署期间将资源复制到全局静态目录。

为了跨进程传递 Cookie 和标头,Bokeh 可能会将此信息包含在 JSON Web 令牌中,并通过 WebSocket 发送。在某些情况下,此令牌可能会变得非常大,导致 Nginx 丢弃请求。您可能必须通过覆盖默认的 Nginx 设置 large_client_header_buffers 来解决此问题

large_client_header_buffers 4 24k;

Apache#

另一个常见的 HTTP 服务器和代理是 Apache。以下是在 Apache 后运行的 Bokeh 服务器的配置示例

<VirtualHost *:80>
    ServerName localhost

    CustomLog "/path/to/logs/access_log" combined
    ErrorLog "/path/to/logs/error_log"

    ProxyPreserveHost On
    ProxyPass /myapp/ws ws://127.0.0.1:5100/myapp/ws
    ProxyPassReverse /myapp/ws ws://127.0.0.1:5100/myapp/ws

    ProxyPass /myapp http://127.0.0.1:5100/myapp
    ProxyPassReverse /myapp http://127.0.0.1:5100/myapp

    <Directory />
        Require all granted
        Options -Indexes
    </Directory>

    Alias /static /path/to/bokeh/server/static
    <Directory /path/to/bokeh/server/static>
        # directives to effect the static directory
        Options +Indexes
    </Directory>

</VirtualHost>

上面的配置将 /static 别名为 Bokeh 静态资源目录的位置。但是,也可以(可能更可取)将静态资源复制到您为 Apache 配置的静态文件的任何标准位置,作为部署的一部分。

您可能还需要为上述配置启用一些模块

a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel
apache2ctl restart

根据您的系统,您可能必须使用 sudo 运行上述命令。

与之前一样,使用以下命令运行 Bokeh 服务器

bokeh serve myapp.py --port 5100

带有代理的 Unix 套接字#

在某些情况下,您可能希望使用 Unix 套接字而不是 WebSocket 将代理的 Bokeh 服务器连接到代理。您可以将 Bokeh 服务器绑定到 Unix 套接字,并使用 Nginx 或 Apache 代理到 Unix 域套接字。

注意

Windows 不支持绑定到 Unix 套接字。

bokeh serve --unix-socket /path/to/socket.sock

Nginx 配置可能如下例所示

upstream myserver {
    server unix:/path/to/socket.sock;
}

server {
    listen 80 default_server;
    server_name _;

    access_log  /tmp/bokeh.access.log;
    error_log   /tmp/bokeh.error.log debug;

    location / {
        proxy_pass http://myserver;
    }

}

请注意,Bokeh 服务器网络选项(例如 WebSocket 来源和 SSL 配置)与 Unix 套接字不兼容。由代理在前端强制执行这些限制。

如果有多个用户共享主机,您可以限制套接字上的文件权限以限制对代理服务器的访问。

使用 Nginx 和 SSL 的反向代理#

要在 SSL 终止的 Nginx 代理后部署 Bokeh 服务器,您需要进行一些额外的自定义。特别是,您必须使用 --use-xheaders 标志配置 Bokeh 服务器。

bokeh serve myapp.py --port 5100 --use-xheaders

--use-xheaders 标志使 Bokeh 在 X-Real-IpX-Forwarded-ForX-SchemeX-Forwarded-Proto 标头可用时,覆盖所有请求的远程 IP 和 URI 方案/协议。

您还需要自定义 Nginx。特别是,您必须配置 Nginx 以发送 X-Forwarded-Proto 标头并使用 SSL 终止。可选地,您可能希望将所有 HTTP 流量重定向到 HTTPS。

此配置的完整详细信息(例如如何以及在何处安装 SSL 证书和密钥)因平台而异,以下仅为参考 nginx.conf 设置

# redirect HTTP traffic to HTTPS (optional)
server {
    listen      80;
    server_name foo.com;
    return      301 https://$server_name$request_uri;
}

server {
    listen      443 default_server;
    server_name foo.com;

    # adds Strict-Transport-Security to prevent man-in-the-middle attacks
    add_header Strict-Transport-Security "max-age=31536000";

    ssl on;

    # SSL installation details vary by platform
    ssl_certificate /etc/ssl/certs/my-ssl-bundle.crt;
    ssl_certificate_key /etc/ssl/private/my_ssl.key;

    # enables all versions of TLS, but not the deprecated SSLv2 or v3
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # disables all weak ciphers
    ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://127.0.0.1:5100;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
    }

}

此配置会将所有传入的 HTTPS 连接代理到 foo.com,并将其转发到内部在 http://127.0.0.1:5100 上运行的 Bokeh 服务器。

负载均衡#

Bokeh 服务器在设计上是可扩展的。如果您需要更多容量,您可以简单地运行其他服务器。在这种情况下,您通常希望在负载均衡器后面运行所有 Bokeh 服务器实例,以便在新连接在各个服务器之间分配。

../../../_images/bokeh_serve_scale.svg

Bokeh 服务器是水平可扩展的。要增加容量,您可以在负载均衡器后面运行更多服务器。#

您可以根据需要运行任意数量的 Bokeh 服务器。以下示例基于在三个不同端口上运行的三个 Bokeh 服务器的设置

bokeh serve myapp.py --port 5100
bokeh serve myapp.py --port 5101
bokeh serve myapp.py --port 5102

以下各节提出了基于此设置的基本配置。有关更多详细信息,请参阅 Nginx 负载均衡器文档Apache 代理均衡器模块文档。例如,有不同的策略可用于定义如何在服务器实例之间分配传入连接。

Nginx#

首先,您需要在 Nginx 配置中添加一个 upstream 节。这通常位于 server 节之上,看起来像这样

upstream myapp {
    least_conn;            # Use the least-connected strategy
    server 127.0.0.1:5100;
    server 127.0.0.1:5101;
    server 127.0.0.1:5102;
}

配置的其余部分使用名称 myapp 来引用上面的 upstream 节,该节列出了三个 Bokeh 服务器实例的内部连接信息。

接下来,在 Bokeh 服务器的 location 节中,更改 proxy_pass 值以引用上面的 upstream 节。以下代码使用 proxy_pass http://myapp;

server {

    location / {
        proxy_pass http://myapp;

        # all other settings unchanged
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
    }

}

Apache#

首先,确保您已启用 proxy_balancerrewrite 模块。

为 HTTP 和 WebSocket 协议添加均衡器

<Proxy "balancer://myapp_http">
    BalancerMember "http://127.0.0.1:5100/myapp"
    BalancerMember "http://127.0.0.1:5101/myapp"
    BalancerMember "http://127.0.0.1:5102/myapp"
    ProxySet lbmethod=bybusyness
</Proxy>

<Proxy "balancer://myapp_ws">
    BalancerMember "ws://127.0.0.1:5100/myapp"
    BalancerMember "ws://127.0.0.1:5101/myapp"
    BalancerMember "ws://127.0.0.1:5102/myapp"
    ProxySet lbmethod=bybusyness
</Proxy>

bybusyness 负载均衡方法确保传入连接被分配给当时活动连接最少的实例。它应该比 其他可用算法(例如 byrequests)产生更好的结果。您可能必须启用 mod_lbmethod_bybusyness

最后,您可以将 WebSocket 和 HTTP 请求代理到相应的均衡器

RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /myapp(.*)    balancer://myapp_ws$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /myapp(.*)    balancer://myapp_http$1 [P,L]

身份验证#

Bokeh 服务器本身没有任何用于身份验证或授权的功能。但是,您可以为 Bokeh 服务器配置一个“身份验证提供程序”,该提供程序连接到 Tornado 的底层功能。有关背景信息,请参阅 Tornado 文档中的 身份验证和安全性。本节的其余部分假定您熟悉该材料。

身份验证模块#

您可以配置 Bokeh 服务器以仅允许经过身份验证的用户连接。为此,请在命令行上提供实现必要模块的路径。

bokeh serve --auth-module=/path/to/auth.py

或者,您可以将 BOKEH_AUTH_MODULE 环境变量设置为此路径。

该模块必须包含以下两个函数之一,这些函数返回当前用户(或 None

def get_user(request_handler):
    pass

async def get_user_async(request_handler):
    pass

该模块将函数传递给 Tornado RequestHandler,后者可以检查 Cookie 或请求标头以确定经过身份验证的用户。如果没有经过身份验证的用户,这些函数应返回 None

此外,该模块必须通过包含以下任一项来指定将未经验证的用户重定向到何处

  • 模块属性 login_url 和(可选)LoginHandler

  • 用于 get_login_url 的函数定义

login_url = "..."

class LoginHandler(RequestHandler):
    pass

def get_login_url(request_handler):
    pass

如果模块提供相对 login_url,它还可以提供可选的 LoginHandler 类,Bokeh 服务器将自动合并该类。

get_login_url 函数在登录 URL 必须根据请求、Cookie 或其他因素而变化的情况下很有用。您还可以在定义 get_url_function 时指定 LoginHandler

要定义用于注销用户的端点,您还可以使用可选的 logout_urlLogoutHandler 参数,类似于登录选项。

如果您不提供身份验证模块,则配置将不需要任何身份验证即可访问 Bokeh 服务器端点。

警告

配置执行身份验证模块的内容。

安全 Cookie#

如果您想在您的身份验证模块中使用 Tornado 的 set_secure_cookieget_secure_cookie 函数,您必须设置 Cookie 密钥。为此,请使用 BOKEH_COOKIE_SECRET 环境变量。

export BOKEH_COOKIE_SECRET=<cookie secret value>

该值应为长而随机的字节序列。

安全性#

默认情况下,Bokeh 服务器将接受任何传入的连接,只要这些连接来自允许的 WebSocket 来源。如果您指定会话 ID,并且服务器上已存在具有该 ID 的会话,则服务器将连接到该会话。否则,服务器将自动创建和使用新会话。

如果您在大型组织或更广泛的互联网中部署嵌入式 Bokeh 应用程序,您可能希望限制谁可以启动会话以及从何处启动会话。Bokeh 允许您管理会话创建权限。

WebSocket 来源#

当 Bokeh 服务器收到 HTTP 请求时,它会立即返回一个脚本,该脚本启动 WebSocket 连接。所有后续通信都通过 WebSocket 进行。

为了降低跨站点滥用的风险,Bokeh 服务器将仅从显式允许的来源启动 WebSocket 连接。具有不在允许列表中的 Origin 标头的请求将生成 HTTP 403 错误响应。

默认情况下,仅允许 localhost:5006,这使得以下两个调用相同

bokeh serve --show myapp.py

bokeh serve --show --allow-websocket-origin=localhost:5006 myapp.py

这两者都会将您的默认浏览器打开到默认应用程序 URL localhost:5006,并且由于 localhost:5006 在允许的 WebSocket 来源列表中,因此 Bokeh 服务器会创建并显示新会话。

当您使用 server_document()server_session() 将 Bokeh 服务器嵌入到另一个网页中时,对 Bokeh 服务器的请求的 Origin 标头是托管您的 Bokeh 内容的页面的 URL。

例如,如果用户导航到您的页面 https://acme.com/products,浏览器报告的来源标头将为 acme.com。在这种情况下,您通常会将 Bokeh 服务器限制为接受来自 acme.com 页面的请求,从而防止其他页面在您不知情的情况下嵌入您的 Bokeh 应用程序。

您可以通过按如下方式设置 --allow-websocket-origin 命令行参数来执行此操作

bokeh serve --show --allow-websocket-origin=acme.com myapp.py

这将阻止其他站点在其页面中嵌入您的 Bokeh 应用程序,因为来自查看这些页面的用户的请求将报告与 acme.com 不同的来源,从而导致 Bokeh 服务器拒绝它们。

警告

请记住,这仅阻止其他网页在您不知情的情况下嵌入您的 Bokeh 应用程序。

如果您需要多个允许的来源,您可以在命令行上传递多个 --allow-websocket-origin 实例。

您还可以配置 Bokeh 服务器以允许所有连接,而与来源无关

bokeh serve --show --allow-websocket-origin='*' myapp.py

此选项仅适用于测试、实验和本地笔记本电脑使用。

签名会话 ID#

默认情况下,即使您不提供会话 ID,Bokeh 服务器也会为来自允许的 WebSocket 来源的所有新请求自动创建新会话。

在另一个 Web 应用程序(例如 Flask 或 Django)中嵌入 Bokeh 应用程序时,请确保只有您的 Web 应用程序能够生成对 Bokeh 服务器的可行请求,您可以将其配置为仅创建具有加密签名会话 ID 的会话。

首先,使用 bokeh secret 命令创建一个密钥以对会话 ID 进行签名。

export BOKEH_SECRET_KEY=`bokeh secret`

然后,在启动 Bokeh 服务器时,将 BOKEH_SIGN_SESSIONS 设置为 yes。您通常还需要在此处设置允许的 WebSocket 来源。

BOKEH_SIGN_SESSIONS=yes bokeh serve --allow-websocket-origin=acme.com myapp.py

然后,在您的 Web 应用程序中,使用 generate_session_id 显式提供签名会话 ID

from bokeh.util.token import generate_session_id

script = server_session(url='https://127.0.0.1:5006/bkapp',
                        session_id=generate_session_id())
return render_template("embed.html", script=script, template="Flask")

确保为 Bokeh 服务器和 Web 应用程序进程(例如 Flask、Django 或您正在使用的任何其他工具)设置相同的 BOKEH_SECRET_KEY 环境变量。

注意

签名会话 ID 充当访问令牌。与任何令牌系统一样,安全性取决于保持令牌机密。您还应该在终止 SSL 连接的代理后面运行 Bokeh 服务器,或配置 Bokeh 服务器以直接终止 SSL。这使您可以安全地将会话 ID 传输到客户端浏览器。

XSRF Cookie#

Bokeh 服务器可以使用 Tornado 的跨站点请求伪造保护。要启用此功能,请使用 --enable-xsrf-cookies 选项或将环境变量 BOKEH_XSRF_COOKIES 设置为 yes

使用此设置,您必须正确地检测自定义处理程序和登录处理程序上的所有 PUT、POST 和 DELETE 操作,以使其能够正常运行。通常,这意味着将以下代码添加到所有 HTML 表单提交模板

{% module xsrf_form_html() %}

有关完整详细信息,请参阅有关 XSRF Cookie 的 Tornado 文档。

扩展服务器#

您可以使用 num-procs 选项派生多个服务器进程。例如,运行以下命令以派生 3 个进程

bokeh serve --num-procs 3

请注意,派生操作发生在底层的 Tornado 服务器中。有关更多信息,请参阅 Tornado 文档