最后更新于2022年9月30日 10:51:01
参考:Exploring go net/http: How Server and the Handler structs works.
首先说总体的原理:其实就是先填表格,张三早上吃的包子,李四早上吃的油条。再用张三去查找,返回包子。
Handler是核心
http最核心的就是这个Handler,用来接收request并返回response,只要实现了ServeHTTP方法,就可以看做一个Handler;
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 这里头其实就是你自己写的业务
}
普通代码
首先是一段最简单直接的示例代码,当然是从参考里面抄来的,不是我写的:
// 普通写法
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello world")
})
server := http.Server{
Addr: ":9091",
}
fmt.Println("Starting server")
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
大家一般都这么写,http.HandleFunc新建一个Handler,再新建一个server;再用ListenAndServe把两者联系起来并且监听对应端口。
ListenAndServe内部会有一个隐藏的变量叫做DefaultServeMux,上面这种写法相当于把路由加到DefaultServeMux里面,再用DefaultServeMux代替server的Handler字段,也就是中间多了这么一层,详细见下文。
简单粗暴的代码
但是其实这里隐藏了部分细节,最直接的写法如下:
// 最粗暴的写法
type customHandler struct {
response string
}
func (c *customHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, c.response)
}
func main() {
server := http.Server{
Addr: ":9091",
Handler: &customHandler{"Hello world from the custom handler"},
}
fmt.Println("Starting server")
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
这里就可以看出来,显式地新建一个Handler,并且把这个Handler填充到server的Handler字段里面。
这段可以看出来服务器运行以后,具体任务是由ServeHTTP完成的,在这里是返回helloWorld信息。
使用ServeMux
但是这种方法也只能维护一个路由,需要一个方法去维护一大堆路由,也就是把customHandler变成一个结构体,于是就有了ServeMux这个东西(multiplexer服务复用器)
DefaultServeMux跟ServeMux差不多一码事
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // 保存路由
es []muxEntry // slice of entries sorted from longest to shortest. 给路由查找排序
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
这下把customHandler换成DefaultServeMux就好了,如下:
func main() {
http.DefaultServeMux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello world")
})
http.DefaultServeMux.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello test")
})
http.DefaultServeMux.HandleFunc("/debug", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello debug")
})
server := http.Server{
Addr: ":9091",
}
fmt.Println("Starting server")
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
DefaultServeMux跟server是怎么连上的?
在调用server.ListenAndServe()
的时候,一直往里面点下去:srv.Serve(ln)=>go c.serve(connCtx)里面有这么一行:
func (c *conn) serve(ctx context.Context) {
serverHandler{c.server}.ServeHTTP(w, w.req) // 就是这个ServeHTTP方法
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// 这个sh.srv就是上面代码里创建的server变量
handler := sh.srv.Handler
// 我们创建server的时候没给它分配Handler,所以就触发这个if条件了,这样就把server跟DefaultServeMux连上了。
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// .........
handler.ServeHTTP(rw, req)
}
DefaultServeMux的HandleFunc做了什么
DefaultServeMux的HandleFunc,包含了Handle函数如下。这个Handle函数就是规定一下怎么把路由加入列表里面,比如不能加入nil pattern啊、不能重复加啊,之类的。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
当然也可以自己新建ServeMux,一样的
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello world")
})
mux.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello test")
})
mux.HandleFunc("/debug", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello debug")
})
server := http.Server{
Addr: ":9091",
}
fmt.Println("Starting server")
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
ServeMux的ServeHTTP的作用
ServeHTTP其实就是Handler用前端发来的数据,处理业务的逻辑。
再复述一遍,ServeMux里面管理了很多的Handler,每个Handler都对应了自己的路径+业务逻辑。
同样的,ServeMux是用来代替customHandler的,因此它也是基于Handler这个接口的,同样也实现了ServeHTTP方法。只不过这里的ServeHTTP不是用来处理详细的业务逻辑的,而是用来告诉进来的request应该使用哪个Handler的ServeHTTP方法。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r) //选出来需要使用的Handler
h.ServeHTTP(w, r) // 调用被选出来的Handler的业务逻辑
}
世界线在这里收束了:根据我的理解,
h, _ := mux.Handler(r)
这行太简单了,不能很智能地匹配一些奇奇怪怪的路由。就给它整难一点儿,什么前缀树啊、正则匹配啊都给上上,目的都是为了解析路由然后找出来具体调用哪个逻辑。所以gorilla/mux、dimfeld/httptreemux这些玩意儿就都给安排上了。
ListenAndServe都做了什么(大概)
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
根据之前传入的port,监听一个tcp的端口,并传入srv.Serve()里面。
func (srv *Server) Serve(l net.Listener) error {
// ...
baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
这里大概就是填写baseCtx字段,使用Accept()函数做连接+失败重连,连上以后填写connCtx字段,再根据旧的tcp连接创建一个新的http连接(c := srv.newConn(rw)
),接着再新开goroutine,把新的http连接跑起来。c.serve(connCtx)里面就是等待传入request,还有调用ServeMux的ServeHTTP了。
这段代码看不懂,摆烂了,啊对对对
大概就到这里了。