ResponseWriter.Write和io.WriteString有什么区别?

lai*_*e9m 30 string http go slice

我已经看到了三种将内容写入HTTP响应的方法:

func Handler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "blabla.\n")
}
Run Code Online (Sandbox Code Playgroud)

和:

func Handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("blabla\n"))
}
Run Code Online (Sandbox Code Playgroud)

还有:

fmt.Fprintf(w, "blabla")
Run Code Online (Sandbox Code Playgroud)

他们之间有什么区别?哪一个更喜欢使用?

icz*_*cza 56

io.Writer

输出流表示可以写入字节序列的目标.在Go中,这是由通用io.Writer接口捕获的:

type Writer interface {
    Write(p []byte) (n int, err error)
}
Run Code Online (Sandbox Code Playgroud)

具有此单一Write()方法的所有内容都可以用作输出,例如磁盘上的文件(os.File),网络连接(net.Conn)或内存缓冲区(bytes.Buffer).

http.ResponseWriter用来配置HTTP响应并将数据发送给客户端也是这样的io.Writer,你要发送(响应正文)通过调用(不一定只是一次)组装的数据ResponseWriter.Write()(这是实现一般io.Writer) .这是您对http.ResponseWriter接口实现的唯一保证.

WriteString()

现在开始WriteString().通常我们想要将文本数据写入io.Writer.是的,我们可以通过简单地转换string为a来实现[]byte,例如

w.Write([]byte("Hello"))
Run Code Online (Sandbox Code Playgroud)

它按预期工作.然而,这是一个非常频繁的操作,因此有一个"通常"接受的方法,由未io.StringWriter导出的接口捕获:

type StringWriter interface {
    WriteString(s string) (n int, err error)
}
Run Code Online (Sandbox Code Playgroud)

这种方法可以写一个string值而不是一个[]byte.因此,如果某些东西(也实现了io.Writer)实现了这个方法,你可以简单地传递string值而不进行[]byte转换.这似乎是代码中的一个小的简化,但它不止于此.转换string[]byte必须制作string内容的副本(因为string值在Go中是不可变的),因此如果string"更大"和/或您必须多次执行此操作,则会有一些明显的开销.

取决于a的性质和实现细节io.Writer,可能的是,可以在string不转换它的情况下写入a的内容,[]byte从而避免上述开销.

例如,如果a io.Writer是写入内存缓冲区的东西(bytes.Buffer就是这样一个例子),它可以使用内置copy()函数:

复制内置函数将元素从源切片复制到目标切片.(作为一种特殊情况,它还会将字符串中的字节复制到一个字节片段.)

copy()可用于一个内容(字节)复制string到一个[]byte不转换string[]byte,例如:

buf := make([]byte, 100)
copy(buf, "Hello")
Run Code Online (Sandbox Code Playgroud)

现在有一个"实用程序"函数io.WriteString()将a string写入io.Writer.但它通过首先检查传递的(动态类型)io.Writer是否有WriteString()方法来实现这一点,如果是,那么将使用它(其实现可能更有效).如果传递的io.Writer没有这样的方法,那么通常的convert-to-byte-slice-and-write-it-way方法将被用作"后备".

您可能认为这WriteString()只会在内存缓冲区中占上风,但事实并非如此.Web请求的响应也经常被缓冲(使用内存缓冲区),因此它也可以提高性能http.ResponseWriter.如果你看一下http.ResponseWriter它的实现:它是未实现的类型http.response(server.go目前是第308行),它确实实现了WriteString()(目前第1212行),所以它确实意味着一种改进.

总而言之,无论何时写入string值,建议使用io.WriteString()它,因为它可能更有效(更快).

fmt.Fprintf()

您应该将此视为一种方便简单的方法,为您要编写的数据添加更多格式,以换取性能稍差的方法.

因此,fmt.Fprintf()如果您想要string以简单的方式创建格式,请使用,例如:

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

