Deno 在 Jupyter Notebook 中很难稳定运行 HTTP 服务,核心原因其实只有两点:第一,端口复用容易发生冲突;第二,服务生命周期缺少明确管理。每次执行 Deno.serve() 时,都会直接占用对应端口;而 Jupyter 里的单元格一旦被重复运行,之前启动的服务通常不会自动释放,结果就是旧端口尚未释放,新服务又尝试接管,最终触发 AddrInUse 错误。围绕这一常见问题,本文将提供真正可执行的解决方案,以及更符合工程实践的优化建议。

Deno 之所以无法在 Jupyter Notebook 里稳定启动 HTTP 服务器,根本原因就在于端口复用冲突和服务生命周期管理缺失:每次执行 `Deno.serve()` 都会独占端口,而 Jupyter 单元格重新运行时并不会自动关闭上一次创建的服务,因此极易出现 `AddrInUse` 错误。下面将结合实际使用场景,给出可落地的解决办法与更适合开发协作的工程化建议。
从设计初衷来看,Jupyter Notebook 并不是为“持续运行 Web 服务”而打造的,它更适合进行交互式、相互隔离的代码实验与结果展示。当你在单元格中调用 Deno.serve({ port: 80001 }, handler) 时,Deno 会在后台启动一个阻塞式 HTTP 服务(底层依赖 Rust tokio runtime),该进程会持续监听 80001 端口,并保持 socket 一直处于打开状态。问题也正出在这里:无论是 Jupyter 的 Python 内核,还是 Deno 内核,本身通常都不会自动清理这类长期驻留的服务进程。换句话说,常见表现通常如下:
- ✅ 第一次运行:HTTP 服务可以正常启动,但 Jupyter 前端不会自动跳转页面或弹出访问窗口(这是正常现象,因为
Deno.serve不会触发浏览器导航); - ❌ 第二次运行:内核再次尝试绑定相同端口 → 触发系统级错误
AddrInUse: Only one usage of each socket address... (os error 10048)。
? 正确做法:显式管理 HTTP 服务生命周期
你不能把“重复运行单元格”当成服务热更新手段。更稳妥的方式,是将启动、停止、重启这些操作拆分成独立且可控的流程。下面是推荐方案(适用于 Deno + Jupyter,例如 jupyter-deno-kernel,或通过 deno run --watch 进行外部集成):
✅ 方案一:使用 AbortController 主动关闭服务(推荐)
// 【单元格 1】定义并启动服务(带可取消能力)
let server: Deno.HttpServer | undefined;
const controller = new AbortController();
async function startServer() {
if (server) await server.shutdown(); // 确保先清理旧服务
server = Deno.serve({
port: 80001,
signal: controller.signal, // 关联中断信号
handler: (_req) => new Response("Hello, World! ?", {
headers: { "Content-Type": "text/plain" }
})
});
console.log("✅ Server running at https://localhost:80001");
}
await startServer();// 【单元格 2】手动停止服务(运行此单元格后再修改 handler)
if (server) {
await server.shutdown();
console.log("⏹️ Server stopped.");
}
controller.abort(); // 中断 signal,确保资源释放? 提示:修改 handler 之后,务必先执行【单元格 2】,再执行【单元格 1】——这才是安全、可控的“重启”方式。
⚠️ 方案二:改用非阻塞开发模式(更符合 Jupyter 使用范式)
尽量避免在 Notebook 中长期挂载 HTTP 服务。更推荐使用 Deno 的 --watch 模式,并配合外部终端来运行服务:
# 在系统终端(非 Jupyter 单元格)中执行 deno run --allow-net --watch server.ts
其中 server.ts 内容为:
const handler = (req: Request) => new Response("Hello from Deno! ?");
Deno.serve({ port: 80001 }, handler);这种方式下,Jupyter 仅负责编写、验证和调试 handler 逻辑,而 HTTP 服务则交由独立进程托管——不仅可以有效避免端口冲突,还能在保存文件后自动重启服务,更适合本地开发与调试流程。
? 重要注意事项
- 端口选择:Windows 默认会限制
1024以下端口的使用权限,通常需要管理员权限,建议优先选择8000+的开发端口(例如80001可以使用,但8080、3000这类端口在 Web 开发中更常见); - 防火墙与浏览器访问:首次访问
https://localhost:80001时,需要手动在浏览器地址栏输入,Jupyter 不会自动打开页面;如果出现连接被拒绝,请优先检查 Windows 防火墙或本地安全软件是否已放行该端口; - 内核兼容性:截至 2026 年,Deno 仍未正式发布原生 Jupyter 内核,
jupyter-deno-kernel依然属于社区维护项目。若用于生产或稳定开发环境,建议优先考虑 VS Code + Deno 扩展,或者使用 JupyterLab + NodeJS Kernel 搭配express做类似场景验证; - 替代思路:如果是教学、演示或临时验证场景,可以将
Deno.serve返回的Promise与setTimeout结合,模拟一个短时间运行的服务(不建议线上环境使用);或者直接借助Deno.inspect()输出 HTML 片段,在 Notebook 中渲染静态内容,避免真正启动 HTTP 服务。
✅ 总结
Jupyter 是进行探索式计算和交互式实验的高效工具,但它并不是 Web 服务的理想运行时。在 Deno 与 Jupyter 协同开发时,应始终遵循「职责分离」原则:
? Notebook 负责 handler 编写、数据构造、接口逻辑验证与 API 响应测试;
? 终端或脚本负责服务启停、端口控制以及进程管理与监控。
只有这样,才能同时兼顾交互开发效率与本地系统稳定性——这也是现代前端开发、AI 工程实践、LLM 应用开发以及本地模型 API 封装场景中的通用方法。
