net/http包原理分析

net/http分析

1. 简介:

http 提供两个函数作为服务器编程开始,它在 addr 地址侦听,收到请求后,回调 Handler 接口方法。ListenAndServeTLS 支持制定证书的 HTTPS 服务。 
func ListenAndServe(addr string, handler Handler) error
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error

因此,为了剖析net\http包,我们需要从ListenAndServe函数开始,不断向下剖析http包的实现;

2. net\http包原理分析:

我们从ListenAndServe函数定义开始:

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

可以看到,ListenAndServe只是利用传入的addr函数,以及handler来初始化实际的服务器server,并利用server的ListenAndServe函数来实现真正的监听功能;

那么我们打开Server结构体的定义:

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
	
	Addr string
	Handler Handler // handler to invoke, http.DefaultServeMux if nil

	TLSConfig *tls.Config
	ReadTimeout time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout time.Duration
	IdleTimeout time.Duration
	MaxHeaderBytes int
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
	ConnState func(net.Conn, ConnState)
	ErrorLog *log.Logger
	BaseContext func(net.Listener) context.Context
	ConnContext func(ctx context.Context, c net.Conn) context.Context

	inShutdown atomicBool // true when when server is in shutdown

	disableKeepAlives int32     // accessed atomically.
	nextProtoOnce     sync.Once // guards setupHTTP2_* init
	nextProtoErr      error     // result of http2.ConfigureServer if used
	mu         sync.Mutex
	listeners  map[*net.Listener]struct{}
	activeConn map[*conn]struct{}
	doneChan   chan struct{}
	onShutdown []func()
}

可以看到Server的结构体定义的变量密密麻麻,我们这里暂时只看我们在ListenANdServe中为Server结构体赋值的两个变量:

  • Addr
  • Handler
为了了解Handler的用途,我们开始追踪Handler的定义。

Handler:

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

那么,我们的Handler实际上是一个接口,它定义了一个名为ServeHTTP的函数。那么所有实现了ServeHTTP函数的变量类型,都可以作为Handler接口的一个变量。那么Handler接口便符合我们在课上讲的单一函数的回调接口使用。我们可以通过以下三种方式来实现一个或是返回一个Handler接口类型变量:

  • 函数类型实现该接口
  • 函数(闭包)返回该接口
  • 数据对象实现该接口

至于net/http中使用了哪一个方式,我们继续分析;

接下来,我们就直接看Server结构体的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)
}

我们忽略所有的错误处理,只看这个代码的实现,可以看到,这里只有两行代码执行了真正的功能:

  • net.Listen(“tcp”, addr)
  • srv.Serve(ln)

其中net.Listen函数启动了对addr所指向的端口进行监听。net.Listen函数还返回对应的Listener,用于以后的对于链接的建立,关闭。那么我们继续看Server的Serve函数,其代码如下:

func (srv *Server) Serve(l net.Listener) error {
	...
	baseCtx := context.Background()
	...
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		...
		connCtx := ctx
		...
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(connCtx)
	}
}

因为原来的代码实在过长,所以我们在这里去除了不必要的部分,并使用…代替;

可以看到,Serve函数在得到监听器之后,就创建了一个循环。并不断等待监听器Accept一个新的访问。当监听器得到一个新的访问的时候,将返回一个rw,rw中保存着request信息。之后调用newConn函数:

func (srv *Server) newConn(rwc net.Conn) *conn {
	c := &conn{
		server: srv,
		rwc:    rwc,
	}
	if debugServerConnections {
		c.rwc = newLoggingConn("server", c.rwc)
	}
	return c
}

即创建一个新的conn类型的变量c,并返回;然后我们利用go程以及相关的上下文,建立一个新的连接以及对应的go程,处理连接。

在c.serve中,我们同样使用了for循环,并在每个循环中利用w, err := c.readRequest(ctx)获得每个请求的request;然后使用了serverHandler{c.server}.ServeHTTP(w, w.req)来处理请求。我们可以查看ServeHTTP的实现:

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为空时,我们使用默认的handler,即DefaultServeMux来处理请求,否则,我们就使用之前赋予服务器的handler来处理请求。最后一行:

handler.ServeHTTP(rw,req)

才真正实现了对请求的处理。

那么我们的net\http的实现过程即为:

  • 调用ListenAndServe,创建Server服务器,并调用它的ListenAndServe函数;
  • 调用net.Listen,启动对端口的监听;
  • 启动一个循环,利用l.Accept不断准备接收访问;
  • 新建一个连接c,并利用go程 go c.serve(connCtx) 完成对该访问任务的处理;
  • 在c.serve中,我们启动一个for循环来处理对多个请求的访问;
    • 使用w, err := c.readRequest(ctx)获得每个请求的request;
    • 使用serverHandler{c.server}.ServeHTTP(w, w.req)对请求进行处理;
      • serverHandler检查handler是否为空,如果为空就使用DefaultServeMux;否则使用已有的handler;
      • handler.ServeHTTP(rw,req)实现对请求的处理;

3. DefaultServeMux实现

找到DefaultServeMuX的定义

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
}

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

可以发现,DefaultServerMuX实际上是ServeMux类型的一个变量。ServeMux变量拥有一个muxEntry的map映射和一个muxEntry的数组es。其中es中的muxEntry按照pattern长度从大到小排列;

那么我们继续看DefaultServeMuX对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)
	h.ServeHTTP(w, r)
}

实际上,我们只需要看最后两行。ServeHTTP调用mux.Handler找到满足request的Handler,然后返回给h,由h的ServeHTTP函数对请求进行处理;

mux.Handler:

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
	...
	return mux.handler(host, r.URL.Path)
}

由于Handler原代码太长,我们只选取有意义,且实际,或者说正常情况下被执行的函数mus.handler。其他部分用…代替;那么我们转向mux.handler

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// Host-specific pattern takes precedence over generic ones
	if mux.hosts {
		h, pattern = mux.match(host + path)
	}
	if h == nil {
		h, pattern = mux.match(path)
	}
	if h == nil {
		h, pattern = NotFoundHandler(), ""
	}
	return
}

在这里,我们可以看到,mux.handler的实际功能为利用mux.match函数在mux.m和mux.es中找到path所对应的handler,如果没有找到,那么就返回NotFoundHandler。而在match函数中,

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.  mux.es contains all patterns
	// that end in / sorted from longest to shortest.
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

我们先尝试在mux.m中寻找直接和path对应的handler,如果找到,那么就返回这个handler。如果找不到,那么就在mux.es中寻找和path拥有最长公共前缀的handler进行返回。

为了向DefaultServeMuX中添加新的路由规则,我们需要调用以下两个函数:

http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))
http.Handle(pattern string, handler func(ResponseWriter, *Request))

就能为DefaultServeMuX添加新的路由规则;

那么DefaultServeMuX的实现如下:

  • 使用http.HandleFunc或http.Handle为DefaultServeMuX添加新的路由规则;
  • 当http.ListenAndServe没有设置handler的时候,使用DefaultServeMuX作为handler;
  • DefaultServeMuX使用match在mux.m和mux.es中找到与path最相匹配的handler作为最终解决对请求的访问的handler。如果找不到,那么使用NotFoundHandler作为请求的处理函数;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值