http标准库
引言#
本篇文章是学习了小徐先生的编程世界中的http底层原理实现公众号,想要记录学习一下
学习的内容更多是为了更加清晰http底层的流程实现,而不是源码解读
过于底层的源码解读我也读不懂,同时他包装地太过于深入让我难以理解
但是,清晰了http的流程让我受益匪浅
CS架构#
http实现的是cs架构,也就是客户端和服务端架构
但是在理解http标准库的时候,我更愿意把cs架构理解为端到端的联系,也就是 端 + conn + 端
的组合,至于客户端和服务端的差别,也就仅仅只是职责的差距,导致的代码上的差距而已
Server#
数据结构
- Server
server
是服务端最核心的类,下面的两个字段简洁地表示出了Server的功能从 Addr 匹配路径到具体的 Handler 进行实现功能
如果 Handler 没有显示声明,则会从 DefaultServerMux 中兜底
1
2
3
4
5
6
7type Server struct {
// server 的地址
Addr string
// 路由处理器.
Handler Handler // handler to invoke, http.DefaultServeMux if nil
// ...
}- Handler
Handler 定义了方法 ServerHTTP,总的来说就是 匹配路径 + 对请求的处理响应
1
2
3type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}- ServerMux
ServerMux 就是对 Handler的具体实现,内部有一个 map 和 slice 来维护 path -> handler 的映射
map 是用来精准映射的 ;如果没命中则使用 slice进行模糊匹配
1
2
3
4
5
6type 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
}- muxEntry
muxEntry 包含了 path + handler 两个部分
1
2
3
4type muxEntry struct {
h Handler
pattern string
}看完这些数据结构可能会有点思绪,也有可能会一脸懵逼,但总体就是为了
path -> handler 这一个功能服务的
流程Debug
带着刚才的数据结构,进行Debug,以下面的代码为例,在 HandleFunc 和 ListenAndServe 上打两个断点:
1
2
3
4
5
6
7
8
9
10
11func main() {
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte("Hello, World!"))
if err != nil {
fmt.Println(err)
}
fmt.Println("Request received")
})
_ = http.ListenAndServe("127.0.0.1:8080", nil)
}- http.HandleFunc
1
2
3func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}DefaultServeMux.HandleFunc
1
2
3
4
5
6func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}mux.Handle
1
2
3
4
5
6
7
8
9
10
11func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// ... 部分代码省略
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
// ...
}
代码很简单,总的来说就是将handle注册到相应Path下,下面是serve的debug
http.ListenAndServe
1
_ = http.ListenAndServe("127.0.0.1:8080", nil)
server.ListenAndServe
装配一下 path-handler
1
2
3
4func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}server.ListenAndServe
启动监听器
1
2
3
4
5
6func (srv *Server) ListenAndServe() error {
// ...
ln, err := net.Listen("tcp", addr)
// ...
return srv.Serve(ln)
}srv.Serve
通过For循环持续地监听请求,如果有就形成连接并启动
1
2
3
4
5
6
7
8
9func (srv *Server) Serve(l net.Listener) error {
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
// ....
c := srv.newConn(rw)
go c.serve(connCtx)
}
}
这里的代码稍微复杂点,不过经过代码的省略流程还是非常清晰的:
1
2
3
4
5
6lis := net.listener("tcp",addr) // start a listener
srv.serve(lis) // begin to listen
conn := lis.Accept() // wait for request,if has a request,get the conn
serve(conn) // begin the conn就是会先启动一个监听器监听一个端口,然后通过持续监听端口(一个for循环中持续的lis.Accept()),但有收到请求的时候,就会形成一个连接 conn ,这里的连接是一个接口,有读写各种接口,最后通过serve(conn)来实现与客户端的互动
至于内部的其他细节内容就不去看了,可以再去看源码
client#
数据结构
- client
- • Transport:负责 http 通信的核心部分,但是说实话我没怎么看懂,就不介绍了哈哈哈
- • Jar:cookie 管理
- • Timeout:超时设置
1
2
3
4
5
6
7
8type Client struct {
// ...
Transport RoundTripper
// ...
Jar CookieJar
// ...
Timeout time.Duration
}request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19type Request struct {
// 方法
Method string
// 请求路径
URL *url.URL
// 请求头
Header Header
// 请求参数内容
Body io.ReadCloser
// 服务器主机
Host string
// query 请求参数
Form url.Values
// 响应参数 struct
Response *Response
// 请求链路的上下文
ctx context.Context
// ...
}response
1
2
3
4
5
6
7
8
9
10
11
12
13type 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
// ...
}RoundTripper
RoundTripper 是通信模块的 interface,需要实现方法 Roundtrip,即通过传入请求 Request,与服务端交互后获得响应 Response.
1
2
3type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
流程Debug
以下面的代码为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16func Test_server(t *testing.T) {
t.Log("Client test")
resp, err := http.Post("http://localhost:8080", "text/plain", nil)
if err != nil {
t.Error(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Error(err)
}
t.Log(string(body))
}http.Post
1
2
3func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
return DefaultClient.Post(url, contentType, body)
}DefaultClient.Post
1
2
3
4
5
6
7
8func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
req, err := NewRequest("POST", url, body) // 断点
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
return c.Do(req) // 断点
}NewRequest()
就是会创建一个新的请求
c.Do(req)
就是会发送请求并且获取回复
🆗就是这么简单,好吧其实不是这么简单的事情,由下图可知,client做的事情就是创建一个请求并通过client.do(req)来获取resp
在client(do)的内部是通过Transport.RoundTrip来处理通信的,Transport.RoundTrip会创建两个goroutine:一个writeLoop,一个readLoop,
分别通过reqChannel 和 respChannel 进行传递消息,具体的实现细节详见顶部的博客(我没看懂,不好意思抄过来)
总结#
这次的源码解析更多的感觉不是解析源码而是在熟悉http底层的操作流程,之前总是在接触http相关的事情,但是具体的先后操作不是很清晰,经过这次操作以及之前的实践会清晰不少,感谢小徐先生的博客