在Go语言中为多个处理程序设置HTTP头

4

我想为多个处理程序设置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)
    }
}
6个回答

4

我不确定多个处理程序的事情,但我知道为什么你写的代码失败了。关键在于这行代码:

*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会在必要时自动插入解引用符号。因为这是误导性的,所以我给你点了个踩。如果修复了,我会取消踩的。 - fuz
1
有意思的是,Go语言似乎在这种情况下并不自动解引用指针。wr.Header 无效,*wr.Header 也不行。唯一有效的方法是将其放在括号中。(*wr).Header 可以正常工作。 - Seth Archer Brown
好的,我会回滚更改。我也会在某个时候查一下。我认为混淆可能是因为Go语言使用“.”运算符来表示本地和指针函数调用(因此是按值传递和按引用传递),这与此不完全相同。 - joshlf
1
我希望我有时间更深入地研究这个问题。这绝对应该被记录在某个地方,因为这可能会非常令人沮丧的小错误。 - Seth Archer Brown
@SethArcher和joshlf,也许你们已经解决了这个问题,但最终的原因是因为参数是一个指向接口的指针,这实际上是一个指向接口中隐藏的未导出类型的指针。应该使用func WriteJSON(wr http.ResponseWriter, ...)而不是func WriteJSON(wr *http.ResponseWriter, ...)。很少需要使用接口的指针。请参见这里 - chappjc
Header().Add() 方法可用于设置“Content-Type”头两次,但这取决于客户端如何解释它。在我的情况下,我在包装器中将“Content-Type”设置为“application/json”,当我使用 Header().Add() 时,它只是在响应中添加了另一个“Content-Type”。第一个仅被 Chrome 在 iOS 上看到,而我测试的其他每个客户端都可以接受最近设置的标头。 - Gaurav Ojha

3

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

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

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

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

还可以查看net/http文档,其中有更多示例


0

http.ResponseWriter 是一个接口。

你应该避免使用指向接口的指针。在 net/http/server.go 中,未公开的 response 结构体是实现 ResponseWriter 的实际类型,当服务器调用你的处理程序时,重要的是,当服务器实际调用处理程序的 ServeHTTP 时, 它会传递一个*response。它已经是一个指针了,但你看不到,因为 ResonseWriter 是一个接口。(响应指针是由 这里 创建的,由 (c *conn).readRequest 创建。(链接可能是错误的行数,但你应该能够找到它们)。

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

ServeHTTP(w ResponseWriter, r *Request)

也就是说,这个声明已经允许一个指向实现了 ResponseWriter 接口的结构体的指针,因此不需要返回指向 ResponseWriter 的指针。


0

我用错误处理程序包装我的处理程序,该处理程序调用我的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>
`

0

由于我刚接触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"

0

我最终做的事情:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接