Golang HTTP服务器源码简析

本文简要分析了Golang HTTP服务器的工作流程,包括`http.NewServeMux`的路由保存、匹配机制,`http.ListenAndServe`的连接处理,以及`readRequest`和`finishRequest`在请求和响应中的作用。通过阅读,可以快速理解Golang HTTP服务器的基本运作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简单用例

mux := http.NewServeMux()
	mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Write([]byte("ok"))
	})
	if err := http.ListenAndServe(":8080", mux); err != nil {
		log.Fatal("err=",err.Error())
	}

打开浏览器,输入localhost:8080


会看到ok

聊聊 http.NewServeMux

http.NewServeMux 返回的类型是*http.ServeMux

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
	h       Handler
	pattern string
}

可以看到ServeMux 主要是保存路由信息(路径和处理函数),所以被称为多路复用器,类似路由

ServeMux 如何保存路由信息

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.m[pattern] = muxEntry{h: handler, pattern: pattern}
...
}

当我们调用HandleFunc函数的时候,它会把pattern 和 handler 保存到 m 中,所以m 可以称为路由表

ServeMux 如何匹配路由

经过http协议的处理,最后的内容会保存到一个结构体中,后面会讲解具体内容,接收完请求之后,会调用ServeHTTP函数来处理相应的请求,所以路由匹配将会在这个函数中进行。

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    ...
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    ...
   return mux.handler(host, r.URL.Path)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    ...
    h, pattern = mux.match(path)
    ...
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	// Check for longest valid match.
	var n = 0
	for k, v := range mux.m {
		if !pathMatch(k, path) {
			continue
		}
		if h == nil || len(k) > n {
			n = len(k)
			h = v.h
			pattern = v.pattern
		}
	}
	return
}

从上面可以看到,真正的匹配是在match函数中进行匹配,也就是一开始先用map准确查找,如果找不到,就枚举查找最长符合的路径。

窥探 http.ListenAndServe

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
    ...
    ln, err := net.Listen("tcp", addr) 
    ...
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
func (srv *Server) Serve(l net.Listener) error {
    ...
    for {
        rw, e := l.Accept() 
        ...
        c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
    }
}
func (c *conn) serve(ctx context.Context) {
    ...
    c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

	for {
	w, err := c.readRequest(ctx)
	...
	serverHandler{c.server}.ServeHTTP(w, w.req)
     w.cancelCtx()
	if c.hijacked() {
    	return
	}
	w.finishRequest()
	...
	}
}

可以看到,每次接收一次连接请求,就会开启一个goroutine处理连接,经过一些配置,然后调用readRequest 开始读取请求内容,构造结构体,然后调用ServeHTTP 处理这个请求, 然后调用 finishRequest 进行响应。

再看 readRequest

func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
	...
	req, err := readRequest(c.bufr, keepHostHeader)
	...
	w = &response{
		conn:          c,
		cancelCtx:     cancelCtx,
		req:           req,
		reqBody:       req.Body,
		handlerHeader: make(Header),
		contentLength: -1,
		closeNotifyCh: make(chan bool, 1),

		// We populate these ahead of time so we're not
		// reading from req.Header after their Handler starts
		// and maybe mutates it (Issue 14940)
		wants10KeepAlive: req.wantsHttp10KeepAlive(),
		wantsClose:       req.wantsClose(),
	}
	...
	return w, nil
}
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
	tp := newTextprotoReader(b)
	req = new(Request)

	// First line: GET /index.html HTTP/1.0
	var s string
	if s, err = tp.ReadLine(); err != nil {  // 读http起始行
		return nil, err
	}
	...
	// Subsequent lines: Key: value.
	mimeHeader, err := tp.ReadMIMEHeader()  // 读http首部
	if err != nil {
		return nil, err
	}
	req.Header = Header(mimeHeader)
	...
	err = readTransfer(req, b)  // 获取http 主体body
	if err != nil {
		return nil, err
	}
	...
	return req,nil

从其内部可以看出,读取一个请求是步骤先是tp.ReadLine 读取http起始行部分信息,获取 method 、uri 和 proto 三部分,然后ReadMIMEHeader 读取头部 ,处理头部信息,保存到map结构当中,最后 readTransfer 交接body。

再看 finishRequest()

func (w *response) finishRequest() {
...
w.conn.bufw.Flush() //将响应内容写到相应缓冲区中
...
}

可以看到,最后将响应内容写到缓冲区中,交由下层协议处理。

省略大部分实现细节,但可以很快了解其大致流程,而不会困顿于局部,有兴趣可以深入了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值