Go 原生 HTTP Server 启动流程分析

本文详细分析了Go原生HTTP服务器的启动流程,包括简单使用、路由注册、Handler处理、ServeMux工作原理以及自定义ServeMux。同时,介绍了如何优雅地停止服务,通过捕获系统信号实现服务的平滑关闭。文章还提及了第三方库如gorilla/mux和httprouter在路由匹配上的实现。
摘要由CSDN通过智能技术生成

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,我们可以在代码中通过判断是否为该错误类型来确定服务器是正常关闭的还是意外关闭的。

总结HTTP 服务处理流程

在这里插入图片描述

文档参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CoLiuRs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值