Application 2023-09-11

Side Effects of Go's response.WriteHeader

Learn why calling Go response.WriteHeader multiple times triggers the superfluous call warning. Covers how the first status code wins and how to fix duplicate WriteHeader in HTTP handlers.

Read in: ja
Side Effects of Go's response.WriteHeader

Overview

This is a note on the side effects of Go's response.WriteHeader.

Superfluous response.WriteHeader

The following is a straightforward example, but calling WriteHeader multiple times like this will result in the error http: superfluous response.WriteHeader call from main.handler.

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.WriteHeader(http.StatusInternalServerError) // Error!
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8888", nil)
}

When called multiple times like this, the status code from the first call is adopted, and the status code from the last call is ignored. (Is this related to HTTP specifications?)

Looking into this gives a sense of the specifications.

Response

In the straightforward example above, it would suffice to adjust the implementation to call WriteHeader only once. However, suppose there are cases where WriteHeader is set multiple times due to implementation constraints.

import (
	"bytes"
	"html/template"
	"net/http"
)

// Function called to render the error page
func ExecuteTpl(w http.ResponseWriter) error {
	err := template.Must(template.ParseFiles("index.html")).Execute(w, nil)
	w.WriteHeader(http.StatusInternalServerError)
	if err != nil {
		return err
	}

	return nil
}

Assuming that WriteHeader has already been called before this function, it would result in an error.

To avoid errors in such situations, you can write to a buffer first and then call WriteHeader at the end.

package main

import (
	"bytes"
	"html/template"
	"net/http"
)

func ExecuteTpl(w http.ResponseWriter) error {
	var buf bytes.Buffer

	w.WriteHeader(http.StatusInternalServerError)
	err := template.Must(template.ParseFiles("index.html")).Execute(w, nil)
	if err != nil {
		return err
	}

	buf.WriteTo(w)

	return nil
}

Other Notes

It seems that when there is an error during the execution of the template, the execution stops, but there is a possibility that writing to the response has already begun. Therefore, it might be better to call WriteHeader before Execute. (Probably)

References

Tags: Golang Tips
Share: 𝕏 Post Facebook Hatena
✏️ View source / Discuss on GitHub
☕ Support

If you enjoy this blog, consider supporting it. Every bit helps keep it running!


Related Articles