Go语言net/http包解析

首先来看一个用net/http包写的web服务器:两个函数实现http服务器

package main

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

func sayhelloName(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()  //解析参数,默认是不会解析的
	fmt.Println(r.Form)  //这些信息是输出到服务器端的打印信息
	fmt.Println("path", r.URL.Path)
	fmt.Println("scheme", r.URL.Scheme)
	fmt.Println(r.Form["url_long"])
	for k, v := range r.Form {
		fmt.Println("key:", k)
		fmt.Println("val:", strings.Join(v, ""))
	}
	fmt.Fprintf(w, "Hello world!") //这个写入到w的是输出到客户端的
}

func main() {
	http.HandleFunc("/", sayhelloName) //设置访问的路由
	err := http.ListenAndServe(":9090", nil) //设置监听的端口
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

函数1:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
//HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。ServeMux的文档解释了模式的匹配机制。
var DefaultServeMux = NewServeMux()
//DefaultServeMux是用于Serve的默认ServeMux。
func NewServeMux() *ServeMux
//NewServeMux创建并返回一个新的*ServeMux

函数2:

func ListenAndServe(addr string, handler Handler) error
//ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。
//handler参数一般会设为nil,此时会使用DefaultServeMux(默认的路由器)。

--------------------------------------第一个函数解析-------------------------------------------------------------

1、HTTP包默认的路由器:DefaultServeMux

type ServeMux struct {  //ServeMux就是路由器类型
	mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
	m  map[string]muxEntry  // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
	hosts bool // 是否在任意的规则中带有host信息
}
//ServeMux类型是HTTP请求的多路转接器。
//它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。
type muxEntry struct {
	explicit bool   // 是否精确匹配
	h        Handler // 这个路由表达式对应哪个handler
	pattern  string  //匹配字符串
}
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)  // 路由器实现
}
//实现了Handler接口的对象可以注册到HTTP服务端,为特定的路径及其子树提供服务。
//ServeHTTP应该将回复的头域和数据写入ResponseWriter接口然后返回。返回标志着该请求已经结束,
//HTTP服务端可以转移向该连接上的下一个请求。


路由器:就是访问特定URI的时候,执行该URI指定的函数(controller)。

上面讲到HandleFunc函数的作用是把一个模式(URI)对应的处理函数(controller)注册到HTTP包默认的路由器-DefaultServeMux中。那么这个注册的过程是怎么进行的呢?


注册“模式-处理函数”对到HTTP默认路由器中:

注册“模式-处理函数”对到路由器中,代码中就是把模式字符串如"/",和函数名写入到ServeMux的实例中-DefaultServeMux。也就是写到map[string]muxEntry这个map里面。模式字符串对应string,而处理函数对应muxEntry结构体的Handler类型。从上面的几个struct和interface中可以看出,如果要把我们自己写的函数写入到muxEntry的h中,函数必须要实现Handler这个接口。然而,从最上面的HTTP服务器代码中看到sayHelloName这个函数并没有实现Handler这个结构啊,这是怎么实现的呢?


调用逻辑①

这里就涉及到上面讲到的HandleFunc这个函数了:

// HandleFunc registers the handler function for the given pattern in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

为什么sayhelloName函数并没有实现ServeHTTP这个接口,却能添加到默认路由器DefaultServeMux里面呢?从上面HandleFunc函数可以看出,当我们把自定义的处理函数如sayhelloName作为参数传递的时候,go语言上会有一个函数类型的转换!

调用逻辑②

HandleFunc这个函数调用了 (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request) 这个方法:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	mux.Handle(pattern, HandlerFunc(handler))
}

这里面就有 HandlerFunc(handler) 这个类型转换的过程。下面来看一下HandlerFunc这个类型:

// The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

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

所以,我们写的sayHelloName这个处理函数就强制转换成了HandlerFunc类型,而HandlerFunc类型实现了ServeHTTP这个方法,即HandlerFunc实现了type Handler interface这个接口。故而sayHelloName实现了type Handler interface接口。即我们可以把我们定义的“模式-处理函数”对写入到默认路由器DefaultServeMux中。


调用逻辑③

