Manual flush golang http.ResponseWriter

Problem

For a recent project I wanted to start rendering the HTML page even if the server was still working on a long runing task. (For showing the loading screen of https://unshort.link without the use of JavaScript)

My code looked basically like following:

func h(rw http.ResponseWriter, req *http.Request) {
    io.Copy(rw,loadingHTMLByteReader)   
    tResult = performLongRunningTask()
    rw.Write(tResult)
}

loadingHTMLByteReader is the HTML I already wanted to render even that the server can not yet write all results.

But sadly that did not produce the result I anticipated. The page was still rendered completely at once after the complete process was done.
Apparently go buffers there response writer until the handler returns, or the buffer (default 4KB) is full.

The buffer is defined via the http.Transport:

type Transport struct {
    ...
    // WriteBufferSize specifies the size of the write buffer used
    // when writing to the transport.
    // If zero, a default (currently 4KB) is used.
    WriteBufferSize int
    ...
}

Ideas

So there are two options for overarching my intended result:

  1. set WriteBufferSize to something really small
  2. manual flush the http.ResponseWriter buffer

Option 1 is obviously a unsatisfying solution, as I would not be able to control exactly that the complete loadingHTMLByteReader would be send if it overlaps the buffer borders.

E.g. loadingHTMLByteReader is of size 3KB if the buffer size is set to 2KB, the last 2KB would not have been written until the buffer is filled with additional data or the handler returned. So I went for option 2

Highly skilled DevOps/SRE Freelancer

I am Simon, the author of this blog. And I have great news: You can work with me

As DevOps and Infrastructure freelancer, I will help you choose the right Infrastructure technology for your company, fix your cloud problems and support your team in building scalable products.

I work with Golang, Docker, Kubernetes, Google Cloud, AWS and Terraform.

Checkout my website simon-frey.com to learn more or directly contact me via the button below.

Simon Frey Header image
Let’s work together!

How to flush http.ResponseWriter

Researching the net I found the following solution how to manually flush the http.ResponseWriter:

if f, ok := rw.(http.Flusher); ok {
    f.Flush()
}

My problem is solved and the site works as intended:

func h(rw http.ResponseWriter, req *http.Request) {
    io.Copy(rw,loadingHTMLByteReader)
    if f, ok := rw.(http.Flusher); ok {
        f.Flush()
    }
    tResult = performLongRunningTask()
    rw.Write(tResult)
}

Important notes

The http.ResponseWriter not always implements the Flusher interface

As stated in the godoc the ResponseWriter is not always implementing the Flusher interface, especially when you using wrappers around the ResponseWriter.
It is important to always check if the type assert worked and only then use the Flusher to prevent server panics in edge cases

There is other buffering in the network

Flushing the response writer only has control about the application layer buffering. You have no control over what the operating system, the network and the client does. All of these entities may introduce additional caching themselves.
Using the manual flush should only be used for performance improvements and nice to have features. Do not assume you can use it for any process critical behavior, it’s only a nice to have.

Sources:
https://stackoverflow.com/questions/19292113/not-buffered-http-responsewritter-in-golang

To never miss an article subscribe to my newsletter
No ads. One click unsubscribe.