我已经看到了三种向HTTP响应写内容的方法:
func Handler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "blabla.\n")
}
和
func Handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("blabla\n"))
}
还有:
fmt.Fprintf(w, "blabla")
它们之间的区别是什么?哪一个更适合使用?
答案 0 :(得分:49)
io.Writer
输出流表示可以写入字节序列的目标。在Go中,这由通用io.Writer
接口捕获:
type Writer interface {
Write(p []byte) (n int, err error)
}
具有此单Write()
方法的所有内容都可以用作输出,例如磁盘上的文件(os.File
),网络连接(net.Conn
)或者 - 内存缓冲区(bytes.Buffer
)。
用于配置HTTP响应并将数据发送到客户端的http.ResponseWriter
也是io.Writer
,您要发送的数据(响应正文)通过调用来汇编(不一定只有一次)ResponseWriter.Write()
(这是为了实现一般io.Writer
)。这是关于http.ResponseWriter
接口实现的唯一保证(关于发送正文)。
WriteString()
现在开始WriteString()
。我们通常希望将文本数据写入io.Writer
。是的,我们只需将string
转换为[]byte
,例如
w.Write([]byte("Hello"))
按预期工作。然而,这是一个非常频繁的操作,所以通常"通常"已接受的io.StringWriter
接口捕获的方法(自Go 1.12以来可用,在此之前未被导出):
type StringWriter interface {
WriteString(s string) (n int, err error)
}
此方法可以编写string
值而不是[]byte
。因此,如果某些内容(也实现io.Writer
)实现了此方法,则只需传递string
值而不进行[]byte
转换。 这似乎是代码中的一个小小的简化,但它不止于此。将string
转换为[]byte
必须复制{{1} } content(因为string
值在Go中是不可变的,请在此处阅读更多相关信息:golang: []byte(string) vs []byte(*string)),因此如果string
是"更大&#,则会有一些明显的开销变得明显34;和/或你必须多次这样做。
根据string
的性质和实施细节,可以编写io.Writer
的内容而不将其转换为string
,从而避免上述开销。
例如,如果[]byte
是写入内存缓冲区的内容(io.Writer
就是这样的例子),它可以使用内置的copy()
函数:
复制内置函数将元素从源切片复制到目标切片。 (作为一种特殊情况,它还会将字符串中的字节复制到一个字节片段。)
bytes.Buffer
可用于将copy()
的内容(字节)复制到string
,而无需将[]byte
转换为string
,例如:
[]byte
现在有一个"实用程序"将buf := make([]byte, 100)
copy(buf, "Hello")
写入string
的函数io.WriteString()
。但它通过首先检查传递io.Writer
的(动态类型)是否具有io.Writer
方法来实现此目的,如果是,则将使用(其实现可能更有效)。如果传递的WriteString()
没有这样的方法,那么一般的转换为字节切片和写入方法将被用作"回退"
你可能会认为这个io.Writer
只会在内存缓冲区中占优势,但事实并非如此。 Web请求的响应也经常被缓冲(使用内存缓冲区),因此在WriteString()
的情况下也可以提高性能。如果你看一下http.ResponseWriter
的实现:它是未导出的类型http.ResponseWriter
(server.go
当前行#308),它确实实现了http.response
(当前行# 1212)因此它确实意味着改进。
总而言之,无论何时编写WriteString()
值,建议使用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)
:
string
有一点你不能忘记:Hi, my name is Bob and I'm 23 years old.
需要格式字符串,因此它将被预处理,而不是按原样写入输出。作为一个简单的例子:
fmt.Fprintf()
您希望将fmt.Fprintf(w, "100 %%")
写入输出(包含2个"100 %%"
个字符),但只会发送一个,因为格式字符串%
是一个特殊字符和%
只会在输出中生成一个%%
。
如果您只想使用%
包撰写string
,请使用不需要格式为fmt
的{{3}}:
string
使用fmt.Fprint()
包的另一个好处是您也可以编写其他类型的值,而不仅仅是fmt.Fprint(w, "Hello")
s,例如。
string
(当然,如何将任何值转换为fmt.Fprint(w, 23, time.Now())
- 以及最终的一系列字节的规则 - 已在string
包的文档中明确定义。)
对于"简单"格式化输出fmt
包可能没问题。对于复杂的输出文档,请考虑使用fmt
(对于一般文本)和text/template
(只要输出为HTML)。
fmt
为了完整起见,我们应该提一下,您希望作为网络响应发送的内容通常由"""生成。支持"流媒体"结果。一个示例可以是JSON响应,它是从结构或映射生成的。
在这种情况下,如果它支持将结果写入到http.ResponseWriter
http.ResponseWriter
这个某事,通常会更有效率一个io.Writer
即时。
这方面的一个很好的例子是生成JSON响应。当然,您可以使用html/template
将对象编组为JSON,它会返回一个字节切片,您只需通过调用io.Writer
即可发送。
但是,让ResponseWriter.Write()
包知道您拥有json
更有效率,最终您希望将结果发送给它。这样就不必首先在缓冲区中生成JSON文本,然后将其写入响应中然后丢弃。您可以致电json.Marshal()
创建新的json.Encoder
,将io.Writer
作为http.ResponseWriter
传递给io.Writer
,之后调用json.NewEncoder()
将直接编写JSON结果你的回复作者。
这里的一个缺点是,如果生成JSON响应失败,您可能会有一个部分发送/已提交的响应,您无法收回。如果这对你来说是一个问题,你除了在缓冲区中生成响应之外别无其他选择,如果编组成功,那么你可以立即写完整的响应。
答案 1 :(得分:6)
从here(ResponseWriter)可以看出,它是io.WriteString
方法的界面。
因此,在fmt.Fprintf
和Write(p []byte) (n int, err error)
中,两者都将Writer作为第一个参数,这也是一个包裹type Writer interface {
Write(p []byte) (n int, err error)
}
方法的接口
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))
}
所以当你调用io.WriteString(w,“blah”)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
}
或fmt.Fprintf(w,“blabla”)check here
ResponseWriter
当你在两种方法中传递w.Write([]byte("blabla\n"))
变量时,你只是间接地调用Write方法。
所以为什么不直接使用json.NewEncoder(w).Encode(wrapper)
//Encode take interface as an argument. Wrapper can be:
//wrapper := SuccessResponseWrapper{Success:true, Data:data}
来调用它。我希望你得到答案。
PS:如果你想将它作为JSON响应发送,还有一种不同的使用方式。
rethinking::map()