关于Handle、HandleFunc、Handler和HandlerFunc

这篇文章很大程度上解决了我对golang的net\httpHandleHandleFuncHandlerHandlerFunc的疑惑,但是该博主的主站貌似不能正常访问了,所以进行转载,以便复习。

转自:关于Handle、HandleFunc、Handler和HandlerFunc | ruomu (gitee.io)

在go中,可以很简单实现一个http服务器。

但是在使用过程中,遇到了一些容易使人迷惑的类型、接口或方法名。

于是决定深入了解并记录一下它们的区别。

ps: 本文的前置知识是,需要对go中的面向对象和接口有一定的了解。

我们可以这样创建一个http server

package main

import (
	"fmt"
	"net/http"
)

type Hello struct {
	name string
}

func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello %s", h.name)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello world")
}

func main() {
	http.HandleFunc("/", indexHandler)
	http.Handle("/hello", &Hello{"ruomu"})
	http.ListenAndServe(":8000", nil)
}

这时如果你访问 localhost:8080/ 会看到 hello world

如果访问localhost:8080/hello会看到hello ruomu

Handler

我们翻看源码net/http/server.go可以看到:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

所以所有实现了 ServeHTTP 方法的结构体创建出来的对象,都可以看做是一个Handler类型的对象。

Handle

源码:

func Handle(pattern string, handler Handler) {
  DefaultServeMux.Handle(pattern, handler) 
}

可以看到,第一个参数不用说了,是绑定的路由,第二个参数是一个 Handler类型的对象。

再回头看前文的代码:

...
type Hello struct {
	name string
}
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello %s", h.name)
}
...
http.Handle("/hello", &Hello{"ruomu"})

ServeHTTP 方法其实对应的就是对应路由被访问时,对应的执行处理方法,至于具体什么时候被执行,如果绑定这些细节这里先不说了。

是不是知道为什么,访问 localhost:8080/hello 会出现 hello ruomu了。

HandleFunc

源码:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

我们发现和 Handle 的区别:

第二个参数不一样,Handle 的第二个参数是一个Handler 类型的对象,而HandleFunc的第二个参数,直接就是一个 func(ResponseWriter, *Request)类型的函数 。

里面执行的语句也有点不大相同,分别是 DefaultServeMux.Handle(pattern, handler)DefaultServeMux.HandleFunc(pattern, handler)。它们也只是调用了不同的方法,并且第二个参数不同。。。感觉像是在套娃? 这里先不展开说了,我们只需要知道它们是正正的注册路由的对象就行了。

试想一下,如果我们需要绑定1000个路由和其对应的处理方法,那我们是不是要写1000个struct?

type r1 struct {}
func (route r1) ServeHTTP(w http.ResponseWriter, r *http.Request) {...}
type r2 struct {}
func (route r2) ServeHTTP(w http.ResponseWriter, r *http.Request) {...}
.
.
.
type r1000 struct {}
func (route r1000) ServeHTTP(w http.ResponseWriter, r *http.Request) {...}

func main() {
	http.Handle("/r1", &r1{})
  http.Handle("/r2", &r2{})
  ...
	http.Handle("/r100", &r1000{})
	http.ListenAndServe(":8000", nil)
}

这样的代码是难以阅读并且难以维护的,我们根本就不需要这么多结构体,我们最终关心的其实时处理方法 ServeHTTP。

看下前文的代码:

...
func indexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello world")
}
...
http.HandleFunc("/", indexHandler)
...

是不是已经知道,如何改造绑定1000个路由的代码了。。

HandlerFunc

源码:

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

这个写法很有意思啊。。

其实目的也是解决绑定1000个路由需要定义1000个struct的问题。

使用 HandlerFunc 进行强制类型转换,因为HandlerFunc也实现了 ServeHTTP方法,所以可以当做一个Handler类型的对象作为http.Handle的第二个参数。

然后Handler的ServeHTTP方法中使用 f(w,r),啊这。。秒啊。。

...
func indexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello world")
}
...
http.Handle("/", http.HandlerFunc(indexHandler))
...

现在是不是疑惑,HandleFunc 和 HandlerFunc有区别?为什么要两种写法呢。。

其实我们继续看HandleFunc的源码就知道了:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}
...
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

可以看到,HandleFunc 最后还是使用了 Handle(pattern, HandlerFunc(handler)) 来处理的。。

DefaultServeMux

这里顺便说一下,本文一开始的代码中,为什么 http.ListenAndServe(":8000", nil)第二个参数要传一个 nil 呢。

关于ServeMux到时候另写一片文章,这里直接不细说。

http.ListenAndServe 的第二个参数其实是一个ServeMux对象,如果不传的话,就会使用默认的 DefaultServeMux。

这篇文章代码里使用的所有绑定路由的方法,都是绑定在 DefaultServeMux上的,如果我们使用 NewServeMux创建一个自定义对象,并且当做 http.ListenAndServe 的第二个参数传入的话,它们就会失效。

net/http/server.go:2835
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}
	handler.ServeHTTP(rw, req)
}

总结

  • Handler 是一个接口,只有一个 ServeHTTP方法。
  • Handle 绑定路由第二个参数需要传一个Handler类型的对象。
  • HandlerFunc 写法比较有意思,他也实现了 ServeHTTP方法,可以强转一个函数为Handler类型的对象,并当做Handle 的第二个参数。
  • HandleFunc(pattern, handle) 写法等于 Handle(pattern, HandlerFunc(handle))

ps: 如果看不懂我写得东西很正常,本来就是记录不是什么教程,写得很随意。这里建议直接看参考链接里的文章。多看多练。

参考链接:

Licensed under CC BY-NC-SA 4.0
自认为是幻象波普星的来客
Built with Hugo
主题 StackJimmy 设计