分享
  1. 首页
  2. 文章

Go常用库-网络框架Gin(1) - 原生http包

沉寂之舟 · · 2035 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一. 写在前面:

  • Go提供了原生的Http实现-net/http包,封装的挺好,作为服务器使用把底层的TCP连接,请求报文解析,Header解析,Cookie解析,返回报文组装等功能都实现好了.使用起来只需要写真正逻辑相关的Handler即可.作为客户端使用也提供了默认的Client,只需要构造出请求,即可发送出去,并获得返回.
  • 但是,由于定义和实现是绑定在一起的,如果不想用原生的东西,扩展起来可就麻烦了.不花时间啃几千行的server.go和transport.go肯定改不动的.相比之下,感觉javax.servlet就漂亮的多,只是定义好接口,具体怎么实现容器或客户端自行决定;也很容易升级Http版本的支持.

二. net/http包源码简单阅读分析

注: 由于目标只是了解处理流程,对于文件传输(chunk)和HTTP/2的部分,都直接略过了.

1. http子包与关联包

包名 作用 其他说明
net/http/cgi 提供cgi的默认实现 Cgi(Common Gateway Interface)是一种代理机制,可以把Http的请求转换为其他形式(Cmd命令行)的调用,然后在把执行结果通过Http返回.
net/http/cookieJar 提供给Http客户端使用的Cookie管理器 在http/jar.go定义了接口
net/http/fcgi fastcgi的默认实现
net/http/httptest 提供给Test用的lib 对Request和Server做了改动,便于测试
net/http/httptrace 定义了一些钩子函数,在Http调用的生命周期进行回调 Client用的,定义Request时候可以指定.
net/http/httputil 一些辅助工具 dump用来快速打印request/response,reverseproxy实现了简单的反向代理..
net/http/httpinternal 定义了chunk传输的公用read(),write()
net/http/pprof 增加通过Http返回debug/pprof的能力 由于是init()直接作用于DefaultServerMux,如果自行New,那这个就不起作用.

2. http公用源码

代码名 作用 其他说明
net/url/url.go RFC 3986定义的URl解析处理 http地址是url的一种
header.go http报文头,保存在map[string][]string结构中 增,改,clone等方法
cookie.go http的Cookie结构 readSetCookies()从Header读取Cookies,SetCookie()把cookies放到ResponseWriter中.
request.go http请求体 ReadRequest()读取,Write()写入,NewRequest()创建
response.go http响应体 ReadResponse()读取,Write()写入,
method.og http方法枚举 GET,POST....
status.go http返回码枚举 StatusOK,StatusMovedPermanently....
http.go 一些工具方法 例如: hasPort()判断Url是否包含端口

Request结构体--(去了注释)

type Request struct {
 Method string
 URL *url.URL
 Proto string // "HTTP/1.0"
 ProtoMajor int // 1
 ProtoMinor int // 0
 Header Header
 Body io.ReadCloser
 GetBody func() (io.ReadCloser, error)
 ContentLength int64
 TransferEncoding []string
 Close bool
 Host string
 Form url.Values
 PostForm url.Values
 MultipartForm *multipart.Form
 Trailer Header
 RemoteAddr string
 TLS *tls.ConnectionState
 Response *Response
 ctx context.Context
}

包含了解析好的URL,eader,Form值;其中Repsonse应该是空的,只有重定向交易才有值.
ctx是后面加入了,为了能优雅进行超时关闭.

Response结构体--(去了注释)

type Response struct {
 Status string // e.g. "200 OK"
 StatusCode int // e.g. 200
 Proto string // e.g. "HTTP/1.0"
 ProtoMajor int // e.g. 1
 ProtoMinor int // e.g. 0
 Header Header
 Body io.ReadCloser
 ContentLength int64
 TransferEncoding []string
 Close bool
 Uncompressed bool
 Trailer Header
 // Request is the request that was sent to obtain this Response.
 // Request's Body is nil (having already been consumed).
 // This is only populated for Client requests.
 Request *Request
 TLS *tls.ConnectionState
}

代表了准备返回给Client的Response,其中Body是个ReadCloser

3. http客户端相关源码

代码名 作用 其他说明
transport.go 实际进行TCP通讯,进行传输 相当于Client和TCP中间的垫片层,把具体交互的细节封装起来
fileTransport.go 客户端传输文件
client.go 提供一个Http客户端 类似httpClient,可以直接用来连接服务器

transport的核心方法roundTrip(): -- 太长了,略..

client结构为:

type Client struct {
 Transport RoundTripper
 Jar CookieJar
 Timeout time.Duration
}

client东西都藏在Transport里面了,额外增加一个cookie控制和超时.
client的核心方法do(): (这点起名风格很spring啊)

