在Deno Deploy的Fresh框架中使用Firebase Admin时请求挂起的问题
问题描述
我最近基于Deno Fresh框架做了个项目,用Firebase Admin在服务端从实时数据库取数据,然后渲染到页面上。本地跑和部署到自己的服务器上都没问题,但一部署到Deno Deploy,所有需要从Firebase拿数据的页面就直接挂住了。
我已经在Deno Deploy里配置了正确的Firebase Admin凭证环境变量,实时数据库的/test路径下也确实有字符串数据,而且Deno Deploy的日志里啥报错都没有。后来我加了好些console.log()排查,发现代码居然卡在了const value = (await ref.get()).val();这一行,后面的代码完全没执行。
代码示例
/firebase.ts
import { initializeApp, cert, getDatabase } from "https://www.gstatic.com/firebasejs/9.23.0/firebase-admin.js"; // 初始化Firebase Admin const app = initializeApp({ credential: cert(Deno.env.get("FIREBASE_CREDENTIALS")), // 这里之前可能没指定databaseURL? }); export const db = getDatabase(app);
页面路由示例(/routes/test.tsx)
import { Handlers } from "$fresh/server.ts"; import { db } from "../firebase.ts"; import { ref } from "https://www.gstatic.com/firebasejs/9.23.0/firebase-admin/database.js"; export const handler: Handlers = { async GET(_req, ctx) { console.log("准备获取Firebase数据"); const testRef = ref(db, "/test"); console.log("已创建数据库引用"); const value = (await testRef.get()).val(); // 代码就卡在这里了 console.log("数据获取完成:", value); return ctx.render({ value }); }, }; export default function TestPage({ value }: { value: string }) { return <div>从Firebase拿到的值:{value}</div>; }
解决方案
为啥会卡?
主要问题出在Deno Deploy的边缘环境特性上——它对长连接的支持很有限,而Firebase Admin SDK的实时数据库客户端默认会试图建立长连接来获取数据,这就导致请求一直处于挂起状态,永远等不到响应。另外也有可能是咱们初始化Firebase的时候漏了关键配置,比如没指定数据库URL,或者凭证解析错了。
具体怎么改?
这里给你两个可行的方案:
方案一:改用Firebase实时数据库REST API(推荐)
既然边缘环境对长连接不友好,咱们直接用HTTP请求调用REST API就绕开这个问题了,而且实现起来也简单:
// 修改页面的handler部分 export const handler: Handlers = { async GET(_req, ctx) { const databaseURL = "你的Firebase实时数据库URL"; // 从Firebase控制台复制 const apiKey = "你的Firebase项目API Key"; // 同样在控制台项目设置里找 // 调用REST API获取数据 const res = await fetch(`${databaseURL}/test.json?auth=${apiKey}`); if (!res.ok) { throw new Error(`请求失败:${res.statusText}`); } const value = await res.json(); return ctx.render({ value }); }, };
如果你的数据是公开可访问的,用API Key就足够;如果需要权限验证,也可以用Firebase Admin生成的自定义Token来替换auth参数。
方案二:调整Firebase Admin SDK的传输配置(可选)
要是你非得用Admin SDK,可以试试强制它用HTTP传输,而不是默认的WebSocket:
// 在/firebase.ts里初始化数据库后添加这段配置 import { setDatabaseAdminConfig } from "https://www.gstatic.com/firebasejs/9.23.0/firebase-admin/database.js"; export const db = getDatabase(app); // 强制使用HTTP传输 setDatabaseAdminConfig(db, { transport: "http", });
不过这个配置不是所有版本的Admin SDK都支持,所以还是优先推荐方案一。
额外检查点
别忘了在初始化Firebase App的时候,一定要指定databaseURL,不然SDK可能找不到你的数据库;还有,环境变量里的凭证是JSON字符串,得先解析成对象再传给cert(),比如改成cert(JSON.parse(Deno.env.get("FIREBASE_CREDENTIALS")!)),不然凭证会无效。
验证方法
改完代码重新部署到Deno Deploy,打开页面看看能不能正常加载数据,再去Deno Deploy的日志里确认,之前卡住的那行代码后面的console.log是不是能正常输出了。
备注:内容来源于stack exchange,提问作者Andrew Roberts