回到上面提到的(ServeMux) HandleFunc(pattern, handler) 这个方法,这个方法里面调用了(mux *ServeMux) Handle(pattern string, handler Handler) 这个方法:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) { //这里的mux就是上面HandleFunc传入的DefaultServeMux,默认路由器
	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 {  //说明map[string]muxEntry 这个不存在
		mux.m = make(map[string]muxEntry)
	}
	mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

上面这个函数就是“URI-处理函数”的注册过程。

--------------------------------------第一个函数解析完成--------------------------------------------------------



--------------------------------------第二个函数解析-------------------------------------------------------------

第二个函数:http.ListenAndServe(":9090", nil)

func ListenAndServe(addr string, handler Handler) error
//ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。
//handler参数一般会设为nil,此时会使用DefaultServeMux。

函数体:

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

实例化一个Server对象,Server类型定义了运行HTTP服务端的参数,Server的零值是合法的配置。Server其实是type Server struct,即一个结构体,具体字段如下:

type Server struct {
    Addr           string        // 监听的TCP地址,如果为空字符串会使用":http"
    Handler        Handler       // 调用的处理器,如为nil会调用http.DefaultServeMux
    ReadTimeout    time.Duration // 请求的读取操作在超时前的最大持续时间
    WriteTimeout   time.Duration // 回复的写入操作在超时前的最大持续时间
    MaxHeaderBytes int           // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
    TLSConfig      *tls.Config   // 可选的TLS配置,用于ListenAndServeTLS方法
    // TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
    // 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
    // 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
    // 连接在函数返回时会自动关闭。
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    // ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
    // 参见ConnState类型和相关常数获取细节。
    ConnState func(net.Conn, ConnState)
    // ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
    // 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
    ErrorLog *log.Logger
    // 内含隐藏或非导出字段
}

然后调用 (Server) ListenAndServe()方法进行监听:

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
// If srv.Addr is blank, ":http" is used.
// ListenAndServe always returns a non-nil error.
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)})
}

net.Listen函数(dial.go): —— 监听端口

func Listen(net, laddr string) (Listener, error)
返回在一个本地网络地址laddr上监听的Listener。网络类型参数net必须是面向流的网络:
"tcp"、"tcp4"、"tcp6"、"unix"或"unixpacket"。参见Dial函数获取laddr的语法。
func Listen(network, address string) (Listener, error) {
	addrs, err := DefaultResolver.resolveAddrList(context.Background(), "listen", network, address, nil)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
	}
	var l Listener
	switch la := addrs.first(isIPv4).(type) {
	case *TCPAddr:
		l, err = ListenTCP(network, la)
	case *UnixAddr:
		l, err = ListenUnix(network, la)
	default:
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
	}
	if err != nil {
		return nil, err // l is non-nil interface containing nil pointer
	}
	return l, nil
}


函数 srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}):——接收客户端请求

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() //通过Listener接收请求
		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) //创建一个Conn。Conn是net包里面的一个接口 type Conn interface。Conn接口代表通用的面向流的网络连接。多个线程可能会同时调用同一个Conn的方法。
		if err != nil {
			continue
		}
		go c.serve() //Go语言高并发的体现
	}
}


具体底层的net.Listen和srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})就不再解析了,具体可以看源码。

--------------------------------------第二个函数解析完成--------------------------------------------------------


总结:利用net/http包建立一个HTTP服务器

①首先要定义一个路由器,这里可以自定一个路由器(如:mux := http.NewServeMux()),也可以使用http包默认的DefaultServeMux路由器。

②然后我们把要注册的“模式(URI)-处理函数”对添加到mux中。模式好办,就是一个string类型的值。对于处理函数,因为ServeMux路由器类型中map[string]muxEntry类型,里面muxEntry类型,的处理函数是Handler类型,而Handler类型是接口类型,如下:

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

所以我们的处理函数必须要实现Handler这个接口,也就是实现ServeHTTP这个方法。最后通过

func (mux *ServeMux) Handle(pattern string, handler Handler)

这个方法把“模式-处理函数”对添加到自定义/http包默认路由器中。


③最后一步是调用http.ListenAndServe(":9090",mux)函数,对指定的addr进行监听就可以了。这个函数第二个参数选取的规则是:如果是自定义路由,传入自定路由;如果是http包默认路由,传入nil值即可。



附录:

一个使用自定义路由的HTTP服务器:

package main

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

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter,r *http.Request){
	io.WriteString(w,"URL:"+r.URL.String()+"Method: "+r.Method)
}

func main() {
	mux:= http.NewServeMux()

	mux.Handle("/", &myHandler{}) //为什么要传指针过去呢?因为是指针实现了这个接口
	err:=http.ListenAndServe(":9090",mux)
	if err!=nil {
		log.Fatal(err)
	}
}
用http包实现http服务器,使用高层封装:第一个版本
*******************************************************************
package main

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