func (c *Client) do(req *Request) (retres *Response, reterr error) {
 if testHookClientDoResult != nil {
 defer func() { testHookClientDoResult(retres, reterr) }()
 }
 if req.URL == nil {
 req.closeBody()
 return nil, &url.Error{
 Op: urlErrorOp(req.Method),
 Err: errors.New("http: nil Request.URL"),
 }
 }
 // 做了基础的校验后,创建函数变量
 var (
 deadline = c.deadline()
 reqs []*Request
 resp *Response
 copyHeaders = c.makeHeadersCopier(req)
 reqBodyClosed = false // have we closed the current req.Body?
 // Redirect behavior:
 redirectMethod string
 includeBody bool
 )
 // 定义错误处理函数
 uerr := func(err error) error {
 // the body may have been closed already by c.send()
 if !reqBodyClosed {
 req.closeBody()
 }
 var urlStr string
 if resp != nil && resp.Request != nil {
 urlStr = stripPassword(resp.Request.URL)
 } else {
 urlStr = stripPassword(req.URL)
 }
 return &url.Error{
 Op: urlErrorOp(reqs[0].Method),
 URL: urlStr,
 Err: err,
 }
 }
 // 这里直接死循环...
 for {
 // For all but the first request, create the next
 // request hop and replace req.
 // 第一次进来这边是空的,不会进去,知道下面加入reqs才第一次外呼
 if len(reqs) > 0 {
 loc := resp.Header.Get("Location")
 if loc == "" {
 resp.closeBody()
 return nil, uerr(fmt.Errorf("%d response missing Location header", resp.StatusCode))
 }
 u, err := req.URL.Parse(loc)
 if err != nil {
 resp.closeBody()
 return nil, uerr(fmt.Errorf("failed to parse Location header %q: %v", loc, err))
 }
 host := ""
 if req.Host != "" && req.Host != req.URL.Host {
 // If the caller specified a custom Host header and the
 // redirect location is relative, preserve the Host header
 // through the redirect. See issue #22233.
 if u, _ := url.Parse(loc); u != nil && !u.IsAbs() {
 host = req.Host
 }
 }
 ireq := reqs[0]
 // 重新构造本次轮询请求的Request
 req = &Request{
 Method: redirectMethod,
 Response: resp,
 URL: u,
 Header: make(Header),
 Host: host,
 Cancel: ireq.Cancel,
 ctx: ireq.ctx,
 }
 if includeBody && ireq.GetBody != nil {
 req.Body, err = ireq.GetBody()
 if err != nil {
 resp.closeBody()
 return nil, uerr(err)
 }
 req.ContentLength = ireq.ContentLength
 }
 // Copy original headers before setting the Referer,
 // in case the user set Referer on their first request.
 // If they really want to override, they can do it in
 // their CheckRedirect func.
 copyHeaders(req)
 // Add the Referer header from the most recent
 // request URL to the new one, if it's not https->http:
 if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL); ref != "" {
 req.Header.Set("Referer", ref)
 }
 err = c.checkRedirect(req, reqs)
 // Sentinel error to let users select the
 // previous response, without closing its
 // body. See Issue 10069.
 if err == ErrUseLastResponse {
 return resp, nil
 }
 // Close the previous response's body. But
 // read at least some of the body so if it's
 // small the underlying TCP connection will be
 // re-used. No need to check for errors: if it
 // fails, the Transport won't reuse it anyway.
 const maxBodySlurpSize = 2 << 10
 if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {
 io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize)
 }
 resp.Body.Close()
 if err != nil {
 // Special case for Go 1 compatibility: return both the response
 // and an error if the CheckRedirect function failed.
 // See https://golang.org/issue/3795
 // The resp.Body has already been closed.
 ue := uerr(err)
 ue.(*url.Error).URL = loc
 return resp, ue
 }
 }
 // 先把调用存到列表
 reqs = append(reqs, req)
 var err error
 var didTimeout func() bool
 // 这里调用send--用到transport发送报文
 if resp, didTimeout, err = c.send(req, deadline); err != nil {
 // c.send() always closes req.Body
 reqBodyClosed = true
 if !deadline.IsZero() && didTimeout() {
 err = &httpError{
 // TODO: early in cycle: s/Client.Timeout exceeded/timeout or context cancellation/
 err: err.Error() + " (Client.Timeout exceeded while awaiting headers)",
 timeout: true,
 }
 }
 return nil, uerr(err)
 }
 var shouldRedirect bool
 redirectMethod, shouldRedirect, includeBody = redirectBehavior(req.Method, resp, reqs[0])
 // 这里判断本次发送结果,如果不是重定向,就可以返回了,退出for循环.
 if !shouldRedirect {
 return resp, nil
 }
 // 本次body已经消费掉了,这里要关闭才行.
 req.closeBody()
 }
}

