Post

net/http包源码解读

使用net/http包编写一个最简单的Web服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", index)
	log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

func index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world!")
}

运行截图

Web服务器一次请求的流程如下:

客户端→request→多路器(multiplexer)/路由器(router)→handler→response→客户端

这一过程的核心是路由,即使用多路器找到URL模式对应的处理函数,因此net/http包中最重要的概念就是多路器和handler,分别对应ServeMux结构体和Handler接口

main()函数的第1行调用http.HandleFunc()函数进行路由注册,第2行调用http.ListenAndServe()函数监听端口启动服务

http.HandleFunc()函数调用默认多路器的同名方法:

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

http.ListenAndServe()函数创建了一个Server实例,之后调用该实例的同名方法:

1
2
3
4
5
6
7
8
9
10
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

type Server struct {
	Addr string
	Handler Handler
	// ...
}

Handler接口

http.ListenAndServe()的第二个参数和Server的第二个字段都是Handler类型

Handler是一个接口,只有一个ServeHTTP()方法:

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

Handler接口描述的是“给定请求能够返回响应的对象”,即执行实际的业务逻辑,handler函数就是这样的对象

示例代码中的index()就是一个handler函数,该函数的参数类型与ServeHTTP()方法相同,但函数名不同

为了能够将handler函数包装为Handler接口值,net/http包定义了一个HandlerFunc类型:

1
2
3
4
5
type HandlerFunc func(ResponseWriter, *Request)

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

HandlerFunc是一个函数类型,且实现了Handler接口,其ServeHTTP()方法就是调用函数本身,因此HandlerFunc(index)是一个Handler类型的值,其ServeHTTP()方法就是调用index()函数

http.HandleFunc()函数就利用了这种转换,http.HandleFunc("/", index)等价于http.Handle("/", http.HandlerFunc(index))

多路器ServeMux

ServeMux结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry
	hosts bool
}

type muxEntry struct {
	h       Handler
	pattern string
}

可以看到ServeMux本质上就是URL模式到handler的映射,存储在其m字段中

可以使用http.NewServeMux()函数创建一个新的ServeMux,也可以直接使用默认的DefaultServeMuxhttp.HandleFunc()就使用了DefaultServeMux

1
2
3
4
func NewServeMux() *ServeMux { return new(ServeMux) }

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

示例代码中执行完main()函数第1行后http.DefaultServeMux的值如下:

DefaultServMux的值

ServeMux的两大功能是路由注册和路由查找

路由注册

路由注册由Handle()HandleFunc()方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	// ...
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	// ...
}

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	// ...
	mux.Handle(pattern, HandlerFunc(handler))
}

可以看到Handle()方法就是将指定的URL模式和handler写入映射,而HandleFunc()方法只是一个快捷操作

由于http.Handle()http.HandleFunc()分别调用了DefaultServeMux.Handle()DefaultServeMux.HandleFunc(),因此

1
2
http.HandleFunc("/", index)
log.Fatal(http.ListenAndServe("localhost:8000", nil))

等价于

1
2
3
mux := http.NewServeMux()
mux.HandleFunc("/", index)
log.Fatal(http.ListenAndServe("localhost:8000", mux))

http.ListenAndServe()的第二个参数是用于处理所有请求的handler,如果是nil则使用DefaultServeMux

路由查找

上面最后一行代码将mux作为http.ListenAndServe()的第二个参数,因为ServeMux也实现了Handler接口

1
2
3
4
5
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	// ...
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

其中mux.Handler(r)可以理解为mux.m[r.URL.Path],即在映射中查找request的URL对应的handler

上面提到,Handler接口描述的是“给定请求能够返回响应的对象”,ServeMux实现这一功能的方式就是根据请求的URL在映射中找到handler并调用该handler,这也是ServeMux的路由查找功能

处理客户端请求

注册好路由后还需要启动服务器监听端口

http.ListenAndServe()创建了一个Server实例并调用其同名方法

(*Server).ListenAndServe()调用net.Listen()监听端口,并使用其返回的listener调用自己的Serve()方法

1
2
3
4
5
6
7
8
func (srv *Server) ListenAndServe() error {
	// ...
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(ln)
}

(*Server).Serve()遵循套接字编程标准:主体是一个无限循环,每次循环调用Listener.Accept()接受客户端请求,返回一个连接对象,最后启动一个goroutine调用连接对象的serve()方法来处理客户端请求

1
2
3
4
5
6
7
8
9
10
func (srv *Server) Serve(l net.Listener) error {
	// ...
	for {
		rw, err := l.Accept()
		// ...
		c := srv.newConn(rw)
		// ...
		go c.serve(connCtx)
	}
}

(*conn).serve()方法的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (c *conn) serve(ctx context.Context) {
	// ...
	for {
		w, err := c.readRequest(ctx)
		// ...
		if err != nil {
			// return
		}

		// ...
		serverHandler{c.server}.ServeHTTP(w, w.req)
		// ...
	}
}

该方法的代码很长,但核心逻辑很简单,主体也是一个无限循环(每个连接能处理多次请求),每次循环获取一次客户端请求,并调用对应的handler处理请求

readRequest()的第一个返回值是response指针,该结构体实现了ResponseWriter接口,并且有一个*Request成员req,因此ww.req可以充当handler的两个参数

如果readRequest()返回了一个错误(例如客户端断开连接)则serve()方法返回,本次连接结束

调用handler时创建了一个serverHandler实例,该类型定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
type serverHandler struct {
	srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	// ...
	handler.ServeHTTP(rw, req)
}

该结构体只有一个Server指针字段srv,并且也实现了Handler接口,其ServeHTTP()方法就是调用srv.Handler.ServeHTTP(),其中srv.Handler就是一开始调用http.ListenAndServe()时的第二个参数,是一个多路器,如果是nil则使用DefaultServeMux(不是很懂为什么要定义这个类型)

以上就是net/http包的核心代码解读

整体流程图如下:

整体流程图

This post is licensed under CC BY 4.0 by the author.