func main(){
	//设置路由
	http.HandleFunc("/", sayHello) //第二个参数指定了传入函数的格式

	err:=http.ListenAndServe(":8081",nil)//使用http包默认的路由器DefaultServeMux时,传入nil即可。
	if err!=nil{
		log.Fatal(err)
	}
}

func sayHello(w http.ResponseWriter, r *http.Request){ //这样才能注册进去路由
	io.WriteString(w, "hello world, this is version 1")
	//只要实现了writer接口就可以这么传。
}


	
注释:http.ResponseWriter对Writer接口的实现。http包server.go文件
type ResponseWriter interface {
	Header() Header
	Write([]byte) (int, error)
	WriteHeader(statusCode int)
}
	
func (w *response) Write(data []byte) (n int, err error) {
	return w.write(len(data), data, "")
}

*******************************************************************





用http包实现http服务器,使用中层封装:第二个版本
*********************************************************************
package main

import (
	"net/http"
	"io"
	"log"
	"os"
)

//对比第一版本:传入的handler是nil,接下来我们要把nil换掉,换成我们自己实现的handler
//其实并不是一个handler,真实是ServerMux(路由类型),首先需要实现一个mux

func main(){
	mux:=http.NewServeMux() //实例化一个mux,返回一个mux
	//设置handler操作,不可以用HandlerFunc这个函数,这个函数用的是默认的路由器DefaultServeMux
	//这里用的是默认的的handler进行注册,要自己实现handler,注册到mux中
	//
	mux.Handle("/",&myHandler{})
	mux.HandleFunc("/hello", sayHello)

//实现文件服务器-简易静态文件实现
	wd,err:=os.Getwd()  //Getwd返回一个对应当前工作目录的根路径
	if err!=nil{
		log.Fatal(err)
	}
	mux.Handle("/static/",http.StripPrefix("/static/",
		http.FileServer(http.Dir(wd))))


	err =http.ListenAndServe(":9090",mux)  //使用自定义的路由器mux时,用http包的ListenAndServe函数,此时要传入mux
	if err!=nil{
		log.Fatal(err)
	}
}

type myHandler struct {}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
	io.WriteString(w, "URL:"+r.URL.String())
}

func sayHello(w http.ResponseWriter, r * http.Request){
	io.WriteString(w, "hello world, this is version 2")
}
********************************************************************

从这里就可以看出来,我们还需要一个mux来控制我们的访问!这个函数用了两个路由器,一个DefaultServeMux和一个mux。
为什么这里没有ListenAndServe设置nil也可以访问到默认路由DefaultServeMux设置的URL呢?
答:注意这一句mux.HandleFunc("/hello", sayHello),这里是调用的HandleFunc这个方法,不是用的HandleFunc函数;所以这里也是操作的mux这个自定义的路由器!





用http包实现http服务器,使用底层封装:第三个版本
*********************************************************************

package main

import (
	"net/http"
	"time"
	"io"
	"log"
)
//通过map保存注册的handler
//然后通过底层的serveHTTP进行转发,这是效率最高的,因为没有进任何封装
//参考beego的那些路由 
var mux map[string]func(http.ResponseWriter, *http.Request)

func main(){
	server := http.Server{
		Addr: ":8080",
		Handler: &myHandler{},
		ReadHeaderTimeout: 5*time.Second,
	}

	mux = make(map[string]func(http.ResponseWriter, *http.Request))
	mux["/hello"]=sayHello
	mux["/bye"]=sayBye

	err:=server.ListenAndServe()  //使用自定义的map来实现路由时,使用ListenAndServe方法,上面用的是ListenAndServe函数。
	if err!=nil{
		log.Fatal(err)
	}
}


type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
	if h,ok := mux[r.URL.String()] ; ok {
		h(w,r) //根据URL从map中取出函数名,然后调用。
		return
	}

	io.WriteString(w, "Version 3 "+"URL: "+r.URL.String())
}

func sayHello(w http.ResponseWriter, r *http.Request){
	io.WriteString(w, "Version 3 "+"Hello ")
}

func sayBye(w http.ResponseWriter, r *http.Request){
	io.WriteString(w, "Version 3 "+"Bye")
}


********************************************************************************

这个程序只是用了ListenAndServe方法,把前面两个版本的http.ListenAndServe函数中的封装解开,直接设置server这个结构体。
而上面那两个版本,是先设置一个路由器(默认的或者是自定义的),然后把路由器传到ListenAndServe函数,ListenAndServe函数再通过传入的路由器调用ListenAndServe方法,其实底层是一样的。
阅读更多

更多精彩内容