4. http服务端相关源码

代码名 作用 其他说明
fs.go 服务端接收文件
server.go 服务端实现
Handler的声明
// Handler的接口,只需要实现ServeHTTP即可
type Handler interface {
 ServeHTTP(ResponseWriter, *Request)
}
// 定义了HandlerFunc类型,它本身是方法,同时实现了ServeHTTP接口-->实际是调用自身
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
 f(w, r)
}
Server的定义
// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
 Addr string // TCP address to listen on, ":http" if empty
 // Server也只有一个默认的Handler,http.DefaultServeMux
 Handler Handler // handler to invoke, http.DefaultServeMux if nil
 TLSConfig *tls.Config
 ReadTimeout time.Duration
 ReadHeaderTimeout time.Duration
 WriteTimeout time.Duration
 
 IdleTimeout time.Duration
 MaxHeaderBytes int
 TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
 ConnState func(net.Conn, ConnState)
 ErrorLog *log.Logger
 BaseContext func(net.Listener) context.Context
 ConnContext func(ctx context.Context, c net.Conn) context.Context
 
 disableKeepAlives int32 // accessed atomically.
 inShutdown int32 // accessed atomically (non-zero means we're in Shutdown)
 nextProtoOnce sync.Once // guards setupHTTP2_* init
 nextProtoErr error // result of http2.ConfigureServer if used
 mu sync.Mutex
 listeners map[*net.Listener]struct{}
 activeConn map[*conn]struct{}
 doneChan chan struct{}
 onShutdown []func()
}
从代码层面跟踪一下如何进行服务的处理:
  1. 实际上Server并不直接处理Request,从其Serve()方法最后可以看出,它只负责连接的建立
 .......
 tempDelay = 0 
 c := srv.newConn(rw)
 c.setState(c.rwc, StateNew) // before Serve can return
 // *** 注意,这里使用goroutine,也就是说对于每个连接,都是单独的协程独立工作 ***
 go c.serve(ctx)
  1. 通过newConn构造出新的conn连接,调用连接的serve方法处理交互,一个conn是可以有多次交互的.
    首先,从连接读取客户端request
for {
 // 在连接还存活的状态下,会一直读取客户端的请求.
 w, err := c.readRequest(ctx)
 ...
 // 解析好Request后,创建serverHandler对象,调用他的ServeHTTP处理请求
 serverHandler{c.server}.ServeHTTP(w, w.req)
 }

然后,调用server的ServeHTTP()方法,找处理的ServeMux

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
 srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
 handler := sh.srv.Handler
 // 默认用Default
 if handler == nil {
 handler = DefaultServeMux
 }
 // 对"*"和"OPTIONS请求特殊处理
 if req.RequestURI == "*" && req.Method == "OPTIONS" {
 handler = globalOptionsHandler{}
 }
 // 调用ServeMux的ServeHTTP方法
 handler.ServeHTTP(rw, req)
}
  1. 调用ServeMux的ServeHTTP,找到程序员注册到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
}
type muxEntry struct {
 h Handler // 这个handler就是我们自定义的处理函数
 pattern string // 这个pattern就是我们自定义的处理路径
}

可以看出,ServeMux是一个map存储器,把具体的muxEntry保存住

// 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
 }
 // 这个方法其实是默认的路由,根据request找具体处理的handler,内部调用了match()方法,实际是一致循环所有muxEntry,然后用strings.HasPrefix判断
 // httprouter说这种方式太慢,用基数树存储结构进行加速.--原生的方法确实不高效
 h, _ := mux.Handler(r)
 // 这里h就是我们调用http.HandleFunc("/hello", hello)时候注册的hello了.
 h.ServeHTTP(w, r)
}

从server代码可以看出,MuxEntry相当于Servlet,负责具体处理,ServerMux是所有Entry的集合,通过match进行router匹配,指派任务.系统提供了默认的Mux统一管理,调用http.handle()实际是想ServerMux进行注册...
ServeHTTP()才是真正具体工作的地方,调用流程为serverHandle-> DefaultServeMux -> 自定义HandlerFunc或者Handler (都叫ServeHTTP,就是为了让人看不懂吗....)

三. httpServer运行分析

好像已经包含在上面的源码分析了...略

四. 文档参考

https://juejin.im/post/5dd11baff265da0c0c1fe813

https://colobu.com/2018/07/25/exposing-go-on-the-internet/

https://cizixs.com/2016/08/17/golang-http-server-side/


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:沉寂之舟

查看原文:Go常用库-网络框架Gin(1) - 原生http包

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2035 次点击
1 回复 | 直到 2025年05月13日 22:27:39
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