繁体   English   中英

理解Go中的http handlerfunc包装器技术

[英]Understanding the http handlerfunc wrapper technique in Go

我看到Mat Ryer写一篇文章,关于如何使用服务器类型和类型的http处理程序作为func(http.ResponseWriter, *http.Request)包装器func(http.ResponseWriter, *http.Request)

我认为这是构建REST API的一种更优雅的方式,但是我完全不知道让包装器正常运行。 我要么在编译时遇到不匹配的类型错误,要么在调用时得到404。

这基本上就是我目前用于学习目的的东西。

package main

import(
   "log"
   "io/ioutil"
   "encoding/json"
   "os"
   "net/http"
   "github.com/gorilla/mux"
)

type Config struct {
   DebugLevel int `json:"debuglevel"`
   ServerPort string `json:"serverport"`
}

func NewConfig() Config {

   var didJsonLoad bool = true

   jsonFile, err := os.Open("config.json")
   if(err != nil){
      log.Println(err)
      panic(err)
      recover()
      didJsonLoad = false
   }

   defer jsonFile.Close()

   jsonBytes, _ := ioutil.ReadAll(jsonFile)

   config := Config{}

   if(didJsonLoad){
      err = json.Unmarshal(jsonBytes, &config)
      if(err != nil){
         log.Println(err)
         panic(err)
         recover()
      }
   }

   return config
}

type Server struct {
   Router *mux.Router
}

func NewServer(config *Config) *Server {
   server := Server{
      Router : mux.NewRouter(),
   }

   server.Routes()

   return &server
}

func (s *Server) Start(config *Config) {
   log.Println("Server started on port", config.ServerPort)
   http.ListenAndServe(":"+config.ServerPort, s.Router)
}

func (s *Server) Routes(){
   http.Handle("/sayhello", s.HandleSayHello(s.Router))
}

func (s *Server) HandleSayHello(h http.Handler) http.Handler {
   log.Println("before")
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
      w.Write([]byte("Hello."))
      h.ServeHTTP(w, r)
   })
}

func main() {
   config := NewConfig()
   server := NewServer(&config)
   server.Start(&config)
}

就像现在这样,我只会回到404调用localhost:8091/sayhello (是的,这是我在配置文件中设置的端口。)

之前,因为我正在使用Gorilla Mux,所以我设置了这样的处理程序:

func (s *Server) Routes(){
    s.Router.HandleFunc("/sayhello", s.HandleSayHello)
}

哪个给了我这个错误,我完全被难过了。 cannot use s.HandleSayHello (type func(http.Handler) http.Handler) as type func(http.ResponseWriter, *http.Request) in argument to s.Router.HandleFunc

我在解决方案中看到了这个SO帖子 ,我应该使用http.Handle并传入路由器。

func (s *Server) Routes(){
   http.Handle("/sayhello", s.HandleSayHello(s.Router))
}

但是现在我如何在设置路线时阻止执行实际功能? 我的print语句中的"before"在服务器启动之前显示。 我现在不认为它是一个问题,但它可能是我开始为数据库查询编写更复杂的中间件我打算用它。

进一步 研究这种技术,我发现其他读数表明我需要定义middlewarehandler类型。

我不完全理解这些例子中发生了什么,因为他们定义的类型似乎没有被使用。

此资源显示如何编写处理程序,但不显示路由的设置方式。

我确实发现Gorilla Mux已经为这些东西制作了包装器 ,但我很难理解API。

他们展示的例子是这样的:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Do stuff here
        log.Println(r.RequestURI)
        // Call the next handler, which can be another middleware in the chain, or the final handler.
        next.ServeHTTP(w, r)
    })
}

并且路由定义如下:

r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)

r.Use没有注册url路由时,用途是什么? 如何使用handler

当我的代码写得像这样,我没有编译错误,但我不明白我的函数是如何写回“Hello”的。 我想我可能在错误的地方使用w.Write

我想你可能会把“中间件”和真正的处理程序混在一起。

http处理程序

