• 7.1.2. WriteResponse

    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 的两个返回值。