Koa: Clarify documentation how to flush headers early using koa

Created on 29 Jul 2016  路  35Comments  路  Source: koajs/koa

We tried all possible google links about "koa flush headers early" and only could found two issues about "flushHeaders" function.

We tried that function but it doesn't work as expected. The expected behavior is:
1) call flushHeaders()
2) all headers that were set are sent over the internet to the client
3) if the server tries to set other headers it throws
4) when the body is set, it's streamed to the client

A basic use-case is to stream "preload" headers for alerting the client to download CSS and font files, so they will be ready by the time slow api and db calls are finished.

For some reason we were able to do this with express and can't figure out how to do it with Koa. Probably just missing documentation, it will help a lot! Thanks.

All 35 comments

The expected behavior should be how it works, what problems are you experiencing? It calls writeHead.

Hi @PlasmaPower
We're using like following:

async function (ctx, next) {
  ctx.set('Content-Type', 'text/html; charset=utf-8')
  ctx.status = 200
  ctx.flushHeaders()

  // headers should be sent, right?

  await asyncMethod()

  // set body here after asyncMethod() finishes

  ctx.body = myStreamResult()
}

But the headers are not being sent.
The method works with async/await?

Could you try logging ctx.res.headersSent after you call flushHeaders? That checks if Node has sent the headers yet.

After calling ctx.flushHeaders() ctx.res.headersSent is true, but still the headers are not being sent. If I send a request using curl or telnet for example, nothing happens after calling ctx.flushHeaders().

Interesting, I can replicate this with a Koa setup but not a plain Node HTTP server.

I'm not quite sure what's going on. Anybody else have ideas? I might do some more troubleshooting later.

Weird, I'll try to investigate more later, please let us know if you have any idea why this is happening, or any other solution that we could use. Thanks!

Any news on this issue? @PlasmaPower, any idea about what's happening?

I have no idea what's happening. I tried to setup a global.Proxy for the response object to log access but that hasn't been working well. I think there is a bug in Node's HTTP module that we're running into, but IDK.

Hi @PlasmaPower do you know anyone who could help about this? I think releasing subresource headers early is a great UX optimization that every site should look into implementing.

I don't know of anyone not already subscribing to this repository, no.

thanks for the help @PlasmaPower! we will rollback to express till we figure out, hope to have some time next week.

if you can make a PR with a failing test case here, that would be great: https://github.com/koajs/koa/blob/v2.x/test/response/flushHeaders.js

not sure what's going on. personally, i always just do something like:

async ctx => {
  const stream = this.body = new PassThrough()

  setTimeout(() => stream.end('something'), 1000)
}

and don't bother with explicitly calling flush headers, but this may be an issue if you have upstream async middleware

@jonathanong you are able to send headers before start processing the request? for example, sending subresources? Not sure if I understood correctly. thanks!

i start processing after the headers are sent. i start in the setTimeout, which is in a different/later phase of the event loop.

@jonathanong are you sure the headers are actually sent? I thought they were, but if you go to webpagetest.org and try it, you might see that in koa the headers are flushed only after the end of the event loop, together with everything else.

yes, the end of the middlewares' phase of the event loop (thought it's not really a phase since async stuff makes it skip phases). the logic in the setTimeout() is in a different phase. i'm not totally sure what the miscommunication is here

@jonathanong any way you can send me a web page where you do this? We want to try on webpagetest.org and try to replicate what you do. We couldn't figure out how to flush the headers early with node + koa. (we saw your examples on different issue comments)

don't think about "flushing headers early", think about "sending data later".

const { PassThrough } = require('stream')
const Koa = require('koa')
const app = module.exports = new Koa()

app.use(ctx => {
  ctx.type = 'json' 
  const stream = ctx.body = new PassThrough()

  setTimeout(() => {
    stream.end(JSON.stringify({ message: 'hello!' }))
  }, 10000)
})

^ that should work, minus any typos i've made in this editor

@jonathanong I'm not sure we're talking about the same thing.

Just to be clear, we don't want to "send the data later", but we want to "send the data early"
.
Makes sense? We want the headers of the HTTP response to always be 200 and always be sent to the client before we do any processing (like the stringfy json you're doing).

Just to be clear, this pseudo code using your example:

const { PassThrough } = require('stream')
const Koa = require('koa')
const app = module.exports = new Koa()

app.use(ctx => {
  ctx.type = 'json' 
  ctx.status = 200
  ctx.headers['Link'] = '</css/mycss.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin'
  ctx.flushHeaders()
  const stream = ctx.body = new PassThrough()

  setTimeout(() => {
    // imagine if this function would take 100ms of CPU data and block the event loop..
    stream.end(JSON.stringify({ message: 'hello!' }))
  }, 10000)
})

We actually want that when we call "flushHeaders" the HTTP stream is released to the client and it already starts downloading the CSS. In the example code, we want the HTTP status 200 to return together with header "Link".

the same thing should be happening, except you're explicitly flushing the headers .1ms earlier

except maybe if we're not explicitly writing head in respond, which we should probably do. it might not write headers unless there's enough data in the buffer

@jonathanong you have any idea on how to actually flush the headers to the HTTP pipe when we call "ctx.flushHeaders()"? That's what we want to achieve.

This is exactly the technique we want to achieve with KOA: http://www.websiteoptimization.com/speed/tweak/flush/

It doesn't seem supported at the moment.

how are you testing it? i've never had an issue with this

@jonathanong can you confirm you are actually able to send the "headers" of the HTTP response without sending the body? I'm a bit confused.

For us, KOA only sends the HTTP headers once the body is flushed, so they go together only.

To test you can try to flush the headers early, like in my pseudo code, and go to this tester:
https://www.webpagetest.org

The results should be consistently similar to this:
http://www.websiteoptimization.com/speed/tweak/flush/

Sorry, it's maybe confusing because it seems you're talking about flushing the HTML "<head>" area early, and I'm talking about flushing the HTTP headers early. Does that make sense?

To be more explicit of our intent, these headers are an example on how to "prefetch" with HTTP headers:

Content-Type: text/html; charset=utf-8
Link: </static/skeleton-024d499f.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin
Date: Tue, 09 Aug 2016 23:42:19 GMT
dc: uswestamazon
Age: 0
Content-Length: 95
Connection: keep-alive

got it. i think before, res.writeHead() actually wrote the headers. seems they added res.flushHeaders(). i assumed we were just talking about res.writeHead(). PR incoming

Thanks for the pr @jonathanong! @renatopet will test asap

fixed by #795. please update to 2.0.0-alpha.5

Great! Thanks!

alpha? shouldn't be beta?

i don't know. i'm confused. the last one was released as alpha.4 so i just bumped the version.

@jonathanong I just followed the last tag and bumped the version. :(

"2.0.0-alpha.5": "2016-08-10T19:16:51.922Z",
"2.0.0-alpha.4": "2016-07-23T18:07:08.835Z",
"2.0.0-alpha.3": "2015-11-05T02:41:26.489Z",
"2.0.0-alpha.2": "2015-10-27T23:31:45.783Z",
"2.0.0-alpha.1": "2015-10-22T23:37:58.640Z",

But this is don't work in browser. Browser wait body answer.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rainesinternationaldev picture rainesinternationaldev  路  5Comments

rally25rs picture rally25rs  路  4Comments

usernameisalreadytaken2014 picture usernameisalreadytaken2014  路  4Comments

tvq picture tvq  路  4Comments

koalex picture koalex  路  3Comments