Go 原生 HTTP Server 启动流程分析
HTTP 服务使用
Web 服务器是我们在平时开发过程中避免不了需要接触的内容,大致了解HTTP的实现原理或者启动流程有助于平时的开发 或者利用一些现有的Web 服务器框架进行二次封装。
下面我们将来分析下Go 原生的HTTP 服务是如何使用、它的启动流程是什么样的
简单使用
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
http.HandleFunc("/", hello) // 注册自己业务处理的Hander
http.HandleFunc("/echo", echo)
server := http.Server{Addr: ":8080"}
go func() {
if err := server.ListenAndServe(); err != nil { // 监听处理
fmt.Println("server start failed")
}
}()
// 通过信号量的方式停止服务,如果有一部分请求进行到一半,处理完成再关闭服务器
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
s := <-c
fmt.Printf("接收信号:%s\n", s)
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Println("server shutdown failed")
}
fmt.Println("server exit")
}
func hello (w http.ResponseWriter, r *http.Request) {
//time.Sleep(1 * time.Second)
fmt.Fprintln(w, "Hello Go!")
}
func echo (w http.ResponseWriter, r *http.Request) {
//time.Sleep(1 * time.Second)
fmt.Fprintln(w, "Hello Go echo!")
}
这样我们就利用Go 原生的 http 包写了一个 http 服务,那么它是怎么工作的呢?总体来说,整个处理流程就是:路由注册→ 启动服务→ 处理连接
路由注册
http.HandleFunc(“/”, hello) // 注册自己业务处理的Hander
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
... ... ...
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e // 写入map[string]muxEntry
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
// match 根据请求的路径找到对应的 Handler,就是我们写入的进去的 Handler
// 可以看到这里它只能是等值匹配或者是前缀匹配
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
也可以参考下github上的实现
- https://github.com/gorilla/mux
- https://github.com/julienschmidt/httprouter
Handler
路由注册我们使用 http.HandleFunc 进行注册,HandleFunc 接收的是一个 路径 和 handler, 这个 hander 的函数签名是 func(ResponseWriter, *Request),只要你的函数签名是这个,都可以用 http.HandleFunc 注册
我们注册的handler 会最终注册到 ServeMux 中的 map[string]muxEntry 中去,其中map 的key 就是 我们的路径,可以看到只能是等值匹配或者是前缀匹配
ServeMux(服务复用器)
前面我们提到了 ServeMux,那么这个是一个什么结构呢?ServeMux实现了ServeHTTP,从源码中国看到,我们所有的http 服务器都需要实现 ServeHTTP,从 serverHandler{c.server}.ServeHTTP(w, w.req) 这里可以看到,最终是会调用这个 ServeHTTP 函数
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // 路径和hander的KV对
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r) // 通过请求获取到hander,
h.ServeHTTP(w, r) // 具体的hander 去处理这个请求,就是我们的注册进行的hander
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
自定义 ServeMux
从上面可以看到,如果我们要实现自己的ServerMux,那么我们只需要实现自己的 ServeHTTP 行数就可以了。比如
package main
import (
"fmt"
"net/http"
)
type CustomizeMux struct {
}
func HelloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
func (mux *CustomizeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome CustomizeMux ServeHTTP")
}
func main () {
mux := http.NewServeMux()
mux.HandleFunc("/", HelloHandler)
mux.Handle("/mux", &CustomizeMux{})
http.ListenAndServe(":8080", mux)
}
启动服务
Server(服务器对象)
http.HandleFunc("/", hello) // 注册自己业务处理的Hander
http.HandleFunc("/echo", echo)
server := http.Server{Addr: ":8080"}
go func() {
if err := server.ListenAndServe(); err != nil { // 监听处理
fmt.Println("server start failed")
}
}()
func (srv *Server) ListenAndServe() error {
... ... ...
for {
rw, err := l.Accept() // 接收请求
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
... ... ...
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(connCtx) // 启动一个新的g来处理请求
}
}
处理连接
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
... ... ...
for {
w, err := c.readRequest(ctx) // 读取来自网络的请求
... ... ...
serverHandler{c.server}.ServeHTTP(w, w.req) // 处理请求,这个 ServeHTTP 其实就是 mux 的 ServeHTTP 方法
... ... ...
}
}
停止服务
我们的web 服务器现在已经能够接受请求,处理请求了,我们如何才能不影响现在正在处理的请求关闭服务器,我们可以通过结合捕捉系统信号(Signal)、goroutine 和管道(Channel)来实现服务器的优雅停止:
// 通过信号量的方式停止服务,如果有一部分请求进行到一半,处理完成再关闭服务器
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
s := <-c
fmt.Printf("接收信号:%s\n", s)
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Println("server shutdown failed")
}
fmt.Println("server exit")
这段代码通过捕捉 os.Interrupt 信号(Ctrl+C)信号)然后调用 server.Shutdown 方法告知服务器应停止接受新的请求并在处理完当前已接受的请求后关闭服务器。为了与普通错误相区别,标准库提供了一个特定的错误类型 http.ErrServerClosed,我们可以在代码中通过判断是否为该错误类型来确定服务器是正常关闭的还是意外关闭的。