目录

 

 

HTTP 服务端

同样的,我们可以设想作为 HTTP 服务端处理一次请求应该具备哪些行为:

  1. 实现处理函数
  2. 预设 URL、Request Method、处理函数,三者之间的路由映射
  3. 监听请求
  4. 分发请求并完成处理

net/http 将上述行为整合为了两大步骤:

  1. 注册处理程序和请求路由。
  2. 监听并处理请求。

通过一个最简单的示例来感受:

package main

import (
    "fmt"
    "net/http"
)

func indexHandler(w http.ResponseWriter, request *http.Request) {
    fmt.Fprintln(w, "Hello World.")
}

func main() {
    http.HandleFunc("/", indexHandler)
    err := http.ListenAndServe("127.0.0.1:80", nil)
    if err != nil {
        fmt.Println("ListenAndServe: ", err)
    }
}
  • http.HandleFunc 函数:用于注册处理程序 indexHandler 和请求路由 ‘/’。
  • http.ListenAndServe 函数:用于监听 Socket(127.0.0.1:80)和处理外部请求。

Go 语言编程 — net/http — HTTP 服务端_HTTP 服务端

实现原理

Go 语言编程 — net/http — HTTP 服务端_HTTP 服务端_02

注册处理程序和请求路由

对于 net/http 而言,HTTP 服务器的本质就是一组实现了 http.Handler 接口的 Handlers(处理器集合),处理 HTTP 请求时,根据请求的 URL 进行路由选择到合适的 Handler 进行处理:

Go 语言编程 — net/http — HTTP 服务端_HTTP 服务端_03

主要体现为两个关键的结构体 http.ServeMux 和 http.Handler:

  • http.Handler:处理 Request 并返回 Response。任何满足了 http.Handler 接口的对象都可作为一个 Handler。http.Handler 是 net/http 服务端具有可扩展性的核心体现。

  • http.ServrMux:本质上是一个 HTTP 请求路由器,或者叫多路复用器(Multiplexor)。它把收到的请求的 URL 与一组预先定义的 HASH 表进行匹配,HASH 的 keys 就是 URL paths + Request Methods 的组合、values 就是关联的 Handlers。

当我们调用 http.HandleFunc 注册一个 Handler(处理器)和请求路由时,默认使用的是 http.DefaultServeMux(路由器):


var defaultServeMux ServeMux
var DefaultServeMux = &defaultServeMux

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)  //调用ServeMux的HandleFunc函数
}

上述可见,DefaultServeMux 是 ServeMux 类型的变量,所以实际上调用了 http.ServeMux.HandleFunc 方法:

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry  // key 是 URL
	hosts bool
}

type muxEntry struct {
	h       Handler   // 通过 HandleFunc 函数传入 Handler
	pattern string    // URL
}

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	mux.Handle(pattern, HandlerFunc(handler))  //调用Handle
}

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()
	//....
	mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
	//....
}

可见,http.ServeMux.Handle 方法完成了 Handler 和请求路径的映射工作。http.ServeMux 的成员 http.muxEntry,存储了从 URL 到 Handler 的映射关系,HTTP 服务器在处理请求时就会使用该哈希查找到对应的 Handler。

与一个细节值得注意,HandleFunc 函数中会把 handler 变量强制类型转换为 HandlerFunc 类型:HandlerFunc(handler),HandleFunc 类型的定义如下:

type HandlerFunc func(ResponseWriter, *Request)

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

可见,handler 变量和 HandleFunc 类型的函数签名是一致的:func(ResponseWriter, *Request),并且 HandleFunc 结构体具有一个 ServeHTTP 方法。同时 ServeHTTP 方法会调用传入的 handler,如:f(w, r)

综上,可以看出 HandlerFunc 只是对我们传入的 handler 变量进行了包装并提供了 ServeHTTP 方法来调用 handler 自身。

Go 语言编程 — net/http — HTTP 服务端_HTTP 服务端_04

