首页 > 解决方案 > 如何在没有 http 服务器的情况下在内部使用 koa 应用回调?

问题描述

A 有一个基于 koa 的 js 应用程序并致力于服务器端渲染。在应用程序的其余部分,我从运行 SSR 的同一服务器加载一些脚本。虽然加载可以通过标准 HTTP 请求,但它会不必要地缓慢。所以我想简单地调用app.callback()koa 提供的将请求解析为请求文件的字符串。对于 SSR,我使用的是 jsdom,它加载文件并将它们作为浏览器处理。

问题是我无法得到结果,resp。“服务器响应”的正文。我试图模拟服务器请求和服务器响应,然后只读取套接字。我不确定这是否是最好的方法,但它似乎可以工作。但是,当我运行应用程序时,我只得到两个块 - 用于大型前端文件的字符串缓冲区。第一个块是标题。其次是代码的一部分,但只有 64kB。我不能得到下一个块。似乎模拟的可写流被停止了。也许它被塞住了,但我不知道为什么以及是否。

如果我尝试加载小于 64kB 的脚本,则会正确解析请求并完成流。

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const { Writable } = require("stream");
const http = require("http");
const fetch = async (url, options) => {
        const response = [];
        let finishedReolve;
        const finished = new Promise(resolve => (finishedReolve = resolve));
        const socket = new Writable({
                write: (data, encoding, cb) => {
                        console.log("write", data);
                        // outputs 2 times - 1. headers, 2. 64kB part of requested code
                        response.push(data);
                        cb();
                        return true;
                },
                destroy(err, cb) {
                        // Never gets called
                        console.log("destroy", err);
                        cb();
                        finishedReolve();
                },
                final(cb) {
                        // Never gets called
                        console.log("final");
                        cb();
                        finishedReolve();
                },
        });
        const req = new http.IncomingMessage(socket);
        req.method = "GET";
        const parsedURL = new URL(url);
        req.url = parsedURL.pathname;
        const res = new http.ServerResponse(req);
        res.assignSocket(req.socket);
        res.on("prefinish", () => {
                // Never gets called
                finishedReolve();
        });
        await this.callback()(req, res);
        await finished;
        return response[0];
};
class CustomResourceLoader extends jsdom.ResourceLoader {
        fetch(url, options) {
                return fetch(url, options);
        }
}
const dom = await JSDOM.fromFile(index_html_path, {
        url: domain + ctx.req.url,
        runScripts: "dangerously",
        resources: new CustomResourceLoader(),
});

流有什么问题?有没有其他方法可以app.callback()用来获取 koa 应用程序的输出?

标签: javascriptserver-side-renderingkoa

解决方案


The issue was, that the readable stream was sending chunks of 64kB, but my writable stream had highWaterMark set to default 16kB. Which (probably) caused the readable stream to wait for drain after it wrote more data that writable could process, which was after first 64kB chunk.

Tho solution was to set highWaterMark to more than it is for the readable stream, so e.g.

highWaterMark: 1024 * 64 * 2,

So the socket looks like this:

const socket = new Writable({
        highWaterMark: 1024 * 64 * 2,
        write: (data, encoding, cb) => {
                response.push(data);
                cb();
                return true;
        },
        destroy(err, cb) {
                console.log("destroy", err);
                cb();
                finishedReolve();
        },
        final(cb) {
                console.log("final");
                cb();
                finishedReolve();
        },
});

Also to finish, I need to listen to that weird event prefinish.


推荐阅读