ResponseWriter.Write和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.
")
}

And:

func Handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("blabla
"))
}

Also there's:

fmt.Fprintf(w, "blabla")

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

展开翻译

译文

我已经看到了三种将内容写入HTTP响应的方式:</ p>

   func Handler(w http.ResponseWriter,req * http.Request){
io.WriteString(w,“ blabla。
”)
}
</ code> </ pre>

And:</ p>

  func Handler(w http.ResponseWriter,r * http.Request){
w.Write([] byte(“ blabla
”) )
}
</ code> </ pre>

也有:</ p>

  fmt.Fprintf(w,“ blabla”)
</ code> </ pre>

它们之间有什么区别? 首选使用哪一个?</ p>
</ div>

2个回答

io.Writer

An output stream represents a target to which you can write sequence(s) of bytes. In Go this is captured by 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 (not necessarily just once) 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 (regarding sending the body).

WriteString()

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

w.Write([]byte("Hello"))

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 interface (available since Go 1.12, prior to that it was unexported):

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 (that also implements 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, read more about it here: golang: []byte(string) vs []byte(*string)), 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 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-that 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 imply improvement.

All in all, whenever you write string values, recommended to use io.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 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).

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 an io.Writer to this something if it supports writing the result to an io.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 calling ResponseWriter.Write().

However, it is more efficient to let the json package know that you have an io.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 new json.Encoder by calling json.NewEncoder() to which you can pass your http.ResponseWriter as an io.Writer, and calling Encoder.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.

dongmin4052
dongmin4052 知道了 谢谢。
接近 4 年之前 回复
dtn55928
dtn55928 如果使用json.Encoder并调用Encoder.Encode(),它将尝试将JSON数据写入ResponseWriter并返回错误(如果存在)。 但是在返回错误时,Encode()可能已经向响应中写入了部分数据(暗示已提交标头):您不能“取回”部分写入的数据,也不能修改HTTP 此时的标题。 如果您想在此时更改响应:您很困惑。
接近 4 年之前 回复
doudiza9154
doudiza9154 如果使用json.Marshal(),它将首先尝试封送值,然后将结果作为[] byte返回,如果封送失败则返回错误。 此时,没有任何内容写入ResponseWriter,因此,如果出现错误,您甚至可以选择发送回HTML错误页面(不同的内容类型)。
接近 4 年之前 回复
duanqianwei2485
duanqianwei2485 我不太明白这一点:这也取决于您要如何处理错误,因为封送处理首先为您提供了更多处理方式,而不是json.Encoder,在这种情况下,检测到错误时可能已经编写了响应。 您能解释一下还是举个例子?
接近 4 年之前 回复
duanqiang9212
duanqiang9212 这取决于您如何进行封送处理。 首先,不要忘记正确设置响应头(内容类型)。 然后,您可以使用json.Marshal()进行编组,然后简单地将w.Write()作为结果,或者您可以创建一个新的json.Encoder,将ResponseWriter包装为基础/目标编写器。 这也取决于您要如何处理错误,因为封送处理首先为您提供了进行操作的更多选项,与json.Encoder相反,在这种情况下,检测到错误时可能已经编写了响应。
接近 4 年之前 回复
doudi8298
doudi8298 因此,如果我发送回经过封送处理的JSON,则使用写入就足够了,因为它是字节?
接近 4 年之前 回复
douhensheng1131
douhensheng1131 Upvoted。 也许我应该接受你的。
接近 4 年之前 回复

As you can see from here(ResponseWriter), that it is a interface with Write([]byte) (int, error) method.

So in io.WriteString and fmt.Fprintf both are taking Writer as 1st argument which is also a interface wrapping Write(p []byte) (n int, err error) method

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

So when you call io.WriteString(w,"blah") check here

func WriteString(w Writer, s string) (n int, err error) {
  if sw, ok := w.(stringWriter); ok {
      return sw.WriteString(s)
  }
  return w.Write([]byte(s))
}

or fmt.Fprintf(w, "blabla") check here

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
   p := newPrinter()
   p.doPrintf(format, a)
   n, err = w.Write(p.buf)
   p.free()
   return
}

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 ")). 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.

json.NewEncoder(w).Encode(wrapper)
//Encode take interface as an argument. Wrapper can be:
//wrapper := SuccessResponseWrapper{Success:true, Data:data}

展开翻译

译文



此处(ResponseWriter),它是具有 Write([] byte)(int,错误)</ code>方法的接口。</ p>

< p>因此在 io.WriteString </ code>和 fmt.Fprintf </ code>中,两者都使用写入器作为第一个参数,它也是一个包装 Write(p [] byte)(n int,err错误)</ code>方法</ p>

< pre> type Writer接口{
Write(p [] byte)(n int,err error)
}
</ code> </ pre>

因此,当您调用io时 .WriteString(w,“ blah”)在此处检查 </ p>

  func WriteString(w Writer,s string)(n int,err error){
如果sw,ok:= w。(stringWriter); ok {
返回sw.WriteString(s)
}
返回w.Write([] byte(s))
}
</ code> </ pre>

或fmt .Fprintf(w,“ blabla”)在此处检查 </ p>

  func Fprintf(w.Writer,格式字符串,... interface {})(n int,err错误){
p:= newPrinter()
p.doPrintf(format,a)

n,err = w.Write(p.buf)
p.free()
return
}
</ code> </ pre>

您只是间接调用Write Method 当您在两种方法中都传递 ResponseWriter </ code>变量时。</ p>

所以为什么不直接使用 w.Write([] byte(“ blabla \ N“))</代码>。 希望您能得到答案。</ p>

PS:如果您想将其作为JSON响应发送,还有另一种使用方法。 </ p>

  json.NewEncoder(w).Encode(wrapper)
// Encode将接口作为参数。 包装器可以是:
//包装器:= SuccessResponseWrapper {成功:true,数据:数据}
</ code> </ pre>
</ div>

dqkxo44488
dqkxo44488 为什么在“写入”示例中将“ ”添加到“ blabla”?
一年多之前 回复
douzhi7754
douzhi7754 很好的解释。
接近 4 年之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