我认为您可能会将“中间件”与真正的处理程序混淆。
http处理程序
实现的类型ServeHTTP(w http.ResponseWriter, r *http.Request)
方法满足http.Handler https://golang.org/pkg/net/http/#Handler接口,因此这些类型的实例可以用作http.Handle https://golang.org/pkg/net/http/#Handle函数或等效函数http.ServeMux.Handle https://golang.org/pkg/net/http/#ServeMux.Handle method.
一个例子可能会让这一点更清楚:
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 处理函数
带签名的函数func(w http.ResponseWriter, r *http.Request)
是可以转换为的 http 处理函数http.Handler
使用http.HandlerFunc https://golang.org/pkg/net/http/#HandlerFunc类型。请注意,签名与签名者的签名相同http.Handler
's ServeHTTP
method.
例如:
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 处理程序函数而不是实现以下功能的 http 处理程序类型ServeHTTP
方法很常见,标准库提供了替代方法http.HandleFunc https://golang.org/pkg/net/http/#HandleFunc and http.ServeMux.HandleFunc https://golang.org/pkg/net/http/#ServeMux.HandleFunc. All HandleFunc
所做的就是我们在上面的示例中所做的,它将传入的函数转换为http.HandlerFunc
并打电话http.Handle
与结果。
http中间件
具有与此类似的签名的函数func(h http.Handler) http.Handler
被视为中间件。请记住,中间件的签名不受限制,您可以拥有比单个处理程序接受更多参数并返回更多值的中间件,但一般来说,一个函数至少接受一个处理程序并返回至少一个新的处理程序可以被认为是中间件。
举个例子看看http.StripPrefix https://golang.org/pkg/net/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))
and
http.ListenAndServe(":"+config.ServerPort, s.Router)
The http.Handle
func 将传入的处理程序注册到默认 ServeMux 实例 https://golang.org/pkg/net/http/#DefaultServeMux,它不会将其注册到 gorilla 路由器实例中s.Router
正如你似乎假设的那样,然后你就通过了s.Router
to the ListenAndServe
函数使用它来服务每个请求localhost:8091
,并且自从s.Router
没有注册处理程序,您会得到404
.
#3
但是现在当我设置时如何防止实际函数执行
我的路线?这"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
method.
The HandleSayHello
方法的主体中有两个语句,即函数调用表达式语句log.Println("before")
和返回语句return http.HandlerFunc(...
每次调用时都会执行这两个操作HandleSayHello
。但是,当您调用时,返回函数(处理程序)内的语句将不会被执行HandleSayHello
,相反,它们将在调用返回的处理程序时执行。
你不想要"before"
打印时HandleSayHello
被调用,但您希望在调用返回的处理程序时打印它?您需要做的就是将日志行向下移动到返回的处理程序:
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)