ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Bokeh Server 的 HTTP 视图层:bokeh.server.views 七类请求处理器深度解析

Bokeh Server 的 HTTP 视图层:bokeh.server.views 七类请求处理器深度解析 Bokeh Server 的 HTTP 视图层bokeh.server.views 七类请求处理器深度解析【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh本文基于 Bokeh 仓库的 API 参考页 views.rst 展开系统讲解bokeh.server.views包中 7 个请求处理器模块的职责、路由映射与核心实现机制。读完本文你将理解bokeh serve启动的 Tornado 应用如何把根路径、文档页、/ws、/metadata、/autoload.js与静态资源等请求分发到对应 Handler并能结合源码定位鉴权、会话创建、WebSocket 令牌校验等关键链路。一、视图层总览路由如何注册到 Bokeh Serverbokeh.server.views包为 Bokeh ServerTornado 应用提供全部 HTTP/WebSocket 请求处理器。官方参考页 views.rst 以automodule指令逐模块列出了 7 个成员参考页小节源码模块导出类auth_request_handlerauth_request_handler.pyAuthRequestHandlerautoload_js_handlerautoload_js_handler.pyAutoloadJsHandlerdoc_handlerdoc_handler.pyDocHandlerautoload_metadata_handlermetadata_handler.pyMetadataHandlerroot_handlerroot_handler.pyRootHandlersession_handlersession_handler.pySessionHandlerstatic_handlerstatic_handler.pyAsyncStaticFileHandler、StaticHandlerwsws.pyWSHandler注意参考页中第四节标题写作 “autoload_metadata_handler”但其automodule指向的是bokeh.server.views.metadata_handler即实际的 metadata_handler.py 模块——阅读文档时可按后者定位源码。这些 Handler 与 URL 的绑定集中在 urls.py 中定义并说明由bokeh.server.tornado中的BokehTornado应用负责注册。路由分两层顶层路由toplevel_patterns与应用无关统一加上配置的prefix前缀toplevel_patterns: URLRoutes [ (r/?, RootHandler), # prefix/ (r/static/extensions/(.*), MultiRootStaticHandler, dict(rootextension_dirs)), (r/static/(.*), StaticHandler), # prefix/static/ ]每应用路由per_app_patterns叠加应用路径与prefix前缀per_app_patterns: URLRoutes [ (r/?, DocHandler), # prefix/app/ (r/ws, WSHandler), # prefix/app/ws (r/metadata, MetadataHandler), # prefix/app/metadata (r/autoload.js, AutoloadJsHandler), # prefix/app/autoload.js ]由此得到一张完整的端点对照表请求路径处理器作用prefix/RootHandler列出所有应用或在单应用时重定向prefix/static/(.*)StaticHandler提供 BokehJS 的 JS/CSS 静态资源prefix/static/extensions/(.*)MultiRootStaticHandler从多个扩展目录提供静态文件prefix/app/DocHandler渲染文档展示页HTMLprefix/app/wsWSHandler服务端 WebSocket 通道prefix/app/metadataMetadataHandler返回应用元数据 JSONprefix/app/autoload.jsAutoloadJsHandler返回服务端 autoload 的 JS 片段除参考页列出的 7 个模块外views包中还有 multi_root_static_handler.py多根目录静态文件等辅助模块它们与上述 Handler 共同构成完整的视图层。二、AuthRequestHandler统一授权钩子的基类AuthRequestHandler 继承自 Tornado 的RequestHandler把 Tornado 的三个标准授权钩子统一委托给配置在 Bokeh Tornado 应用上的AuthProviderget_login_url()L65-L79按优先级取登录地址——若已缓存self._bokeh_login_url则直接返回否则调用auth_provider.get_login_url(self)再否则把auth_provider.login_url与prefix /做urljoin拼接源码注释特别强调第二个参数必须lstrip(/)否则urljoin会把带前导斜杠的第二参数当作绝对路径而丢弃前缀两者都没有时抛出RuntimeError(login_url or get_login_url() must be supplied when authentication hooks are enabled)。get_current_user()L81-L89同步钩子委托给auth_provider.get_user(self)未配置时回退为字符串default_user这解释了为何默认无鉴权部署下每个请求都有“当前用户”。prepare()L91-L108异步版本。优先使用auth_provider.get_user_async若只有同步get_user则通过_run_in_executor放入线程池执行并且对OPTIONS预检请求直接跳过若最终current_user为空、请求方法是GET/HEAD且配置了get_login_url则解析出登录 URL 缓存到_bokeh_login_url供后续get_login_url()返回 302 重定向。从源码结构看所有需要鉴权的业务 Handler下文逐一介绍都通过继承AuthRequestHandler并在处理方法上叠加 Tornado 的authenticated装饰器来获得“未登录即跳转登录页”的行为。三、SessionHandler会话解析的抽象基类SessionHandler 是文档页与 autoload.js 等“需要会话”的端点的公共父类。它在__init__中接收两个关键关键字参数application_context该应用对应的ApplicationContext持有应用实例与会话集合bokeh_websocket_path用于告知前端 WebSocket 的绝对路径。核心方法是带authenticated装饰的get_session()L78-L84authenticated async def get_session(self) - ServerSession | None: try: request cast(RequestLike, self.request) return await self.application.create_session(self.application_context, request) except SessionError as error: raise HTTPError(status_codeerror.status, reasonerror.reason)它把“根据请求中的 token/session ID 解析必要时创建会话”的职责下沉到BokehTornado.create_session当出现SessionError时转换为对应状态码的HTTPError。从源码结构看create_session返回None表示 token 非法或会话不存在子类 Handler 会进一步把它翻译成 403。四、DocHandler文档展示页 / /DocHandler 继承SessionHandler处理每个应用的根路径请求负责渲染出浏览器里看到的文档页面authenticated async def get(self, *args: Any, **kwargs: Any) - None: session await self.get_session() if session is None: raise HTTPError(status_code403, reasonInvalid token or session ID) page server_html_page_for_session( session, resourcesself.application.resources(), titlesession.document.title, templatesession.document.template, template_variablessession.document.template_variables) self.set_header(Content-Type, text/html) self.write(page)实现要点会话解析失败get_session()返回None直接返回403 “Invalid token or session ID”页面 HTML 由bokeh.embed.server.server_html_page_for_session生成导入见 doc_handler.py L33标题、Jinja 模板与模板变量均取自session.document即服务端Document对象——这意味着在应用代码中修改doc.title、doc.template会直接反映到下次请求的页面资源BokehJS 的 JS/CSS通过self.application.resources()由 Tornado 应用注入与 urls.py 中StaticHandler提供/static/的静态文件相呼应。五、AutoloadJsHandler把应用“嵌入”外部页面的 /autoload.jsAutoloadJsHandler 处理prefix/app/autoload.js配合server_document等嵌入 API把服务端应用加载进第三方页面。它是视图层中 CORS 逻辑最复杂的 Handler默认 CORS 头L67-L70def set_default_headers(self) - None: self.set_header(Access-Control-Allow-Origin, *) self.set_header(Access-Control-Allow-Headers, *) self.set_header(Access-Control-Allow-Credentials, true)可信源镜像_allow_websocket_originL72-L87读取请求头Origin取其 host 与允许列表比对允许列表优先取settings.allowed_ws_origin()即--allow-websocket-origin/BOKEH_ALLOW_WS_ORIGIN否则用应用级websocket_origins。命中后把具体 origin 回写到Access-Control-Allow-Origin并加Vary: Origin——源码注释解释了原因带凭据的 CORS 请求不能依赖通配符 origin只能镜像已被信任打开 Bokeh WebSocket 的源。GET 主流程L89-L121可归纳为 6 步调用_allow_websocket_origin()处理 CORSget_session()解析会话失败返回 403必填查询参数bokeh-autoload-element目标 DOM 元素 ID缺失时send_error(400, reasonNo bokeh-autoload-element query parameter)可选参数bokeh-app-path默认/与bokeh-absolute-url用于推断server_url可选参数resources默认default值为none时不注入资源随后由应用生成 bundle 并附加一段Script其内容由script_for_render_items基于RenderItem(tokensession.token, elementidelement_id, ...)渲染用bokeh.core.templates的AUTOLOAD_JS模板导入见 L34输出 JSContent-Type为application/javascript。OPTIONS 预检L123-L126浏览器在跨域 GET 前会先发 OPTIONS这里设置Access-Control-Allow-Methods: PUT, GET, OPTIONS并复用 origin 镜像逻辑。六、MetadataHandler/metadata 返回应用元数据MetadataHandler 处理prefix/app/metadata返回一个极简的 JSONauthenticated async def get(self, *args: Any, **kwargs: Any) - None: url self.application_context.url userdata self.application_context.application.metadata if callable(userdata): userdata userdata() if userdata is None: userdata {} metadata dict(urlurl, datauserdata) self.set_header(Content-Type, application/json) self.write(json.dumps(metadata))规则很直接data字段来自应用对象的metadata属性可为dict或返回dict的可调用对象为None时回退为空 dicturl字段来自application_context.url即该应用被注册的路径。这个端点的典型用途是让客户端脚本在不打开文档页的情况下探测应用身份或携带的自定义元数据。七、RootHandler应用索引页 /RootHandler 是顶层路由r/?的处理器initialize从 Tornado 的路由上下文URLRoutes允许携带第三个dict参数见 urls.py L90-L108取出四个关键字applications、prefix、index、use_redirect。其get逻辑authenticated async def get(self, *args: Any, **kwargs: Any) - None: prefix if self.prefix is None else self.prefix if self.use_redirect and len(self.applications) 1: redirect_to prefix list(self.applications.keys())[0] self.redirect(redirect_to) else: index app_index.html if self.index is None else self.index self.render(index, prefixprefix, itemssorted(self.applications.keys()))单应用 开启重定向302 跳转到prefix/app_name这正是bokeh serve单目录启动后浏览器直接看到应用的原因多应用或关闭重定向渲染索引页模板默认包内 app_index.html也可用index参数指定自定义模板把排序后的应用名列表items传入模板。八、StaticHandler 与异步静态文件服务static_handler.py 提供了两个类服务于prefix/static/(.*)路由。8.1 AsyncStaticFileHandler不在事件循环里做文件 I/OAsyncStaticFileHandler 重写自 TornadoStaticFileHandler模块文档字符串一句话点明目的“Serve static files without performing filesystem I/O on the event loop.”具体做法get_modified_time、compute_etag、get_content_size、os.stat等阻塞调用全部通过_run_in_executorIOLoop.current().run_in_executorasyncio.shieldL133-L142放入线程池支持Range请求解析Range头、越界返回416部分满足返回206并设置Content-RangeL76-L104ETag 未变化则返回304内容以分块方式流式写出_stream_contentL179-L198并处理iostream.StreamClosedError应对客户端提前断开路径安全_validate_absolute_path拒绝跳出根目录的路径403目录 URL 未带尾斜杠时 301 重定向补斜杠同时显式禁止以//开头的双斜杠重定向防开放重定向L144-L167。8.2 StaticHandler指向 BokehJS 资源目录StaticHandler 进一步把path固定为settings.bokehjs_path()即安装包内 BokehJS 产物目录。另一个值得注意的方法是append_versionL217-L229if settings.dev: return path else: version TornadoStaticFileHandler.get_version(dict(static_pathsettings.bokehjs_path()), path) return f{path}?v{version}即非 dev 模式下静态 URL 会追加?v版本戳以便浏览器缓存失效dev 模式则依赖浏览器开发者工具管理缓存源码注释原话。同包的 MultiRootStaticHandler 支持“按路径首段映射到不同根目录”root为dict[str, Path]urls.py用它为/static/extensions/(.*)从多个 Bokeh 扩展的产物目录提供静态文件dict(rootextension_dirs)urls.py L99。九、WSHandler / /ws 的完整握手与消息链路WSHandler 是AuthRequestHandler与 TornadoWebSocketHandler的混合体也是 Bokeh 前后端实时通信的唯一通道。9.1 子协议与令牌传递select_subprotocoldef select_subprotocol(self, subprotocols: list[str]) - str | None: if not len(subprotocols) 2: return None self._token subprotocols[1] return subprotocols[0]握手时客户端必须提供恰好两个子协议项第一个是bokeh子协议本身第二个是会话 token。open()L130-L171随即做三重校验子协议不是bokeh、缺少 token、token 签名无效check_token_signature签名模式取决于application.sign_sessions密钥为application.secret_key任一命中都会close()并抛ProtocolError。此外还检查 payload 中的session_expiry缺失或已过期都会断开过期时的错误信息直接提示用更大的--session-token-expiration值调整L157-L163。9.2 Origin 校验check_originL99-L128把Origin头解析出 host与允许列表比对列表优先取settings.allowed_ws_origin()对应命令行--allow-websocket-origin或环境变量BOKEH_ALLOW_WS_ORIGIN否则取应用级websocket_origins通常由--allow-websocket-origin与服务器自身地址组成。拒绝时会记录一条包含修复建议的 ERROR 日志“use --allow-websocket-origin%s or set BOKEH_ALLOW_WS_ORIGIN%s to permit this”。9.3 异步建链_async_openopen()通过后通过io_loop.add_callback转入异步流程_async_openL189-L227从 token 中取session_id调用application.create_session_if_needed(application_context, session_id, request, token)——不存在则创建新会话从application_context取出ServerSession创建协议Receiver用于重组分片消息与ServerConnectionapplication.new_connection(self, session)向客户端发送协议层ack()消息表示连接建立完成。发送侧用write_lock保证一次连接只按序写消息send_messageL271-L287并在WebSocketClosedError时仅告警不崩溃。9.4 消息处理与关闭on_messageL229-L269每个 WebSocket 帧先经Receiver.consume(fragment)组装成完整Message再交给connection.handle(parsed_message)若产生 reply 则回写。协议错误走_protocol_error关闭码1002其它内部错误走_internal_error关闭码1011。源码注释强调不能从on_message抛异常因为调用方只是 Tornado它无法处理未处理的 Futureon_closeL289-L296记录关闭码与原因通知会话notify_connection_lost()并调用application.client_lost(connection)由上层决定是否销毁会话压缩get_compression_optionsL181-L187把构造期传入的compression_level/mem_level透传给 Tornado未配置则禁用压缩模块尾部还定义了一个仅供测试收集原始收发报文的MessageTestPortL323-L328注释明确它是“undocumented API purely for harvesting low level messages for testing”。十、处理器继承体系与阅读建议把上面各模块串起来bokeh.server.views的继承关系可以概括为tornado.web.RequestHandler └── AuthRequestHandler # 授权钩子委托给 AuthProvider ├── RootHandler # prefix/ ├── MetadataHandler # prefix/app/metadata └── SessionHandler # get_session() 解析会话 ├── DocHandler # prefix/app/ └── AutoloadJsHandler # prefix/app/autoload.js tornado.web.RequestHandler WebSocketHandler └── WSHandler # prefix/app/ws tornado.web.StaticFileHandler └── AsyncStaticFileHandler # 线程池 I/O Range/ETag ├── StaticHandler # prefix/static/(.*) └── MultiRootStaticHandler # prefix/static/extensions/(.*)实践中的阅读与验证路径从路由入手先看 urls.py 确定某个 URL 由哪个 Handler 处理注意顶层路由与每应用路由都受prefix影响鉴权问题追 auth_request_handler.py 与应用的auth_provider重点看prepare()中_bokeh_login_url的缓存时机403/会话问题看SessionHandler.get_session()与BokehTornado.create_sessionsession_handler.py L78-L84跨域/嵌入问题对照AutoloadJsHandler的 CORS 头与WSHandler.check_origin的允许列表来源--allow-websocket-origin/BOKEH_ALLOW_WS_ORIGIN连接闪断优先查 WebSocket 日志中的子协议、token 签名与session_expiry三类ProtocolError它们对应open()中三个显式关闭分支。最后需要说明一点适用边界本文所有端点、参数与行为描述均基于当前仓库中 src/bokeh/server/views/ 的源码prefix、use_redirect、index等路由上下文字段由 tornado.py 中的BokehTornado在装配路由时注入若你定制部署如自定义 Tornado/ASGI 装配这些默认值可能随之变化请以当前仓库版本实际代码为准。【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表