背景
本机 ubuntu 20,node 20, qwik 1.1.5, fastify 4.18.0
使用 qwik 通过 trpc http link 的方式调用暴露出来的 qwik 的 onRequest 的 trpc 端点,调用是正常的。但是在服务器上,服务无法拿到 request 中的 body 数据。
初次探索
node_modules/@trpc/server/dist/adapters/fetch/index.mjs 这个模块负责对 把 fastify 的请求 转成 qwik 的请求。
在
body: opts.req.headers.get('content-type') === 'application/json' ? await opts.req.text() : ''
语句中,
- 对 opts.req 运行 opts.req.json() 返回无法解析。
- 对 opts.req 运行 opts.req.text() 返回 body is unusable。
网上给的 readablestream 的读入方式太麻烦了。
- 尝试:现在猜测是请求发出的时候,在 caddy 中转或者在某中间链路中发生丢失导致的问题。本机调用的时候给的 TRPC endpoint 是自己本机的端口地址,而服务器上的是给的是域名地址。把服务器上的域名地址改到本机端口地址,结果问题依然发生。所以不是这个问题导致的。
- 尝试:node 版本和依赖完全统一,依然发生问题,不是由于依赖或者运行环境引起的。
内部调用的解决办法
将 http 调用改为过程调用。避开了问题,问题解决。
用 SPA 应用调用,问题依然发生,由于还是要对外进行服务的,需要找真正原因。
查找过程
TRPC 给的适配器是 fetch 原生,不包括 qwik。qwik 传入的 request 是跟平台无关的,导致这个适配问题的产生。服务跑的是传统的 fastify, https://github.com/fastify/fastify。
如果你的 qwik 是经过 fastify 包装的, 入口是 entry.fastify.tsx。
await fastify.register(FastifyQwik, { distDir, buildDir });
fastify 会把进来的请求进行一次包装,在 plugin 那一层放出给到 qwik 应用。 fastify 不处理的流量给到 qwik 的 router 里。
const { router, notFound } = createQwikCity({ render, qwikCityPlan });
fastify.setNotFoundHandler(async (request, response) => {
// 在这个部分 fastify 的 post 数据是存在的。
await router(request.raw, response.raw, fastify.log.error);
await notFound(request.raw, response.raw, fastify.log.error);
});
qwik 会去拿 request.raw 原始的 request 类型 - node-incomingmessage
node_modules/@builder.io/qwik-city/middleware/node/index.d.ts
const serverRequestEv = await fromNodeHttp(getUrl(req, opts.origin), req, res, 'server');
实现跨平台的方式,它使用适配器模式,在 fromNodeHttp 他会进行重新包装自有 request。
const getRequestBody = async function* () {
for await (const chunk of req) {
yield chunk;
}
};
const body = req.method === 'HEAD' || req.method === 'GET' ? void 0 : getRequestBody();
const options = {
method: req.method,
headers: requestHeaders,
body,
duplex: 'half',
};
至此,判断可能是某个环节中转导致问题发生。大概率是 fastify 的 request.raw 中转导致的。
备注
qwik 会把里面的内容一并打包到 entry.fastify.js
import { createQwikCity } from '@builder.io/qwik-city/middleware/node';
pipe 越少,问题越少
原本在迁移过程前, 想把 trpc 转移至 qwik 外的 fastify, 看 qwik 本身有处理 plugin 的能力,就把 trpc 的 endpoint 放在了 qwik router 里面了。
但是 qwik 的文档里面 deploy 都是需要适配器使用的,无法单独部署。不可直接使用 vite preview 作为服务。
https://github.dev/wmalarski/qwik-trpc-supabase/blob/master/src/entry.dev.tsx 基于 trpc 的适配方式,但这个没有选择前置服务器,vite preview 只作为预览是可行的
解决方案:
- fastify 提前接管 trpc 流量,不走 qwik 流量。
总结
- 预览的效果和最终效果一致能减少很多不必要的工作。
- 「若無必要,勿增實體」(Non sunt multiplicanda entia sine necessitate)