从外部 Go 中间件控制 HTTP headers

Control HTTP headers from outer Go middleware

假设我在 Go 中有一个中间件,我想用我自己的值覆盖任何现有的 Server headers。

// Server attaches a Server header to the response.
func Server(h http.Handler, serverName string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Server", serverName)
        h.ServeHTTP(w, r)
    })
}

然后我将它添加到这样的响应链中

http.Handle("/", authHandler)
http.ListenAndServe(":8000", Server(http.DefaultServeMux, "myapp"))

不幸的是,如果 authHandler 或任何由 DefaultServeMux 调用 w.Header().Add("Server", "foo")(比如 httputil.ReverseProxy.ServeHTTP),我最终会在响应中得到两个服务器 headers。

$ http localhost:5962/v1/hello_world
HTTP/1.1 200 OK
Content-Length: 11
Content-Type: text/plain
Date: Tue, 12 Jul 2016 04:54:04 GMT
Server: inner-middleware
Server: myapp

我真正想要的是这样的:

// Server attaches a Server header to the response.
func Server(h http.Handler, serverName string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h.ServeHTTP(w, r)
        w.Header().Set("Server", serverName)
    })
}

然而,这是disallowed by the semantics of ServeHTTP:

ServeHTTP should write reply headers and data to the ResponseWriter and then return. Returning signals that the request is finished; it is not valid to use the ResponseWriter or read from the Request.Body after or concurrently with the completion of the ServeHTTP call.

我看到了一些相当丑陋的黑客攻击,例如 httputil.ReverseProxy.ServeHTTP 中的代码,它复制 headers 并重写响应代码,或者使用 httptest.ResponseRecorder,它读取整个 body 放入字节缓冲区。

我也可以颠倒传统的中间件顺序,将我的服务器中间件放在最后,或者使它成为 inner-most 中间件。

有没有我遗漏的简单方法?

使用 WriteHeader(Status) 后无法更改 headers (add/remove/modify)。

要使其生效,请更改为:

// Server attaches a Server header to the response.
func Server(h http.Handler, serverName string) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Server", serverName)
    h.ServeHTTP(w, r)
  })
}

您可以定义一个自定义类型,它包装 ResponseWriter 并在写入所有 header 之前插入服务器 header,但代价是额外的间接层。这是一个例子:

type serverWriter struct {
    w           http.ResponseWriter
    name        string
    wroteHeader bool
}

func (s serverWriter) WriteHeader(code int) {
    if s.wroteHeader == false {
        s.w.Header().Set("Server", s.name)
        s.wroteHeader = true
    }
    s.w.WriteHeader(code)
}

func (s serverWriter) Write(b []byte) (int, error) {
    return s.w.Write(b)
}

func (s serverWriter) Header() http.Header {
    return s.w.Header()
}

// Server attaches a Server header to the response.
func Server(h http.Handler, serverName string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        sw := serverWriter{
            w:           w,
            name:        serverName,
            wroteHeader: false,
        }
        h.ServeHTTP(sw, r)
    })
}

我在这里写了更多关于这个的内容。 https://kev.inburke.com/kevin/how-to-write-go-middleware/