I've seen three ways of writing content to HTTP response:
func Handler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "blabla.\n")
}
And:
func Handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("blabla\n"))
}
Also there's:
fmt.Fprintf(w, "blabla")
What's the difference between them? Which one is preferred to use?
As you can see from here(ResponseWriter), that it is a interface with
Write([]byte) (int, error)
method.So in
io.WriteString
andfmt.Fprintf
both are taking Writer as 1st argument which is also a interface wrappingWrite(p []byte) (n int, err error)
methodSo when you call io.WriteString(w,"blah") check here
or fmt.Fprintf(w, "blabla") check here
you are just calling Write Method indirectly as you are passing
ResponseWriter
variable in both methods.So just why not call it directly using
w.Write([]byte("blabla\n"))
. I hope you got your answer.PS: there's also a different way to use that, if you want to send it as JSON response.
io.Writer
An output stream represents a target to which you can write sequence of bytes. In Go this is captured by the general
io.Writer
interface: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 anio.Writer
, the data you want to send (the response body) is assembled by calling (not necessarily just once)ResponseWriter.Write()
(which is to implement the generalio.Writer
). This is the only guarantee you have about the implementation of thehttp.ResponseWriter
interface.WriteString()
Now on to
WriteString()
. Often we want to write textual data to anio.Writer
. Yes, we can do that by simply converting thestring
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:This method gives the possibility to write a
string
value instead of a[]byte
. So if something (that also implementsio.Writer
) implements this method, you can simply passstring
values without[]byte
conversion. This seems like a minor simplification in code, but it's more than that. Converting astring
to[]byte
has to make a copy of thestring
content (becausestring
values are immutable in Go), so there is some overhead which becomes noticeable if thestring
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 astring
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 builtincopy()
function:The
copy()
may be used to copy the content (bytes) of astring
into a[]byte
without converting thestring
to[]byte
, e.g.:Now there is a "utility" function
io.WriteString()
that writes astring
into anio.Writer
. But it does this by first checking if the (dynamic type of the) passedio.Writer
has aWriteString()
method, and if so, that will be used (whose implementation is likely more efficient). If the passedio.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 ofhttp.ResponseWriter
too. And if you look at the implementation ofhttp.ResponseWriter
: it's the unexported typehttp.response
(server.go
currently line #308) which does implementWriteString()
(currently line #1212) so it does mean an improvement.All in all, whenever you write
string
values, recommended to useio.WriteString()
as it may be more efficient (faster).fmt.Fprintf()
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 formattedstring
created in the easy way, e.g.:Which will result in the following
string
to be written: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: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 thefmt
package, usefmt.Fprint()
which does not require a formatstring
:Another benefit of using the
fmt
package is that you can write values of other types too, not juststring
s, e.g.(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 thefmt
package.)For "simple" formatted outputs the
fmt
package might be ok. For complex output documents do consider using thetext/template
(for general text) andhtml/template
(whenever the output is HTML).Passing / handing over
http.ResponseWriter
For completeness, we should mention that often the content you want to send as the web response is generated by "something" that supports "streaming" the result. An example may be a JSON response, which is generated from a struct or map.
In such cases it's often more efficient to pass / hand over your
http.ResponseWriter
which is anio.Writer
to this something if it supports writing the result to anio.Writer
on-the-fly.A good example of this is generating JSON responses. Sure, you could marshal an object into JSON with
json.Marshal()
, which returns you a byte slice, which you can simply send by callingResponseWriter.Write()
.However, it is more efficient to let the
json
package know that you have anio.Writer
, and ultimately you want to send the result to that. That way it is unnecessary to first generate the JSON text in a buffer, which you just write into your response and then discard. You can create a newjson.Encoder
by callingjson.NewEncoder()
to which you can pass yourhttp.ResponseWriter
as anio.Writer
, and callingEncoder.Encode()
after that will directly write the JSON result into your response writer.One disadvantage here is that if generating the JSON response fails, you might have a partially sent / committed response which you cannot take back. If this is a problem for you, you don't really have a choice other than generating the response in a buffer, and if marshaling succeeds, then you may write the complete response at once.