Go 原生 HTTP Server 启动流程分析

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 服务处理流程

在这里插入图片描述

文档参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
React Native 是一个用于构建移动应用的开源框架,它允许开发者使用 JavaScript 和 React 的知识来构建原生移动应用。通过使用 React Native,开发者可以在不同平台上共享代码,并且可以访问设备的原生功能,例如相机、位置服务和推送通知等。 在 React Native 中,原生代码分析是指开发者需要编写一些原生代码来处理特定的功能,例如需要调用 Android 或 iOS 平台特定的 API 来实现某些功能。在 React Native 中,开发者经常需要在 JavaScript 代码和原生代码之间进行交互,这就需要进行原生代码分析。 在进行 React Native 原生代码分析时,开发者需要了解不同平台的编程语言和工具,例如 Android 平台需要使用 Java 或 Kotlin 进行编码,iOS 平台需要使用 Objective-C 或 Swift 进行编码。开发者需要深入了解各个平台的相关知识,并且需要在 React Native 应用中集成原生模块时,需要编写相应的原生代码。 另外,开发者需要了解如何在 React Native 代码中调用原生代码,以及如何在原生代码中调用 React Native 模块。这需要开发者对跨平台应用程序开发有一定的了解,以便能够在 React Native 中进行原生代码分析。 总的来说,React Native 的原生代码分析需要开发者掌握跨平台开发的知识和技能,同时还需要对各个平台的原生开发有一定的了解,这样才能够高效地在 React Native 应用中进行原生代码分析

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CoLiuRs

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

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

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

打赏作者

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

抵扣说明:

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

余额充值