我的第二个例子受到了 Errors are values
博客文章[10]的启发。
在本章前面我们已经看过处理打开、写入和关闭文件的示例。错误处理是存在的,但是接收范围内的,因为操作可以封装在诸如 ioutil.ReadFile
和 ioutil.WriteFile
之类的辅助程序中。但是,在处理底层网络协议时,有必要使用 I/O
原始的错误处理来直接构建响应,这样就可能会变得重复。看一下构建 HTTP
响应的 HTTP
服务器的这个片段。
type Header struct {
Key, Value string
}
type Status struct {
Code int
Reason string
}
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
_, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
if err != nil {
return err
}
for _, h := range headers {
_, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)
if err != nil {
return err
}
}
if _, err := fmt.Fprint(w, "\r\n"); err != nil {
return err
}
_, err = io.Copy(w, body)
return err
}
首先,我们使用 fmt.Fprintf
构造状态码并检查错误。 然后对于每个标题,我们写入键值对,每次都检查错误。 最后,我们使用额外的 \r\n
终止标题部分,检查错误之后将响应主体复制到客户端。 最后,虽然我们不需要检查 io.Copy
中的错误,但我们需要将 io.Copy
返回的两个返回值形式转换为 WriteResponse
的单个返回值。
这里很多重复性的工作。 我们可以通过引入一个包装器类型 errWriter
来使其更容易。
errWriter
实现 io.Writer
接口,因此可用于包装现有的 io.Writer
。 errWriter
写入传递给其底层 writer
,直到检测到错误。 从此时起,它会丢弃任何写入并返回先前的错误。
type errWriter struct {
io.Writer
err error
}
func (e *errWriter) Write(buf []byte) (int, error) {
if e.err != nil {
return 0, e.err
}
var n int
n, e.err = e.Writer.Write(buf)
return n, nil
}
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
ew := &errWriter{Writer: w}
fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
for _, h := range headers {
fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
}
fmt.Fprint(ew, "\r\n")
io.Copy(ew, body)
return ew.err
}
将 errWriter
应用于 WriteResponse
可以显着提高代码的清晰度。 每个操作不再需要自己做错误检查。 通过检查 ew.err
字段,将错误报告移动到函数末尾,从而避免转换从 io.Copy
的两个返回值。
最后编辑: kuteng 文档更新时间: 2021-01-09 21:51 作者:kuteng