实现ServeHTTP(w http.ResponseWriter, r *http.Request)方法的类型满足http.Handler接口,因此这些类型的实例可以用作http.Handle函数或等效函数的第二个参数。 http.ServeMux.Handle方法。

一个例子可能会使这更清楚:

type myHandler struct {
    // ...
}

func (h myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(`hello world`))
}

func main() {
    http.Handle("/", myHandler{})
    http.ListenAndServe(":8080", nil)
}

http处理程序funcs

与签名功能func(w http.ResponseWriter, r *http.Request)是http处理funcs中可以转换到一个http.Handler使用http.HandlerFunc类型。 请注意,签名与http.HandlerServeHTTP方法的签名相同。

例如:

func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(`hello world`))
}

func main() {
    http.Handle("/", http.HandlerFunc(myHandlerFunc))
    http.ListenAndServe(":8080", nil)
}

表达http.HandlerFunc(myHandlerFunc)转换myHandlerFunc功能的类型http.HandlerFunc它实现了ServeHTTP方法,以便所得到的该表达式的值是有效的http.Handler ,因此它可以被传递给http.Handle("/", ...)函数调用作为第二个参数。

使用普通的http处理程序func而不是实现ServeHTTP方法的http处理程序类型是很常见的,标准库提供了替代http.HandleFunchttp.ServeMux.HandleFunc 所有HandleFunc都是我们在上面的例子中做的,它将传入的函数转换为http.HandlerFunc并用结果调用http.Handle


http中间件

具有类似于此func(h http.Handler) http.Handler的签名的func(h http.Handler) http.Handler被视为中间件。 请记住,中间件的签名不受限制,您可以使用比单个处理程序更多参数的中间件并返回更多值,但通常是一个至少需要一个处理程序并至少重新运行一个的函数新的处理程序可以被认为是中间件。

作为示例,请查看http.StripPrefix


现在让我们清楚一些明显的混乱。

#1

func (s *Server) HandleSayHello(h http.Handler) http.Handler {

方法的名称和之前使用它的方式,直接将它传递给HandleFunc ,表明你希望这是一个普通的http处理程序函数,但签名是中间件的签名,这就是你得到错误的原因:

cannot use s.HandleSayHello (type func(http.Handler) http.Handler) as type func(http.ResponseWriter, *http.Request) in argument to s.Router.HandleFunc

因此,将代码更新为类似下面的代码将消除编译错误,并且还将正确呈现"Hello." 访问/sayhello时的文字。

func (s *Server) HandleSayHello(w http.ResponseWriter, r *http.Request) {
      w.Write([]byte("Hello."))
}

func (s *Server) Routes(){
    s.Router.HandleFunc("/sayhello", s.HandleSayHello)
}

#2

就像现在这样,我只会回到404调用localhost:8091/sayhello

问题在于这两行

http.Handle("/sayhello", s.HandleSayHello(s.Router))

http.ListenAndServe(":"+config.ServerPort, s.Router)

http.Handle函数使用默认的ServeMux实例注册传入的处理程序,它不会像你s.Router那样将其注册到s.Router的gorilla路由器实例,然后你将s.Router传递给使用的ListenAndServe函数它服务于localhost:8091每个请求,并且因为s.Router没有注册处理程序,所以你得到了404


#3

但是现在我如何在设置路线时阻止执行实际功能? 我的print语句中的"before"在服务器启动之前显示。

func (s *Server) Routes(){
   http.Handle("/sayhello", s.HandleSayHello(s.Router))
}

func (s *Server) HandleSayHello(h http.Handler) http.Handler {
   log.Println("before")
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
      w.Write([]byte("Hello."))
      h.ServeHTTP(w, r)
   })
}

取决于你的“实际功能”是什么意思。 在Go中,您可以通过在名称末尾添加括号来执行函数。 所以当你设置路由时,这里执行的是http.Handle函数和HandleSayHello方法。

