zoukankan      html  css  js  c++  java
  • Koa 中实现 chunked 数据传输

    有关于 Transfer-Encoding:chunked 类型的响应,参见之前的文章HTTP 响应的分块传输。这里看 Koa 中如何实现。

    Koa 中请求返回的处理

    虽然官方文档有描述说明不建议直接调用 response.write

    Bypassing Koa's response handling is not supported. Avoid using the following node properties:

    • res.statusCode
    • res.writeHead()
    • res.write()
    • res.end()

    但要实现分片向客户端发送数据,必然还是得调用 Node.js Http 模块的 response.write(chunk[, encoding][, callback]) 方法,而这里的 response 就是 ctx.resctx.response

    所以为什么 Koa 要说不建议直接调用上述方法操作请求的返回呢,我们来看看 Koa 内部对 response 都会做些什么默认的处理。

    application.js

      handleRequest(ctx, fnMiddleware) {
        const res = ctx.res;
        res.statusCode = 404;
        const onerror = err => ctx.onerror(err);
        const handleResponse = () => respond(ctx);
        onFinished(res, onerror);
        return fnMiddleware(ctx).then(handleResponse).catch(onerror);
      }

    在应用完各种中间件后(fnMiddleware(ctx))通过 handleResponse 对请求进行一些操作,最终是在 respond 函数里。

    respond 方法
    function respond(ctx) {
      // allow bypassing koa
      if (false === ctx.respond) return;
    

    if (!ctx.writable) return;

    const res = ctx.res;
    let body = ctx.body;
    const code = ctx.status;

    // ignore body
    if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    return res.end();
    }

    if ('HEAD' == ctx.method) {
    if (!res.headersSent && isJSON(body)) {
    ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
    }

    // status body
    if (null == body) {
    if (ctx.req.httpVersionMajor >= 2) {
    body = String(code);
    } else {
    body = ctx.message || String(code);
    }
    if (!res.headersSent) {
    ctx.type = 'text';
    ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
    }

    // responses
    if (Buffer.isBuffer(body)) return res.end(body);
    if ('string' == typeof body) return res.end(body);
    if (body instanceof Stream) return body.pipe(res);

    // body: json
    body = JSON.stringify(body);
    if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
    }
    res.end(body);
    }

    respond 方法里会根据外部是否有设置过 ctx.body,以及不同的 header 来设置 ctx.body,最终会调用 response.end 来结束掉本次请求。

    注意到如果设置了 ctx.respond = false,这个方法就直接 return 了,这是一种跳过这里处理的方式。但其实如果我们在中间件中手动调用了 ctx.res.end() 后,相当于已经提前结束掉请求了,同样也不会走 Koa 这里的处理。

    所以直接在中间件中调用 ctx.res.write()ctx.res.end() 就可以实现 chunked 类型的响应,倒无须对 Koa 做额外设置。

    Koa 实现 chunked 数据传输

    根据上面的分析,及之前一篇关于HTTP 响应的分块传输的文章,我们得出以下 Koa 中的实现逻辑:

    const Koa = require("koa");
    const app = new Koa();
    const PORT = 3000;
    app.use((ctx, _next) => {
      const res = ctx.res;
      ctx.status = 200;
      res.setHeader("Content-Type", "text/html");
      res.write(`start<br>`);
      return new Promise(resolve => {
        let i = 0,
          total = 5;
        while (i <= total) {
          (function(i) {
            setTimeout(() => {
              if (i === total) {
                resolve();
                res.end();
              } else {
                res.write(`${i}<br>`);
              }
            }, i * 1000);
          })(i);
          i++;
        }
      });
    });
    

    app.listen(PORT);
    console.info(</span>server started at http://localhost:<span class="pl-s1"><span class="pl-pse">${</span><span class="pl-c1">PORT</span><span class="pl-pse">}</span></span><span class="pl-pds">);

    运行效果:

    Koa 中实现 chunked 响应的运行效果

    Koa 中实现 chunked 响应的运行效果

    如你所见,Koa 中的这个实现会在调用 ctx.res.end() 后将本来应该在页面内容中处于最顶部的内容,移动到最底部。不解。

    或者通过 curl 在命令行中查看效果:

    $ curl -N http://localhost:3000

    命令行中接收 chunked 数据的效果

    命令行中接收 chunked 数据的效果

    示例代码可在 wayou/koa-chunked-response 找到。

    相关资源

  • 相关阅读:
    bzoj1711
    bzoj1458
    bzoj1433
    hdu2732
    bzoj1066
    hdu3549
    poj1698
    [ZJOI2007]时态同步
    SA 学习笔记
    [LUOGU]2016 Sam数
  • 原文地址:https://www.cnblogs.com/Wayou/p/koa_transfer_encoding_chunked.html
Copyright © 2011-2022 走看看