一尘不染

从外部Go中间件控制HTTP标头

go

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

// 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),我最终在响应中会有两个Server标头。

$ 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)
    })
}

但是,ServeHTTP的语义不允许这样

ServeHTTP应该将回复标头和数据写入ResponseWriter,然后返回。返回信号表明请求已完成;在ServeHTTP调用完成之后或与之同时使用ResponseWriter或从Request.Body中读取是无效的。

我已经看到一些与此有关的丑陋骇客,例如httputil.ReverseProxy.ServeHTTP中的代码,该代码复制标头并重写响应代码,或者使用httptest.ResponseRecorder将整个正文读入字节缓冲区。

我也可以颠倒传统的中间件顺序,并以某种方式将Server中间件放在最后,或者使其成为最内部的中间件。

我缺少任何简单的方法吗?


阅读 234

收藏
2020-07-02

共1个答案

一尘不染

您可以定义一个自定义类型,该类型将包装ResponseWriter并在写入所有标头之前插入Server标头,但要付出额外的间接层费用。这是一个例子:

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/

2020-07-02