我看到了article written by Mat Ryer,该书介绍了如何使用服务器类型和HTTP处理程序,它们是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
我在this SO post的解决方案中看到应该使用http.Handle
并通过路由器。
func (s *Server) Routes(){
http.Handle("/sayhello", s.HandleSayHello(s.Router))
}
但是现在如何设置路由时阻止实际功能执行?我的打印语句中的"before"
在服务器启动之前显示。我现在不认为这是一个问题,但是可能是我开始打算为数据库查询编写更复杂的中间件之后。
Researching这种技术further,我发现其他读数表明我需要定义middleware
或handler
类型。
我不完全理解这些示例中发生的事情,因为它们定义的类型似乎没有被使用。
This resource显示如何编写处理程序,但不显示路由的设置方式。
我确实发现Gorilla Mux拥有built in wrappers的东西,但是我很难理解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
。
答案 0 :(得分:4)
来自gorilla mux文档文件:
中间件是(通常)一小段代码,它们接受一个请求,对其进行处理,然后将其传递给另一个中间件或最终处理程序。
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压缩,日志记录等。
答案 1 :(得分:4)
我认为您可能会将“中间件”与实际的处理程序混在一起。
实现ServeHTTP(w http.ResponseWriter, r *http.Request)
方法的类型满足http.Handler
接口,因此,这些类型的实例可以用作http.Handle
函数的第二个参数或等效的{ {3}}方法。
一个示例可能会更清楚:
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)
}
签名为func(w http.ResponseWriter, r *http.Request)
的函数是http处理函数,可以使用http.ServeMux.Handle
类型将其转换为http.Handler
。请注意,签名与http.Handler
的{{1}}方法的签名相同。
例如:
ServeHTTP
表达式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处理程序函数代替实现http.Handle("/", ...)
方法的http处理程序类型很常见,因此标准库提供了替代方法http.HandlerFunc
和http.HandleFunc
。 ServeHTTP
所做的就是上面示例中的操作,它将传入的函数转换为HandleFunc
并调用http.HandlerFunc
并返回结果。
签名类似于此http.Handle
的功能被视为中间件。请记住,中间件的签名不受限制,您可以让中间件接受比单个处理程序更多的参数,并且还返回更多的值,但通常来说,一个函数至少需要一个处理程序并重新运行至少一个处理程序。新的处理程序可以视为中间件。
现在让我们清除一些明显的困惑。
func(h http.Handler) http.Handler
方法的名称以及您之前使用它的方式,将其直接传递给func (s *Server) HandleSayHello(h http.Handler) http.Handler {
,建议您将其设为普通的http处理函数,但这是中间件的签名,这就是原因对于您遇到的错误:
HandleFunc
因此,将您的代码更新为类似以下代码的代码将消除该编译错误,并在访问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
目前,我只会返回404调用
func (s *Server) HandleSayHello(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello.")) } func (s *Server) Routes(){ s.Router.HandleFunc("/sayhello", s.HandleSayHello) }
。
问题出在这两行
localhost:8091/sayhello
和
http.Handle("/sayhello", s.HandleSayHello(s.Router))
http.ListenAndServe(":"+config.ServerPort, s.Router)
函数将传入的处理程序注册到http.StripPrefix
,您似乎并不假定它在http.Handle
的大猩猩路由器实例中注册它,然后您将传递s.Router
到s.Router
函数中,该函数使用它来处理来自ListenAndServe
的每个请求,并且由于localhost:8091
没有向其注册处理程序,因此您可以获得s.Router
。
但是现在我如何在设置时阻止实际功能执行 我的路线?我的印刷声明中的
404
出现在 服务器启动。
"before"
取决于“实际功能”的含义。在Go中,您可以通过在函数名称的末尾添加括号来执行函数。因此,在设置路由时,这里执行的是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)
})
}
函数和http.Handle
方法。
HandleSayHello
方法在其主体中本质上有两个语句,即函数调用表达式语句HandleSayHello
和return语句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)))
}
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)