HandleSayHello方法在其主体中基本上有两个语句,函数调用表达式语句log.Println("before")和return语句return http.HandlerFunc(...每次调用HandleSayHello时都会执行这两个HandleSayHello但是,当调用HandleSayHello时,返回函数内的语句(处理程序)将不会被执行,而是在调用返回的处理程序时执行它们。

调用HandleSayHello时不希望打印"before" ,但是在调用返回的处理程序时是否希望打印它? 您需要做的就是将日志行向下移动到返回的处理程序:

func (s *Server) HandleSayHello(h http.Handler) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
      log.Println("before")
      w.Write([]byte("Hello."))
      h.ServeHTTP(w, r)
   })
}

这个代码当然现在没有多大意义,即使作为教育目的的例子,它也会混淆而不是澄清处理程序和中间件的概念。

相反,可以考虑这样的事情:

// the handler func
func (s *Server) HandleSayHello(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello."))
}

// the middleware
func (s *Server) PrintBefore(h http.Handler) http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
               log.Println("before") // execute before the actual handler
               h.ServeHTTP(w, r)     // execute the actual handler
       })
}

func (s *Server) Routes(){
        // PrintBefore takes an http.Handler but HandleSayHello is an http handler func so
        // we first need to convert it to an http.Hanlder using the http.HandlerFunc type.
        s.Router.HandleFunc("/sayhello", s.PrintBefore(http.HandlerFunc(s.HandleSayHello)))
}

#4

r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)

r.Use没有注册url路由时,用途是什么? 如何使用handler

Use在路由器级别注册中间件,这意味着在该路由器上注册的所有处理程序将在执行之前执行中间件。

例如,上面的代码相当于:

r := mux.NewRouter()
r.HandleFunc("/", loggingMiddleware(handler))

当然, Use并不是不必要和令人困惑的,如果你有许多端点都有不同的处理程序,并且所有端点都需要一堆中间件应用于它们,这是很有用的。

然后像这样的代码:

r.Handle("/foo", mw1(mw2(mw3(foohandler))))
r.Handle("/bar", mw1(mw2(mw3(barhandler))))
r.Handle("/baz", mw1(mw2(mw3(bazhandler))))
// ... hundreds more

可以从根本上简化:

r.Handle("/foo", foohandler)
r.Handle("/bar", barhandler)
r.Handle("/baz", bazhandler)
// ... hundreds more
r.Use(mw1, mw2, m3)

来自gorilla mux doc文件:

中间件(通常)是一小段代码,它们接受一个请求,对其执行某些操作,然后将其传递给另一个中间件或最终处理程序。

r.Use()对于注册中间件很有用。 您可以尽可能多地注册中间件。

r.HandleFunc("/hello", func (w http.ResponseWriter, r *http.Request) {
    fmt.Println("from handler")
    w.Write([]byte("Hello! \n"))
})

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // do something here
        fmt.Println("from middleware one")
        next.ServeHTTP(w, r)
    })
})

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // do another thing here
        fmt.Println("from middleware two")
        next.ServeHTTP(w, r)
    })
})

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // do something again but differently here
        fmt.Println("from middleware three")
        next.ServeHTTP(w, r)
    })
})

如果您看到上面的代码,则在每个中间件上都有语句next.ServeHTTP(w, r) 该语句用于将传入的请求继续到下一步(它可以是下一个中间件,或者是实际的处理程序)。

每个中间件总是在实际处理程序之前执行。 它自己按顺序执行,具体取决于中间件注册的顺序。

在所有中间件成功执行之后,最后一个中间件的next.ServeHTTP(w, r)将继续传入请求以转到实际处理程序(在上面的示例中,它是/hello路由的处理程序)。

当您访问/hello ,日志将打印:

from middleware one
from middleware two
from middleware three
from handler

如果您希望在某些条件下传入的请求不会继续,那么只需不要调用next.ServeHTTP(w, r) 例:

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // ...

        if someCondition {
            next.ServeHTTP(w, r)
        } else {
            http.Error(w, "some error happen", http.StatusBadRequest)
        }
    })
})

中间件通常用于在调用处理程序之前或之后对传入请求执行某些处理。 例如:CORS配置,CRSF检查,gzip压缩,日志记录等。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM