【问题标题】:Set http headers for multiple handlers in go在 go 中为多个处理程序设置 http 标头
【发布时间】:2013-07-13 19:20:28
【问题描述】:

我正在尝试为多个处理程序设置一个 http 标头。我的第一个想法是制作一个自定义写入函数,该函数将在编写响应之前设置标题,就像底部的代码示例一样。

但是,当我将指针传递给 http.ResponseWriter 并尝试从我的函数中访问它时,它会告诉我“类型 *http.ResponseWriter 没有 Header 方法”。

为多个处理程序设置标头的最佳方法是什么,以及为什么指针不按我想要的方式工作?

func HelloServer(w http.ResponseWriter, req *http.Request) {
    type Message struct {
        Name string
        Body string
        Time int64
    }

    m := Message{"Alice", "Hello", 1294706395881547000}

    b, _ := json.Marshal(m)
    WriteJSON(&w, b)
}

func WriteJSON(wr *http.ResponseWriter, rawJSON []byte) {
    *wr.Header().Set("Content-Type", "application/json")

    io.WriteString(*wr, string(rawJSON))
}

func main() {
    http.HandleFunc("/json", HelloServer)

    err := http.ListenAndServe(":9000", nil)
    if err != nil {
    log.Fatal("ListenAndServer: ", err)
    }
}