这将导致以下内容string:

Hi, my name is Bob and I'm 23 years old.
Run Code Online (Sandbox Code Playgroud)

有一点你不能忘记:fmt.Fprintf()期望一个格式字符串,因此它将被预处理,而不是按原样写入输出.作为一个简单的例子:

fmt.Fprintf(w, "100 %%")
Run Code Online (Sandbox Code Playgroud)

您希望"100 %%"将写入输出(带有2个%字符),但只会发送一个,因为格式字符串%是一个特殊字符,并且%%只会在输出中产生一个.

如果您只想%使用string包编写,请使用fmt不需要格式的包fmt.Fprint():

fmt.Fprint(w, "Hello")
Run Code Online (Sandbox Code Playgroud)

使用该string软件包的另一个好处是,您也可以编写其他类型的值,而不仅仅是fmts,例如

fmt.Fprint(w, 23, time.Now())
Run Code Online (Sandbox Code Playgroud)

(当然,如何将任何值转换为string-and到最终字节序列的规则在string包的文档中已经很好地定义了.)

对于"简单"格式化输出,fmt包可能没问题.对于复杂的输出文档,请考虑使用fmt(对于一般文本)和text/template(每当输出为HTML时).

传递/移交 html/template

为了完整起见,我们应该提到,您希望作为Web响应发送的内容通常由支持"流式传输"结果的"内容"生成.一个示例可以是JSON响应,它是从结构或映射生成的.

在这种情况下,它往往更有效的传递/移交http.ResponseWriter这是http.ResponseWriter这个东西,如果它支持将结果写到一个io.Writer关于即时.

一个很好的例子就是生成JSON响应.当然,您可以将一个对象编组为JSON io.Writer,它会返回一个字节切片,您只需通过调用即可发送json.Marshal().

但是,让ResponseWriter.Write()包知道你有一个更高效json,最终你想要将结果发送给它.这样就不必首先在缓冲区中生成JSON文本,然后将其写入响应中然后丢弃.您可以io.Writer通过调用json.Encoder来创建一个新的,您可以将其json.NewEncoder()作为一个传递http.ResponseWriter,并io.Writer在此之后调用将直接将JSON结果写入您的响应编写器.

这里的一个缺点是,如果生成JSON响应失败,您可能会有一个部分发送/已提交的响应,您无法收回.如果这对您来说是个问题,那么除了在缓冲区中生成响应之外,您实际上没有其他选择,如果编组成功,那么您可以立即编写完整的响应.

  • Upvoted.也许我应该接受你的. (2认同)
  • @laike9m 如果您使用“json.Encoder”并调用“Encoder.Encode()”,它会尝试将 JSON 数据写入“ResponseWriter”,并返回一个“错误”(如果有)。但是,当返回错误时,“Encode()”可能已经将一些部分数据写入响应(意味着正在提交标头):您无法“收回”部分写入的数据,也无法此时修改 HTTP 标头。如果你想在这个时候改变回应:你就完蛋了。 (2认同)

小智 6

这里可以看到(ResponseWriter),它是一个带Write([]byte) (int, error)方法的接口.

因此,在io.WriteStringfmt.Fprintf两个正在作家作为第一个参数,这也是一个接口的包装Write(p []byte) (n int, err error)方法

type Writer interface {
    Write(p []byte) (n int, err error)
}
Run Code Online (Sandbox Code Playgroud)

所以当你调用io.WriteString(w,"blah")时请点击此处

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))
}
Run Code Online (Sandbox Code Playgroud)

或fmt.Fprintf(w,"blabla")在这里查看

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
}
Run Code Online (Sandbox Code Playgroud)

你只是ResponseWriter在两个方法中传递变量时间接调用Write 方法.

那么为什么不直接使用它来调用它w.Write([]byte("blabla\n")).我希望你得到你的答案.

PS:如果你想将它作为JSON响应发送,还有一种不同的使用方式.

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