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)
函数内,关键的操作有两个:
- 调用
w, err := c.readRequest(ctx)
,取出分析相应的请求信息 w。 - 调用
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