【问题讨论】:

    标签: go


    【解决方案1】:

    我不确定多个处理程序的事情,但我知道您编写的代码为什么会失败。关键是那行:

    *wr.Header().Set("Content-Type", "application/json")
    

    由于运算符优先级而被解释为:

    *(wr.Header().Set("Content-Type", "application/json"))
    

    由于wr 的类型为*http.ResponseWriter,它是 和接口的指针,而不是接口本身,所以这不起作用。我假设您知道这一点,这就是您这样做的原因*wr。我假设您对编译器的暗示是:

    (*wr).Header().Set("Content-Type", "application/json")
    

    如果我没记错的话,那应该可以编译并正常运行。

    【讨论】:

    • 实际上,您不需要取消引用。 Go 会在必要时自动插入取消引用。因为误导而被否决。修复后将删除反对票。
    • 有趣的是,go 应该自动取消引用指针,因为在这种情况下似乎没有。 wr.Header 不起作用 *wr.Header 也不起作用。唯一有效的是当我将它包裹在括号中时。 (*wr).Header 工作正常。
    • 哈哈好吧,我会回滚更改。我也会在某个时候查一下。我认为混淆可能是go使用了“。”运算符表示本地和指针函数调用(因此按值传递和按引用传递),这与 this 不太一样。
    • 我希望我有时间更深入地研究这一点。这绝对应该记录在某个地方,因为这有点令人沮丧。
    • @SethArcher 和 joshlf,也许你已经想通了,但最终的原因是因为参数是指向接口的指针,这实际上是指向隐藏在界面。它应该是 func WriteJSON(wr http.ResponseWriter, ... 而不是 func WriteJSON(wr *http.ResponseWriter, ...)。很少使用指向接口的指针。请参阅here
    【解决方案2】:

    您不需要使用*wr,因为它已经引用了一个指针。

    wr.Header().Set("Content-Type", "application/json") 应该足够了。

    如果您想为每个请求设置“全局”标头,您可以创建一个满足 http.HandleFunc (go.auth has a good example) 的函数,然后像这样包装您的处理程序:

    http.HandleFunc("/hello", Defaults(helloHandler))

    还可以查看net/http 文档which has further examples

    【讨论】:

      【解决方案3】:

      我用错误处理程序包装我的处理程序 它调用了我的 AddSafeHeader 函数。

      我基于http://golang.org/doc/articles/error_handling.html 但它不使用 ServeHTTP,因此它适用于 appstats:

      http.Handle("/", appstats.NewHandler(util.ErrorHandler(rootHandler)))
      

      这里:

      package httputil
      
      import (
        "appengine"
        "net/http"
        "html/template"
      )
      
      func AddSafeHeaders(w http.ResponseWriter) {
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-XSS-Protection", "1; mode=block")
        w.Header().Set("X-Frame-Options", "SAMEORIGIN")
        w.Header().Set("Strict-Transport-Security", "max-age=2592000; includeSubDomains")
      }
      
      // Redirect to a fixed URL
      type redirectHandler struct {
        url  string
        code int
      }
      
      func (rh *redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        Redirect(w, r, rh.url, rh.code)
      }
      
      func Redirect(w http.ResponseWriter, r *http.Request, urlStr string, code int) {
        AddSafeHeaders(w)
        http.Redirect(w, r, urlStr, code)
      }
      
      // RedirectHandler returns a request handler that redirects
      // each request it receives to the given url using the given
      // status code.
      func RedirectHandler(url string, code int) http.Handler {
        return &redirectHandler{url, code}
      }
      
      func ErrorHandler(fn func(appengine.Context, http.ResponseWriter, *http.Request)) func(appengine.Context, http.ResponseWriter, *http.Request) {
        return func(c appengine.Context, w http.ResponseWriter, r *http.Request) {
          defer func() {
            if err, ok := recover().(error); ok {
              c.Errorf("%v", err)
              w.WriteHeader(http.StatusInternalServerError)
              errorTemplate.Execute(w, err)
            }
          }()
          AddSafeHeaders(w)
          fn(c, w, r)
        }
      }
      
      // Check aborts the current execution if err is non-nil.
      func Check(err error) {
        if err != nil {
          panic(err)
        }
      }
      
      var errorTemplate = template.Must(template.New("error").Parse(errorTemplateHTML))
      
      const errorTemplateHTML = `
      <html>
      <head>
              <title>XXX</title>
      </head>
      <body>
              <h2>An error occurred:</h2>
              <p>{{.}}</p>
      </body>
      </html>
      `
      

      【讨论】:

        【解决方案4】:

        http.ResponseWriter 是一个接口。

        您可能不应该使用指向接口的指针。在 net/http/server.go 中,未导出的 response 结构是在服务器调用您的处理程序时实现 ResponseWriter 的实际类型,重要的是,when the server actually calls the handler's ServeHTTP,它传递了一个 *response。它已经是一个指针,但你看不到它,因为ResonseWriter 是一个接口。 (响应指针由(c *conn).readRequest 创建here。(这些链接将来可能指向错误的行,但您应该能够找到它们)。

        这就是为什么实现Handler所需的ServeHTTP函数是:

        ServeHTTP(w ResponseWriter, r *Request)
        

        即不是指向ResponseWriter 的指针,因为此声明已经允许指向实现ResponseWriter 接口的结构的指针。

        【讨论】:

          【解决方案5】:

          由于我是 Go 新手,我根据 elithrar 的回答创建了一个人为设计的最小示例,该示例展示了如何轻松地将标头添加到所有路由/响应中。我们这样做,通过创建一个满足http.HandlerFunc 接口的函数,然后包装路由处理函数:

          package main
          
          import (
              "encoding/json"
              "log"
              "net/http"
          
          
              "github.com/gorilla/mux"
          )
          
          
          // Hello world.
          func Hello(w http.ResponseWriter, r *http.Request) {
              json.NewEncoder(w).Encode("Hello World")
          }
          
          // HelloTwo world
          func HelloTwo(w http.ResponseWriter, r *http.Request) {
              json.NewEncoder(w).Encode("Hello Two World")
          }
          
          // JSONHeaders conforms to the http.HandlerFunc interface, and
          // adds the Content-Type: application/json header to each response.
          func JSONHeaders(handler http.HandlerFunc) http.HandlerFunc {
              return func(w http.ResponseWriter, r *http.Request) {
                  w.Header().Set("Content-Type", "application/json")
                  handler(w, r)
              }
          }
          
          func main() {   
              router := mux.NewRouter()
              // Now, instead of calling your handler function directly, pass it into the wrapper function.
              router.HandleFunc("/", JSONHeaders(Hello)).Methods("GET") 
              router.HandleFunc("/hellotwo", JSONHeaders(HelloTwo)).Methods("GET")
          
              log.Fatal(http.ListenAndServe(":3000", router))
          }
          
          

          结果:

          $ go run test.go &
          $ curl -i localhost:3000/
          HTTP/1.1 200 OK
          Content-Type: application/json
          Date: Thu, 28 Feb 2019 22:27:04 GMT
          Content-Length: 14
          
          "Hello World"
          

          【讨论】:

            【解决方案6】:

            我最终做了什么:

            // Accepts a user supplied http.HandlerFunc and then modifies it in various ways. In this case, it adds two new headers.
            func CommonlHandler(h http.HandlerFunc) http.HandlerFunc {
                return func (rs http.ResponseWriter, rq *http.Request) {
                    rs.Header().Add("Server", "Some server")
                    rs.Header().Add("Cache-Control", "no-store")
                    h(rs, rq)
            }
            
            // somewhere down the line, where you're setting up http request handlers
            
            serveMux := http.NewServeMux()
            
            serveMux.HandleFunc("/", CommonHandler(func(rs http.ResponseWriter, rq *http.Request) {
                // Handle request as usual. Since it's wrapped in the CommonHandler and we've set some headers there, responses to requests to "/" will contain those headers.
                // Be mindful what modifications you're doing here. If, for ex., you decide you want to apply different caching strategy than the Common one, since this will be passed to the CommonHandler, your changes will be overwritten and lost. So it may be a good idea to introduce checks in CommonHandler to determine whether the header exists, before you decide to create it.
            }))
            
            serveMux.HandleFunc("/contact", CommonHandler(func(rs http.ResponseWriter, rq *http.Request) {
                // handle another request as usual
            }))
            

            【讨论】:

              猜你喜欢
              • 2011-04-09
              • 2013-08-03
              • 1970-01-01
              • 2023-01-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-08-06
              • 1970-01-01
              相关资源
              最近更新 更多