Marko

Troubleshooting HTTP Streams

The way Marko streams HTML is old and well-supported, but default configurations and assumptions by other software can foil it. This page describes some known culprits that may buffer your Node server’s output HTTP streams.

Reverse proxies/load balancers

  • Turn off proxy buffering, or if you can’t, set the proxy buffer sizes to be reasonably small.

  • Make sure the “upstream” HTTP version is 1.1 or higher; HTTP/1.0 and lower do not support streaming.

  • Some software doesn’t support HTTP/2 or higher “upstream” connections at all or very well — if your Node server uses HTTP/2, you may need to downgrade.

  • Automatic gzip/brotli compression may have their buffer sizes set too high; you can tune their buffers to be smaller for faster streaming in exchange for slightly worse compression.

  • Check if “upstream” connections are keep-alive: overhead from closing and reopening connections may delay responses.

NGiNX

Most of NGiNX’s relevant parameters are inside its builtin http_proxy module:

proxy_http_version 1.1; # 1.0 by default
proxy_buffering off; # on by default

Apache

Apache’s default configuration works fine with streaming, but your host may have it configured differently. The relevant Apache configuration is inside its mod_proxy and mod_proxy_* modules and their associated environment variables.

CDNs

Content Delivery Networks (CDNs) consider efficient streaming one of their best features, but it may be off by default or if certain features are enabled.

Node.js itself

For extreme cases where Node streams very small HTML chunks with its built-in compression modules, you may need to tweak the compressor stream settings. Here’s an example with createGzip and its Z_PARTIAL_FLUSH flag:

const http = require("http");
const zlib = require("zlib");

const markoTemplate = require("./something.marko");

http
  .createServer(function (request, response) {
    response.writeHead(200, { "content-type": "text/html;charset=utf-8" });
    const templateStream = markoTemplate.stream({});
    const gzipStream = zlib.createGzip({
      flush: zlib.constants.Z_PARTIAL_FLUSH
    });
    templateStream.pipe(outputStream).pipe(response);
  })
  .listen(80);
EDIT

Contributors

Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.

Chat in Marko's Discord Server