这里总结一下注册一个 Handler 的流程

  1. http.HandlerFunc 调用 ServeMux.HandleFunc 方法。
  2. ServeMux.HandleFunc 首先对我们传进来的 handler 变量强制转换为 HandlerFunc 类型(目的是提供 ServerHTTP 接口来执行传入的 Handler)。然后调用 ServeMux.Handle 方法。
  3. ServeMux.Handle 方法会把 URL 和 handler 存在 ServeMux 结构的 m 字段中。

net/http 自建了几个常用的 Handler:FileServer、RedirectHandler 和 NotFoundHandler,例子:

package main

import (
	"log"
	"net/http"
)

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

	rh := http.RedirectHandler("http://www.baidu.com", 307)
	mux.Handle("/foo", rh)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}
  1. 调用了 http.NewServeMux 函数来创建一个空的 ServeMux。
  2. 调用了 http.RedirectHandler 函数创建了一个新的 Handler,这个 Handler 会对收到的所有请求都执行 307 重定向到指定的 URL,如 http://www.baidu.com。
  3. 接下来使用 ServeMux.Handle 函数将 Handler 注册到新建的 ServeMux,所以它在 URL path /foo 上收到所有的请求都交给这个 Handler。
  4. 最后创建了一个新的 ServeHTTP,并通过 http.ListenAndServe 函数监听所有进入的请求,通过传递刚才创建的 ServeMux 来为请求去匹配对应 Handler。

监听并处理请求

Go 语言编程 — net/http — HTTP 服务端_HTTP 服务端_05

http.ListenAndServe 函数用于监听 TCP 连接并处理请求,该函数会使用传入的监听地址和 Handler 初始化一个 HTTP 服务器 http.Server,然后调用该服务器的 http.Server.ListenAndServe 方法:


func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return 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)})
}

http.Server.ListenAndServe 方法使用了 net 库提供的 net.Listen 函数监听对应地址上的 TCP 连接并通过 http.Server.Serve 处理客户端的请求。http.Server.Serve 会在循环中监听外部的 TCP 连接并为每个连接调用 http.Server.newConn 创建新的结构体 http.conn,它是 HTTP 连接的服务端表示:

func (srv *Server) Serve(l net.Listener) error {
	// ...
	for {
		rw, e := l.Accept() // 等待请求
		//...
		c := srv.newConn(rw)  // 这里创建了一个 conn 结构体,它代表一个连接
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
	}
}		

创建了服务端的连接之后,net/http 中的实现会为每个 HTTP 请求创建单独的 Goroutine 并在其中调用 http.Conn.serve 方法,如果当前 HTTP 服务接收到了海量的请求,会在内部创建大量的 Goroutine,这可能会使整个服务质量明显降低无法处理请求。

func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()

	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()

	c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

	for {
		w, _ := c.readRequest(ctx)
		serverHandler{c.server}.ServeHTTP(w, w.req)
		w.finishRequest()
		...
	}
}

上述代码片段是简化后的连接处理过程,其中包含读取 HTTP 请求、调用 Handler 处理 HTTP 请求以及调用完成该请求。读取 HTTP 请求会调用 http.Conn.readRequest 方法,该方法会从连接中获取 HTTP 请求并构建一个实现了 http.ResponseWriter 接口的变量 http.response,向该结构体写入的数据都会被转发到它持有的缓冲区中:

func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
	...
	w.written += int64(lenData)
	if w.contentLength != -1 && w.written > w.contentLength {
		return 0, ErrContentLength
	}
	if dataB != nil {
		return w.w.Write(dataB)
	} else {
		return w.w.WriteString(dataS)
	}
}

解析了 HTTP 请求并初始化 http.ResponseWriter 之后,我们就可以调用 http.serverHandler.ServeHTTP 方法查找处理器来处理 HTTP 请求了:

type serverHandler struct {
	srv *Server
}

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

conn.serve 调用了 serverHandler 的 ServeHTTP 方法,如果我们没有手动设置新的 ServerMux 的话,那么这个方法会调用DefaultServeMux 的 ServeHTTP 方法处理外部的 HTTP 请求。

