net/http的server端
先创建一个简单的demo(HTTP/1.1),执行后的调用链路在这里
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Debug", "1")
w.Write([]byte("Hello World"))
})
if err := http.ListenAndServe(":8000", nil); err != nil {
fmt.Println("start http server fail:", err)
}
}
我们知道用c语言实现一个tcp服务,用到的系统调用有socket,bind,listen,我们看看在go中是如何做的。
go编译运行后,程序会阻塞监听在8080端口上,这个阶段的调用链路如下:
|-goroutine-1 created by runtime.main
|-main.main
|-net/http.(*ServeMux).Handle // 路由注册到net/http.DefaultServeMux的map中
|-net/http.appendSorted
|-net/http.(*Server).ListenAndServe
|-net.Listen
|-net.(*ListenConfig).Listen
|-net.(*sysListener).listenTCP
|-net.internetSocket
|-net.socket
|-net.sysSocket
|-runtime/internal/syscall.Syscall6 // 系统调用socket
|-net.setDefaultSockopts
|-net.listenerBacklog
|-net.(*netFD).listenStream
|-net.setDefaultListenerSockopts
|-net.(*TCPAddr).sockaddr
|-syscall.Bind // 系统调用bind
|-listenFunc(fd.pfd.Sysfd, backlog) // 系统调用listen
|-internal/poll.(*FD).Init
|-net.sockaddrToTCP
|-net/http.(*Server).Serve
|-net/http.(*onceCloseListener).Accept
|-net.(*TCPListener).Accept
|-net.(*TCPListener).accept
|-net.(*netFD).accept
|-internal/poll.(*FD).Accept
|-internal/poll.(*pollDesc).wait
|-internal/poll.runtime_pollWait
|-netpollblock // 协程挂起,等有新连接了,netpoll模型唤醒它
|-net/http.(*Server).Serve.func3 // 新连接用新协程来服务它
|-go c.serve(connCtx)
模拟请求一下8000端口,看看新协程的服务流程:
|-goroutine-6 created by net/http.(*Server).Serve.func3
|-net/http.(*conn).serve
|-net/http.(*conn).readRequest // 读取请求
|-net/http.serverHandler.ServeHTTP // 这里如果没有serverMux,则用net/http.DefaultServeMux
|-net/http.(*ServeMux).ServeHTTP
|-net/http.(*ServeMux).Handler
|-net/http.(*ServeMux).handler
|-net/http.(*ServeMux).match // 这里通过请求的path匹配对应的handle(全字匹配/前缀匹配)
|-net/http.HandlerFunc.ServeHTTP
|-main.main.func1 // handle方法就是demo代码里面定义的
|-net/http.(*conn).hijacked // 检查是否有劫持,参考http://u301.co/am
|-net/http.(*response).finishRequest // 返回响应内容
我们知道发起http请求,服务端返回对应的http响应文本,那这些文本产生的时机是在什么时候呢,我们进一步分析goroutine-18的执行栈如下:
|-goroutine-6 created by net/http.(*Server).Serve.func3
|-net/http.(*conn).serve
|-w = net/http.(*conn).readRequest
|-w.w = newBufioWriterSize(&w.cw, ...)
|-net/http.serverHandler.ServeHTTP(w, w.req)
|-w.Write("Hello World")
|-w.WriteHeader(200)
|-w.w.Write("Hello World") // 存入w.w.buf(内存不够就提前调用w.w.wr.Write)
|-w.finishRequest()
|-w.w.Flush()
|-w.w.wr.Write(p) // p即是w.w.buf
|-w.w.wr.writeHeader(p)
|-writeStatusLine(w.w.wr.res.conn.bufw, is11, 200, ...) // 这里写:HTTP/1.1 200 OK\r\n
|-WriteSubset(w.w.wr.res.conn.bufw, excludeHeader) // 这里写:自定义header X-Debug
|-setHeader.Write(w.w.wr.res.conn.bufw) // 这里写:常规的header
|-w.w.wr.res.conn.bufw.Write(crlf) // 多一个空行
|-w.w.wr.res.conn.bufw.Write(p) // 这里写:Hello World
// 涉及到的两个重要的结构体如下:
w.cw = net/http.chunkWriter {
res *response
header Header
wroteHeader bool
chunking bool
}
w.w = bufio.Writer {
buf []byte
wr io.Writer
}
至此大概的流程分析完成,可以看到官方的net包创建的server服务有几个问题:
- 每个新连接都会启动一个新协程,连接关闭后协程也销毁了,不能复用
- 路由匹配逻辑过于简单,也不高效
- 路由没有区分请求类型,如GET/POST