laike9m laike9m - 14 days ago 4x
HTTP Question

What's the difference between ResponseWriter.write and io.WriteString?

I've seen three ways of writing content to HTTP response

func Handler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "blabla.\n")


func Handler(w http.ResponseWriter, r *http.Request) {

Also there's

fmt.Fprintf(w, "blabla")

What's the difference between them? Which one is preferred to use?



An output stream represents a target to which you can write sequence of bytes. In Go this is captured with the general io.Writer interface:

type Writer interface {
    Write(p []byte) (n int, err error)

Everything that has this single Write() method can be used as an output, for example a file on your disk (os.File), a network connection (net.Conn) or an in-memory buffer (bytes.Buffer).

The http.ResponseWriter that is used to configure the HTTP response and send the data to the client is also such an io.Writer, the data you want to send (the response body) is assembled by calling ResponseWriter.Write() (which is to implement the general io.Writer). This is the only guarantee you have about the implementation of the http.ResponseWriter interface.


Now on to WriteString(). Often we want to write textual data into an io.Writer. Yes, we can do that by simply converting the string to a []byte, e.g.


which works as expected. However this is a very frequent operation and so there is a "generally" accepted method for this captured by the io.stringWriter unexported interface:

type stringWriter interface {
    WriteString(s string) (n int, err error)

This method gives the possibility to write a string value instead of a []byte. So if something (e.g. an io.Writer) implements this method, you can simply pass string values without []byte conversion. This seems like a minor simplification in code, but it's more than that. Converting a string to []byte has to make a copy of the string content (because string values are immutable in Go), so there is some overhead which becomes noticeable if the string is "bigger" and/or you have to do this many times.

Depending on the nature and implementation details of an io.Writer, it may be that it is possible to write the content of a string without converting it to []byte and thus avoiding the above mentioned overhead.

As an example, if an io.Writer is something that writes to an in-memory buffer (bytes.Buffer is such an example), it may utilize the builtin copy() function:

The copy built-in function copies elements from a source slice into a destination slice. (As a special case, it also will copy bytes from a string to a slice of bytes.)

The copy() may be used to copy the content (bytes) of a string into a []byte without converting the string to []byte, e.g.:

buf := make([]byte, 100)
copy(buf, "Hello")

Now there is a "utility" function io.WriteString() that writes a string into an io.Writer. But it does this by first checking if the (dynamic type of the) passed io.Writer has a WriteString() method, and if so, that will be used (whose implementation is likely more efficient). If the passed io.Writer has no such method, then the general convert-to-byte-slice-and-write-it-that-way method will be used as a "fallback".

You might think that this WriteString() will only prevail in case of in-memory buffers, but that is not true. Responses of web requests are also often buffered (using an in-memory buffer), so it may improve performance in case of http.ResponseWriter too. And if you look at the implementation of http.ResponseWriter: it's the unexported type http.response (server.go currently line #308) which does implement WriteString() (currently line #1212) so it does mean an improvement.

All in all, whenever you write string values, recommended to use io.WriteString() as it may be more efficient (faster).


You should look at this as a convenient and easy way to add more formatting to the data you want to write, in exchange for being somewhat less performant.

So use fmt.Fprintf() if you want formatted string created in the easy way, e.g.:

name := "Bob"
age := 23
fmt.Fprintf(w, "Hi, my name is %s and I'm %d years old.", name, age)

Which will result in the following string to be written:

Hi, my name is Bob and I'm 23 years old.

One thing you must not forget: fmt.Fprintf() expects a format string, so it will be preprocessed and not written as-is to the output. As a quick example:

fmt.Fprintf(w, "100 %%")

You'd expect that "100 %%" would be written to the output (with 2 % characters), but only one will be sent as in the format string % is a special character and %% will only result in one in the output.

If you just want to write a string using the fmt package, use fmt.Fprint() which does not require a format string:

fmt.Fprint(w, "Hello")

Another benefit of using the fmt package is that you can write values of other types too, not just strings, e.g.

fmt.Fprint(w, 23, time.Now())

(Of course the rules how to convert any value to a string –and to series of bytes eventually– is well defined, in the doc of the fmt package.)

For "simple" formatted outputs the fmt package might be ok. For complex output documents do consider using the text/template (for general text) and html/template (whenever the output is HTML).