Go HTTP模块处理流程简析

Go语言提供完善的net/http包,用户使用起来非常方便简单,只需几行代码就可以搭建一个简易的Web服务,可以对Web路由、静态文件、cookie等数据进行操作。


一个使用http包建立的Web服务

package main

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

func RequestHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World")
}

func main() {
	http.HandleFunc("/", RequestHandler)

	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

核心代码代码如下,下面对它们进行解析

// 用户自定义HTTP路由句柄
func RequestHandler(w http.ResponseWriter, r *http.Request)

// Multiplexer路由注册
http.HandleFunc("/", RequestHandler)

// 服务器监听本地地址,并循环处理HTTP请求
http.ListenAndServe(":8080", nil)

创建ServeMux路由handler

func RequestHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World")
}

这个是用户自定义的函数,结合第二行代码看,作为http.HandleFunc函数调用的第二个参数,因为http.HandleFunc第二个参数类型是func(ResponseWriter, *Request),所以本函数定义带有(ResponseWriter, *Request)参数列表,无返回值。第一个参数http.ResponseWriter是一个接口,用于对一次HTTP请求做响应处理,第二个参数http.Request是一次HTTP请求实例,后面再做详细分析。本函数本质上是一个http请求处理的回调函数,是由http包框架开放出,用户可以自定义处理函数。


ServeMux路由表结构

Go默认路由表结构为ServerMux

// ServeMux结构是一个HTTP请求多路复用器(Multiplexer)
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry	// ServeMux路由表
	hosts bool // whether any patterns contain hostnames
}

// 路由表项
type muxEntry struct {
	h       Handler	// 请求处理handler
	pattern string	// 请求路径
}

ServeMux路由注册过程过程

// 往系统默认的ServerMux中添加一条路由
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

// 系统默认的Multiplexer:ServeMux
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
    
    // 将用户定义的handler方法,强转为HandlerFunc类型,即实现Handler接口,
    // 后面直接通过f.ServeHTTP(w, r)实现对用户注册的handler的调用,f类型为HandlerFunc
	mux.Handle(pattern, HandlerFunc(handler))
}

// HandlerFunc类型
type HandlerFunc func(ResponseWriter, *Request)

// HandlerFunc类型实现了Handler接口中ServeHTTP方法
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

// 往ServeMux路由表(mux.m,map结构)中添加路由
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)
	}
    // 向路由表中新增路由表项
	mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

    // 如果不是以'/'开头,则从host开始匹配
	if pattern[0] != '/' {
		mux.hosts = true
	}
}

ServeMux路由匹配过程

// 见net/http/server.go中func (c *conn) serve(ctx context.Context) 1847行(Go SDK 1.11.5)
// 这里就完成对用户注册的路由handler调用
serverHandler{c.server}.ServeHTTP(w, w.req)

// serverHandler类型
type serverHandler struct {
	srv *Server
}

// serverHandler也实现了Handler接口中ServeHTTP方法
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    // http.ListenAndServe(":8080", nil)中第二个参数为nil,表示使用系统默认Multiplexer:DefaultServeMux,
    // 在这里就启用DefaultServeMux,可以查看Server结构对Handler成员注释说明(handler to invoke, http.DefaultServeMux if nil)
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}
    // 调用ServeMux实现Handler接口中ServeHTTP方法
	handler.ServeHTTP(rw, req)
}

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
	}
    // 通过Request找到路由
	h, _ := mux.Handler(r)

    // 到这里就调用用户注册的路由Handler
	h.ServeHTTP(w, r)
}

// 进一步跟踪mux.Handler(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) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// 通过路径匹配路由
	if mux.hosts {
		h, pattguizeern = mux.match(host + path)
	}
	if h == nil {
		h, pattern = mux.match(path)
	}
	if h == nil {
		h, pattern = NotFoundHandler(), ""
	}
	return
}

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
}

// 如果pattern不是以'/'结尾,则需完全匹配
// 如果pattern是以'/'结尾,并且pattern(/tree/)是path(/tree/xxx)的前缀子串,
// 则path(/tree/xxx)属于pattern(/tree/*)路由子集下,后面通过match中for语句,基于长匹配优先于短匹配原则,完全匹配路由
func pathMatch(pattern, path string) bool {
	if len(pattern) == 0 {
		// should not happen
		return false
	}
	n := len(pattern)
	if pattern[n-1] != '/' {
		return pattern == path
	}
	return len(path) >= n && path[0:n] == pattern
}

ServeMux路由匹配规则

对于每个HTTP请求,ServeHTTP会对URL进行模式(pattern)匹配,然后调用注册在此pattern下的handler来处理当前请求。

1、如果pattern以'/'开头,表示匹配URL的路径部分,如果不是以'/'开头,表示从host开始匹配;
2、模式匹配时,以匹配度最高为原则(长匹配优先于短匹配);
3、如果pattern(/tree/)以'/'结尾,将会对不带'/'的URL(/tree)进行301重定向到'/tree/'上,除非单独为'/tree'模式注册handler;
4、如果pattern(/tree)注册了handler,当请求的路径为'/tree/'时,则无法匹配该模式;

**实现Handler接口**

Handler结构定义如下

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

net/http/server.go中实现Handler接口有以下方法

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)}
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request)
func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request)
func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request)
func (h initNPNRequest) ServeHTTP(rw ResponseWriter, req *Request)
posted @ 2019-03-22 15:20  waynezly  阅读(826)  评论(0编辑  收藏  举报