Go net/http 分析

net/http 库如何实现 http 服务器功能

要使一个 http 服务器能够正常工作,必须至少实现如下的核心功能:

  • 监听主机的某个端口
  • 当在监听的端口上有客户端的请求到来时,接收该客户端的请求
  • 处理客户端的请求

net/http 库对应这三个功能都有相应的实现:

监听端口

监听主机上的某个窗口应该使用函数 http.ListenAndServe ,该函数的源码定义如下:

func ListenAndServe(addr string, handler Handler) error {
  server := &Server{Addr: addr, Handler: handler}
  return server.ListenAndServe()
}

观察 http.ListenAndServe源码可以发现,http.ListenAndServe 是先利用传入的端口号 addr 和处理函数 handler 初始化一个 Server 结构实例,然后通过调用这个实例的 ListenAndServe() 来实现监听功能的。以下为一个最简单的使用,在浏览器输入localhost:8080,则会打印 hello world!:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", sayHello)
	http.ListenAndServe(":8080", nil)
}

func sayHello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "hello world!")
}

接着上面 Server 结构的 ListenAndServe() 函数定义如下:

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

如上代码所示,Server 结构通过调用 net 包的 net.Listen("tcp", addr) 在底层用 TCP 协议搭建了一个服务,然后监听我们设置的端口,这个函数将会返回一个 Listener 接口,这个接口定义了方法 Accept 、Close 、Addr ,分别用于建立新的连接、关闭该接口对应的连接和返回接口对应的网络地址。

type Listener interface {
    // Accept waits for and returns the next connection to the listener.
    Accept() (Conn, error)

    // Close closes the listener.
    // Any blocked Accept operations will be unblocked and return errors.
    Close() error

    // Addr returns the listener's network address.
    Addr() Addr
}

接收客户端请求

然后,调用srv.Serve 函数接收客户端传来的数据

func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	var tempDelay time.Duration // how long to sleep on accept failure
	for {
		rw, e := l.Accept()
		if e != nil {
			if ne, ok := e.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0
		c, err := srv.newConn(rw)
		if err != nil {
			continue
		}
		go c.serve()
	}
}

在这个函数的循环体 for{} 内,首先通过调用的 rw, e := l.Accept() 接收客户端请求,请求信息保存在变量 rw 中,根据客户端请求的信息调用 newConn(rw) 创建一个新的连接 c ,最后单独创建一个 goroutine ,把相应的上下文信息作为参数去调用新连接的 c.serve(ctx) 函数,处理客户端的请求。即用户的每一次请求都创建一个新连接,在一个新的goroutine去服务,相互不影响

处理客户端请求

在处理客户端请求的 c.serve(ctx) 函数内,关键的操作有两个:

  1. 调用 w, err := c.readRequest(ctx) ,取出分析相应的请求信息 w。
  2. 调用 serverHandler{c.server}.ServeHTTP(w, w.req) ,获取相应的处理函数 handle对请求信息进行处理。

在这里主要分析一下如何获取处理函数 Handler

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)
}

代码已经很清晰了,如果传入为nil ,则 handler = DefaultServeMux ,最后再 handler.ServeHTTP(rw, req)。DefaultServeMux 是一个  ServeMux 的指针类型  ,var DefaultServeMux = &defaultServeMux 。我们先来看看 Handler 到底是什么

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}

Handler是一个接口,所以我们只要实现了 ServeHTTP 的方法,即可认为是一个 Handler。所以 ServeMux 肯定有 ServeHTTP 方法

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		w.Header().Set("Connection", "close")
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

如果是*那么关闭链接,不然调用mux.Handler(r)返回对应设置路由的处理 Handler,最后执行h.ServeHTTP(w, r),也就是调用对应路由的 handler 的ServerHTTP接口,那么mux.Handler(r)怎么处理的呢?

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
	if r.Method != "CONNECT" {
		if p := cleanPath(r.URL.Path); p != r.URL.Path {
			_, pattern = mux.handler(r.Host, p)
			return RedirectHandler(p, StatusMovedPermanently), pattern
		}
	}	
	return mux.handler(r.Host, r.URL.Path)
}

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
}

原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,调用这个handler的ServeHTTP接口就可以执行到相应的函数了。

但,Handler 是一个接口,sayHello函数并没有实现ServeHTTP这个接口,为什么最终会认为sayHello也是 Handler 呢?原来在http包里面还定义了一个类型HandlerFunc,我们定义的函数sayHello就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。

type HandlerFunc func(ResponseWriter, *Request)

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

总结

ListenAndServe()
	net.Listen("tcp", addr)//建立监听
	return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
		rw, e := l.Accept()//接受请求

		c, err := srv.newConn(rw)
		if err != nil {
			continue
		}
		go c.serve()//处理请求(来一个请求创建一个连接c,单独起一个协程处理)
			w, err := c.readRequest(ctx) //解析请求 
			serverHandler{c.server}.ServeHTTP(w, w.req)//解析路由,得到对应的处理Handler

参考:https://blog.csdn.net/Jack_CJZ/article/details/78535076

谢大:https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.4.md

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值