到目前为止,我们已经知道实现 ServeHTTP 的类型有两个:ServerMux 和 HandlerFunc。

  • HandlerFunc 结构体:实现了 Handler 接口,开发者编写的 Handler 都会被转换为 HandlerFunc 类型,它的 ServeHTTP 方法会执行我们注册的 Handler,即:“处理器”。
  • ServeMux 结构体:实现了 Handler 接口,它的 m 字段用于存储我们注册的 Handler,它的 ServeHTTP 方法提供了路由功能,即: “多路复用器”。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	h, _ := mux.Handler(r)  // 获取对应的 Handler
	h.ServeHTTP(w, r)       // 执行 Handler
}

经过一系列的函数调用,上述过程最终会调用 HTTP 服务器的 http.ServerMux.match 方法,该方法会遍历前面注册过的路由表并根据特定规则进行匹配:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

如果请求的路径和路由中的表项匹配成功,就会调用表项中对应的 Handler,Handler 中包含的业务逻辑会通过 http.ResponseWriter 构建 HTTP 请求对应的响应并通过 TCP 连接发送回客户端。

HTTP 服务器的可扩展性

net/http 服务器的可扩展性体现在 3 个方面。

自定义 Handler,同时使用默认的 DefaultServeMux 和默认的 Server 的结构

package main

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

func main() {
	// 设置路由规则
	http.HandleFunc("/", Tmp)

	// 使用默认的 DefaultServeMux
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func Tmp(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "version 1")
}

自定义 Handler 和 ServeMux,使用默认的 Server 的结构

package main

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

type myHandler struct{}

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

func Tmp(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "version 2")
}

func main(){
	mux := http.NewServeMux()
	mux.Handle("/", &myHandler{})	
	mux.HandleFunc("/tmp", Tmp)

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

自定义 Handler、ServeMux 和 Server

package main

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

var mux map[string]func(http.ResponseWriter, *http.Request) 

func main(){
	server := http.Server{
		Addr: ":8080",
		Handler: &myHandler{},
		ReadTimeout: 5*time.Second,
	}
	
	mux = make(map[string]func(http.ResponseWriter, *http.Request))
	mux["/tmp"] = Tmp

	err := server.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{
	// 用这个 Handler 来实现路由转发,相应的路由调用相应 func
		h(w, r)
		return
	}
	io.WriteString(w, "URL:"+r.URL.String())
}

func Tmp(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "version 3")
}

将函数作为处理器

对于简单的情况(比如上面的例子),定义个新的有 ServerHTTP 方法的自定义类型有些累赘。

实际上,任何具有 func(http.ResponseWriter, *http.Request) 签名的函数都能转化为一个 HandlerFunc 类型。这很有用,因为 HandlerFunc 对象内置了 ServeHTTP 方法,后者可以聪明又方便的调用我们最初提供的函数内容。

package main

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

func timeHandler(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(time.RFC1123)
	w.Write([]byte("The time is: " + tm))
}

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

	// Convert the timeHandler function to a HandlerFunc type
	th := http.HandlerFunc(timeHandler)
	// And add it to the ServeMux
	mux.Handle("/time", th)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

实际上,将一个函数转换成 HandlerFunc 后注册到 ServeMux 是很普遍的用法,所以 Golang 为此提供了个便捷的 ServerMux.HandlerFunc 方法:

package main

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

func timeHandler(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(time.RFC1123)
	w.Write([]byte("The time is: " + tm))
}

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

	mux.HandleFunc("/time", timeHandler)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

再进一步的优化,如果我们想从 main() 函数中传递一些信息或者变量给 Hander 函数,一个优雅的方式是将我们 Hander 放到一个闭包中,将我们要使用的变量带进去:

package main

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

func timeHandler(format string) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		tm := time.Now().Format(format)
		w.Write([]byte("The time is: " + tm))
	}
	return http.HandlerFunc(fn)
}

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

	th := timeHandler(time.RFC1123)
	mux.Handle("/time", th)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}