GO的框架之net/HTTP标准库

1 整体框架 http的交互框架是C-S架构 import (     "net/http" ) func main() {     http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {         w

1 整体框架

Untitled.png

http的交互框架是C-S架构

import (
    "net/http"
)


func main() {
    http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("pong"))
    })


    http.ListenAndServe(":8091", nil)
}

可以通过http.HandleFunc注册请求路径/ping的handle函数;http.ListenAndServe将监听8091端口,这个端口启动了http服务

发送请求:

func main() {
    reqBody, _ := json.Marshal(map[string]string{"key1": "val1", "key2": "val2"})

    resp, _ := http.Post(":8091", "application/json", bytes.NewReader(reqBody))
    defer resp.Body.Close()

    respBody, _ := io.ReadAll(resp.Body)
    fmt.Printf("resp: %s", respBody)
}

服务端

核心数据结构

(1)Server

type Server struct {
    // server 的地址
    Addr string
    // 路由处理器.
    Handler Handler // handler to invoke, http.DefaultServeMux if nil
    // ...
}

整个 http 服务端模块被封装在 Server 类当中.

Handler 是 Server 中最核心的成员字段,实现了从请求路径 path 到具体处理函数 handler 的注册和映射能力.

在用户构造 Server 对象时,倘若其中的 Handler 字段未显式声明,则会取 net/http 包下的单例对象 DefaultServeMux(ServerMux 类型) 进行兜底.

(2)Handler

Handler 是一个 interface,定义了方法: ServeHTTP.

该方法的作用是,根据 http 请求 Request 中的请求路径 path 映射到对应的 handler 处理函数,对请求进行处理和响应.

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

(3)ServeMux

type ServeMux struct {
    mu sync.RWMutex
    m map[string]muxEntry
    es []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool // whether any patterns contain hostnames
}

(4)muxEntry

muxEntry 为一个 handler 单元,内部包含了请求路径 path + 处理函数 handler 两部分.

type muxEntry struct {
    h Handler
    pattern string
}

注册handler

Untitled.png

实现路由注册的核心逻辑位于 ServeMux.Handle 方法中,两个核心逻辑值得一提:

  • 将 path 和 handler 包装成一个 muxEntry,以 path 为 key 注册到路由 map ServeMux.m
  • 响应模糊匹配机制. 对于以 ‘/’ 结尾的 path,根据 path 长度将 muxEntry 有序插入到数组 ServeMux.es 中.(模糊匹配机制的伏笔在 2.3 小节回收)

启动server

Untitled.png

调用 net/http 包下的公开方法 ListenAndServe,可以实现对服务端的一键启动. 内部会声明一个新的 Server 对象,嵌套执行 Server.ListenAndServe 方法.

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

Server.ListenAndServe 方法中,根据用户传入的端口,申请到一个监听器 listener,继而调用 Server.Serve 方法.

func (srv *Server) ListenAndServe() error {
    // ...
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    // ...
    return srv.Serve(ln)
}

Server.Serve 方法很核心,体现了 http 服务端的运行架构:for + listener.accept 模式.

  • 将 server 封装成一组 kv 对,添加到 context 当中
  • 开启 for 循环,每轮循环调用 Listener.Accept 方法阻塞等待新连接到达
  • 每有一个连接到达,创建一个 goroutine 异步执行 conn.serve 方法负责处理
var ServerContextKey = &contextKey{"http-server"}

type contextKey struct {
    name string
}

func (srv *Server) Serve(l net.Listener) error {
   // ...
   ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, err := l.Accept()
        // ...
        connCtx := ctx
        // ...
        c := srv.newConn(rw)
        // ...
        go c.serve(connCtx)
    }
}

conn.serve 是响应客户端连接的核心方法:

  • 从 conn 中读取到封装到 response 结构体,以及请求参数 http.Request
  • 调用 serveHandler.ServeHTTP 方法,根据请求的 path 为其分配 handler
  • 通过特定 handler 处理并响应请求
func (c *conn) serve(ctx context.Context) {
    // ...
    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)


    for {
        w, err := c.readRequest(ctx)
        // ...
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        // ...
    }
}

Untitled.png

依次调用 ServeMux.ServeHTTP、ServeMux.Handler、ServeMux.handler 等方法,最终在 ServeMux.match 方法中,以 Request 中的 path 为 pattern,在路由字典 Server.m 中匹配 handler,最后调用 handler.ServeHTTP 方法进行请求的处理和响应.

当通过路由字典 Server.m 未命中 handler 时,此时会启动模糊匹配模式,两个核心规则如下:

  • ‘/’ 结尾的 pattern 才能被添加到 Server.es 数组中,才有资格参与模糊匹配
  • 模糊匹配时,会找到一个与请求路径 path 前缀完全匹配且长度最长的 pattern,其对应的handler 会作为本次请求的处理函数.

客户端

核心数据结构

(1)Client

与 Server 对仗,客户端模块也有一个 Client 类,实现对整个模块的封装:

  • Transport:负责 http 通信的核心部分,也是接下来的讨论重点
  • Jar:cookie 管理
  • Timeout:超时设置

(2)RoundTripper

RoundTripper 是通信模块的 interface,需要实现方法 Roundtrip,即通过传入请求 Request,与服务端交互后获得响应 Response.

(3)Transport

Tranport 是 RoundTripper 的实现类,核心字段包括:

  • idleConn:空闲连接 map,实现复用
  • DialContext:新连接生成器

(4)Request

http 请求参数结构体.

type Request struct {
    // 方法
    Method string
    // 请求路径
    URL *url.URL
    // 请求头
    Header Header
    // 请求参数内容
    Body io.ReadCloser
    // 服务器主机
    Host string
    // query 请求参数
    Form url.Values
    // 响应参数 struct
    Response *Response
    // 请求链路的上下文
    ctx context.Context
    // ...
}

(5)Response

http 响应参数结构体.

type Response struct {
    // 请求状态,200 为 请求成功
    StatusCode int    // e.g. 200
    // http 协议,如:HTTP/1.0
    Proto      string // e.g. "HTTP/1.0"
    // 请求头
    Header Header
    // 响应参数内容
    Body io.ReadCloser
    // 指向请求参数
    Request *Request
    // ...
}

客户端发起一次 http 请求大致分为几个步骤:

  • 构造 http 请求参数,这个过程就会加上cookie之类的,之后会更新cookie(服务器如果有更新)
  • 获取用于与服务端交互的 tcp 连接,会先尝试复用相同协议 访问相同服务端的空闲连接,没有则新建连接
  • 通过 tcp 连接发送请求参数
  • 通过 tcp 连接接收响应结果
LICENSED UNDER CC BY-NC-SA 4.0
Comment