7.1.2. WriteResponse

我的第二个例子受到了 Errors are values 博客文章[10]的启发。

在本章前面我们已经看过处理打开、写入和关闭文件的示例。错误处理是存在的,但是接收范围内的,因为操作可以封装在诸如 ioutil.ReadFileioutil.WriteFile 之类的辅助程序中。但是,在处理底层网络协议时,有必要使用 I/O 原始的错误处理来直接构建响应,这样就可能会变得重复。看一下构建 HTTP 响应的 HTTP 服务器的这个片段。

  1. type Header struct {
  2. Key, Value string
  3. }
  4. type Status struct {
  5. Code int
  6. Reason string
  7. }
  8. func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
  9. _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
  10. if err != nil {
  11. return err
  12. }
  13. for _, h := range headers {
  14. _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)
  15. if err != nil {
  16. return err
  17. }
  18. }
  19. if _, err := fmt.Fprint(w, "\r\n"); err != nil {
  20. return err
  21. }
  22. _, err = io.Copy(w, body)
  23. return err
  24. }

首先,我们使用 fmt.Fprintf 构造状态码并检查错误。 然后对于每个标题,我们写入键值对,每次都检查错误。 最后,我们使用额外的 \r\n 终止标题部分,检查错误之后将响应主体复制到客户端。 最后,虽然我们不需要检查 io.Copy 中的错误,但我们需要将 io.Copy 返回的两个返回值形式转换为 WriteResponse 的单个返回值。

这里很多重复性的工作。 我们可以通过引入一个包装器类型 errWriter 来使其更容易。

errWriter 实现 io.Writer 接口,因此可用于包装现有的 io.WritererrWriter 写入传递给其底层 writer,直到检测到错误。 从此时起,它会丢弃任何写入并返回先前的错误。

  1. type errWriter struct {
  2. io.Writer
  3. err error
  4. }
  5. func (e *errWriter) Write(buf []byte) (int, error) {
  6. if e.err != nil {
  7. return 0, e.err
  8. }
  9. var n int
  10. n, e.err = e.Writer.Write(buf)
  11. return n, nil
  12. }
  13. func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
  14. ew := &errWriter{Writer: w}
  15. fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
  16. for _, h := range headers {
  17. fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
  18. }
  19. fmt.Fprint(ew, "\r\n")
  20. io.Copy(ew, body)
  21. return ew.err
  22. }

errWriter 应用于 WriteResponse 可以显着提高代码的清晰度。 每个操作不再需要自己做错误检查。 通过检查 ew.err 字段,将错误报告移动到函数末尾,从而避免转换从 io.Copy 的两个返回值。