内容#

本节描述了内容是如何在 JupyterLite 站点中集成的以及如何使用它。

在内核中访问#

默认情况下,通过默认文件浏览器可访问的内容与执行内核中可访问的内容是独立的。使文件可用于内核可能取决于内核的实现方式。

Emscripten 内核#

使用 Emscripten 的内核(例如 pyodidexeus 内核)依赖于 Emscripten 文件系统来访问其内容。对于这种情况,@jupyterlite/contents 提供了一个 DriveFS 辅助类,可用于在 Emscripten 文件系统中挂载文件。

  const mountpoint = '/drive';
  const { FS, PATH, ERRNO_CODES } = /* provided by the emscripten module */;
  const { baseUrl } = options;
  const { DriveFS } = await import('@jupyterlite/contents');

  const driveFS = new DriveFS({
    FS,
    PATH,
    ERRNO_CODES,
    baseUrl, // Website base URL
    driveName: 'my-drive', // Any name of your choosing
    mountpoint,
  });
  FS.mkdir(mountpoint);
  FS.mount(driveFS, {}, mountpoint);
  FS.chdir(mountpoint);

挂载驱动器后,Jupyter Server 内容(文件浏览器中显示的内容)将在内核中的 /drive 文件夹下可用。

为了让 DriveFS 从主应用程序请求其内容,需要网站基本 URL。深入了解驱动器架构将阐明这一点。

        flowchart LR
    subgraph main thread
        direction TB
        M[Main thread]
        M --- C[Contents]
    end
    subgraph webworker
        direction TB
        M -.- K[Kernel]
        K --- FS[Emscripten FS]
        FS --- D[DriveFS]
    end
    S[Service worker] -.-|BroadcastChannel| M
    D -.-|REST API| S
    

在 JupyterLite 中运行内核时,有三个线程在起作用:

  • 主线程:它执行主用户界面并了解文件浏览器内容。

  • 内核 Web Worker:它执行内核(例如,评估主线程发送的笔记本中的代码片段)。它将一个 DriveFS 挂载到 Emscripten 文件系统中。

  • 服务 Worker:它从缓存中提供网站资产(以支持离线工作)。它还可以捕获任何其他网络请求。

假设内核执行以下写入文本文件的 Python 代码片段:

Path("dummy.txt").write_text("Writing on Emscripten filesystem")

以下是执行文件系统操作的简化交互序列:

        sequenceDiagram
  participant P as Python interpreter
  participant F as Emscripten FS
  participant D as DriveFS
  participant S as Service Worker
  participant M as Main thread
  participant C as Contents manager

  P->>+F: Write text into file
  F->>+D: Call put
  D->>+S: Send HTTP POST /api/drive
  S->>+M: Broadcast message via channel
  M->>+C: Call `save`
  C-->>-M: None
  M-->>-S: Response
  S-->>-D: Response
  D-->>-F: Return
  F-->>-P: Done
    

当代码与文件系统交互时,它与 Emscripten 虚拟文件系统交互。这个虚拟文件系统允许经典代码(例如本例中的 Python 片段)在几乎没有或无需更改的情况下运行。此外,虚拟文件系统使开发人员能够通过 文件系统 API 提供自己的处理文件系统 I/O 的机制。在序列图中,我们将 API 交互简化为单个 put 操作(实际上,在写入文件时会发生多次调用)。由于我们插入了自定义驱动器实现 DriveFS,因此 put 的解析成为该代码的责任。实现的逻辑会向 /api/drive 端点发起一个 POST HTTP 请求,请求正文描述了要执行的文件系统操作。在这种情况下,它看起来像:

{
  "method": "put",
  "path": "/dummy.txt",
  "data": { "format": "text", "data": "Writing on Emscripten filesystem" }
}

此请求由服务 Worker(在 @jupyterlite/server 包中定义)捕获。服务 Worker 通过名为 /sw-api.v1BroadcastChannel 中的消息将 HTTP 请求转发到主线程。此消息由在插件 @jupyterlite/application-extension:service-worker-manager 中实例化的 ServiceWorkerManager 接收。该包装器可以访问 Jupyter 内容管理器来处理请求。例如,在 put 操作的情况下,将调用内容管理器的 save 方法。然后,回复将传播回(通过 BroadcastChannel,然后是网络请求等)Emscripten 文件系统。

由于所有针对相同源的打开选项卡都可以监听 BroadcastChannel 上的消息,因此请求在其负载中包含一个唯一标识符 (browsingContextId),以标识消息来源的浏览上下文(即浏览器选项卡)。当消息返回到 ServiceWorkerManager 时,它会检查此标识符以确保消息适用于正确的选项卡。

使用 HTTP 请求的需求源于同步 API (Emscripten 文件系统) 与异步 API (Jupyter 内容管理器) 之间接口的限制。

这种架构使得 lite 内核能够访问来自自定义 JupyterLab 驱动器的内容,以支持多种内容来源。