经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
快速掌握Go 语言 HTTP 标准库的实现方法
来源:jb51  时间:2022/7/25 19:16:09  对本文有异议

本篇文章来分析一下 Go 语言 HTTP 标准库是如何实现的。

本文使用的go的源码1.15.7

基于HTTP构建的服务标准模型包括两个端,客户端(Client)和服务端(Server)。HTTP 请求从客户端发出,服务端接受到请求后进行处理然后将响应返回给客户端。所以http服务器的工作就在于如何接受来自客户端的请求,并向客户端返回响应。

一个典型的 HTTP 服务应该如图所示:

HTTP client

在 Go 中可以直接通过 HTTP 包的 Get 方法来发起相关请求数据,一个简单例子:

  1. func main() {
  2. resp, err := http.Get("http://httpbin.org/get?name=luozhiyun&age=27")
  3. if err != nil {
  4. fmt.Println(err)
  5. return
  6. }
  7. defer resp.Body.Close()
  8. body, _ := ioutil.ReadAll(resp.Body)
  9. fmt.Println(string(body))
  10. }

我们下面通过这个例子来进行分析。

HTTP 的 Get 方法会调用到 DefaultClient 的 Get 方法,DefaultClient 是 Client 的一个空实例,所以最后会调用到 Client 的 Get 方法:

Client 结构体

  1. type Client struct {
  2. Transport RoundTripper
  3. CheckRedirect func(req *Request, via []*Request) error
  4. Jar CookieJar
  5. Timeout time.Duration
  6. }

Client 结构体总共由四个字段组成:

Transport:表示 HTTP 事务,用于处理客户端的请求连接并等待服务端的响应;

CheckRedirect:用于指定处理重定向的策略;

Jar:用于管理和存储请求中的 cookie;

Timeout:指定客户端请求的最大超时时间,该超时时间包括连接、任何的重定向以及读取相应的时间;

初始化请求

  1. func (c *Client) Get(url string) (resp *Response, err error) {
  2. // 根据方法名、URL 和请求体构建请求
  3. req, err := NewRequest("GET", url, nil)
  4. if err != nil {
  5. return nil, err
  6. }
  7. // 执行请求
  8. return c.Do(req)
  9. }

我们要发起一个请求首先需要根据请求类型构建一个完整的请求头、请求体、请求参数。然后才是根据请求的完整结构来执行请求。

NewRequest 初始化请求

NewRequest 会调用到 NewRequestWithContext 函数上。这个函数会根据请求返回一个 Request 结构体,它里面包含了一个 HTTP 请求所有信息。

Request

Request 结构体有很多字段,我这里列举几个大家比较熟悉的字段:

NewRequestWithContext

  1. func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
  2. ...
  3. // parse url
  4. u, err := urlpkg.Parse(url)
  5. if err != nil {
  6. return nil, err
  7. }
  8. rc, ok := body.(io.ReadCloser)
  9. if !ok && body != nil {
  10. rc = ioutil.NopCloser(body)
  11. }
  12. u.Host = removeEmptyPort(u.Host)
  13. req := &Request{
  14. ctx: ctx,
  15. Method: method,
  16. URL: u,
  17. Proto: "HTTP/1.1",
  18. ProtoMajor: 1,
  19. ProtoMinor: 1,
  20. Header: make(Header),
  21. Body: rc,
  22. Host: u.Host,
  23. }
  24. ...
  25. return req, nil
  26. }

NewRequestWithContext 函数会将请求封装成一个 Request 结构体并返回。

准备 http 发送请求

如上图所示,Client 调用 Do 方法处理发送请求最后会调用到 send 函数中。

  1. func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
  2. resp, didTimeout, err = send(req, c.transport(), deadline)
  3. if err != nil {
  4. return nil, didTimeout, err
  5. }
  6. ...
  7. return resp, nil, nil
  8. }

Transport

Client 的 send 方法在调用 send 函数进行下一步的处理前会先调用 transport 方法获取 DefaultTransport 实例,该实例如下:

  1. var DefaultTransport RoundTripper = &Transport{
  2. // 定义 HTTP 代理策略
  3. Proxy: ProxyFromEnvironment,
  4. DialContext: (&net.Dialer{
  5. Timeout: 30 * time.Second,
  6. KeepAlive: 30 * time.Second,
  7. DualStack: true,
  8. }).DialContext,
  9. ForceAttemptHTTP2: true,
  10. // 最大空闲连接数
  11. MaxIdleConns: 100,
  12. // 空闲连接超时时间
  13. IdleConnTimeout: 90 * time.Second,
  14. // TLS 握手超时时间
  15. TLSHandshakeTimeout: 10 * time.Second,
  16. ExpectContinueTimeout: 1 * time.Second,
  17. }

Transport 实现 RoundTripper 接口,该结构体会发送 http 请求并等待响应。

  1. type RoundTripper interface {
  2. RoundTrip(*Request) (*Response, error)
  3. }

从 RoundTripper 接口我们也可以看出,该接口定义的 RoundTrip 方法会具体的处理请求,处理完毕之后会响应 Response。

回到我们上面的 Client 的 send 方法中,它会调用 send 函数,这个函数主要逻辑都交给 Transport 的 RoundTrip 方法来执行。

RoundTrip 会调用到 roundTrip 方法中:

  1. func (t *Transport) roundTrip(req *Request) (*Response, error) {
  2. t.nextProtoOnce.Do(t.onceSetNextProtoDefaults)
  3. ctx := req.Context()
  4. trace := httptrace.ContextClientTrace(ctx)
  5. ...
  6. for {
  7. select {
  8. case <-ctx.Done():
  9. req.closeBody()
  10. return nil, ctx.Err()
  11. default:
  12. }
  13.  
  14. // 封装请求
  15. treq := &transportRequest{Request: req, trace: trace, cancelKey: cancelKey}
  16. cm, err := t.connectMethodForRequest(treq)
  17. if err != nil {
  18. req.closeBody()
  19. return nil, err
  20. }
  21. // 获取连接
  22. pconn, err := t.getConn(treq, cm)
  23. if err != nil {
  24. t.setReqCanceler(cancelKey, nil)
  25. req.closeBody()
  26. return nil, err
  27. }
  28.  
  29. // 等待响应结果
  30. var resp *Response
  31. if pconn.alt != nil {
  32. // HTTP/2 path.
  33. t.setReqCanceler(cancelKey, nil) // not cancelable with CancelRequest
  34. resp, err = pconn.alt.RoundTrip(req)
  35. } else {
  36. resp, err = pconn.roundTrip(treq)
  37. }
  38. if err == nil {
  39. resp.Request = origReq
  40. return resp, nil
  41. }
  42. ...
  43. }
  44. }

roundTrip 方法会做两件事情:

  • 调用 Transport 的 getConn 方法获取连接;
  • 在获取到连接后,调用 persistConn 的 roundTrip 方法等待请求响应结果;获取连接 getConn

getConn 有两个阶段:

调用 queueForIdleConn 获取空闲 connection;调用 queueForDial 等待创建新的 connection;

  1. func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
  2. req := treq.Request
  3. trace := treq.trace
  4. ctx := req.Context()
  5. if trace != nil && trace.GetConn != nil {
  6. trace.GetConn(cm.addr())
  7. }
  8. // 将请求封装成 wantConn 结构体
  9. w := &wantConn{
  10. cm: cm,
  11. key: cm.key(),
  12. ctx: ctx,
  13. ready: make(chan struct{}, 1),
  14. beforeDial: testHookPrePendingDial,
  15. afterDial: testHookPostPendingDial,
  16. }
  17. defer func() {
  18. if err != nil {
  19. w.cancel(t, err)
  20. }
  21. }()
  22.  
  23. // 获取空闲连接
  24. if delivered := t.queueForIdleConn(w); delivered {
  25. pc := w.pc
  26. ...
  27. t.setReqCanceler(treq.cancelKey, func(error) {})
  28. return pc, nil
  29. }
  30.  
  31. // 创建连接
  32. t.queueForDial(w)
  33.  
  34. select {
  35. // 获取到连接后进入该分支
  36. case <-w.ready:
  37. ...
  38. return w.pc, w.err
  39. ...
  40. }

获取空闲连接 queueForIdleConn

成功获取到空闲 connection:

成功获取 connection 分为如下几步:

  • 根据当前的请求的地址去空闲 connection 字典中查看存不存在空闲的 connection 列表;
  • 如果能获取到空闲的 connection 列表,那么获取到列表的最后一个 connection;
  • 返回;

获取不到空闲 connection:

当获取不到空闲 connection 时:

  • 根据当前的请求的地址去空闲 connection 字典中查看存不存在空闲的 connection 列表;
  • 不存在该请求的 connection 列表,那么将该 wantConn 加入到 等待获取空闲 connection 字典中;

从上面的图解应该就很能看出这一步会怎么操作了,这里简要的分析一下代码,让大家更清楚里面的逻辑:

  1. func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
  2. if t.DisableKeepAlives {
  3. return false
  4. }
  5.  
  6. t.idleMu.Lock()
  7. defer t.idleMu.Unlock()
  8. t.closeIdle = false
  9.  
  10. if w == nil {
  11. return false
  12. }
  13.  
  14. // 计算空闲连接超时时间
  15. var oldTime time.Time
  16. if t.IdleConnTimeout > 0 {
  17. oldTime = time.Now().Add(-t.IdleConnTimeout)
  18. }
  19. // Look for most recently-used idle connection.
  20. // 找到key相同的 connection 列表
  21. if list, ok := t.idleConn[w.key]; ok {
  22. stop := false
  23. delivered := false
  24. for len(list) > 0 && !stop {
  25. // 找到connection列表最后一个
  26. pconn := list[len(list)-1]
  27. // 检查这个 connection 是不是等待太久了
  28. tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
  29. if tooOld {
  30. go pconn.closeConnIfStillIdle()
  31. }
  32. // 该 connection 被标记为 broken 或 闲置太久 continue
  33. if pconn.isBroken() || tooOld {
  34. list = list[:len(list)-1]
  35. continue
  36. }
  37. // 尝试将该 connection 写入到 w 中
  38. delivered = w.tryDeliver(pconn, nil)
  39. if delivered {
  40. // 操作成功,需要将 connection 从空闲列表中移除
  41. if pconn.alt != nil {
  42. } else {
  43. t.idleLRU.remove(pconn)
  44. list = list[:len(list)-1]
  45. }
  46. }
  47. stop = true
  48. }
  49. if len(list) > 0 {
  50. t.idleConn[w.key] = list
  51. } else {
  52. // 如果该 key 对应的空闲列表不存在,那么将该key从字典中移除
  53. delete(t.idleConn, w.key)
  54. }
  55. if stop {
  56. return delivered
  57. }
  58. }
  59. // 如果找不到空闲的 connection
  60. if t.idleConnWait == nil {
  61. t.idleConnWait = make(map[connectMethodKey]wantConnQueue)
  62. }
  63. // 将该 wantConn 加入到 等待获取空闲 connection 字典中
  64. q := t.idleConnWait[w.key]
  65. q.cleanFront()
  66. q.pushBack(w)
  67. t.idleConnWait[w.key] = q
  68. return false
  69. }

上面的注释已经很清楚了,我这里就不再解释了。

建立连接 queueForDial

在获取不到空闲连接之后,会尝试去建立连接,从上面的图大致可以看到,总共分为以下几个步骤:

  • 在调用 queueForDial 方法的时候会校验 MaxConnsPerHost 是否未设置或已达上限;
  • 检验不通过则将当前的请求放入到 connsPerHostWait 等待字典中;
  • 如果校验通过那么会异步的调用 dialConnFor 方法创建连接;

dialConnFor 方法首先会调用 dialConn 方法创建 TCP 连接,然后启动两个异步线程来处理读写数据,然后调用 tryDeliver 将连接绑定到 wantConn 上面。

下面进行代码分析:

  1. func (t *Transport) queueForDial(w *wantConn) {
  2. w.beforeDial()
  3. // 小于零说明无限制,异步建立连接
  4. if t.MaxConnsPerHost <= 0 {
  5. go t.dialConnFor(w)
  6. return
  7. }
  8.  
  9. t.connsPerHostMu.Lock()
  10. defer t.connsPerHostMu.Unlock()
  11. // 每个 host 建立的连接数没达到上限,异步建立连接
  12. if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {
  13. if t.connsPerHost == nil {
  14. t.connsPerHost = make(map[connectMethodKey]int)
  15. }
  16. t.connsPerHost[w.key] = n + 1
  17. go t.dialConnFor(w)
  18. return
  19. }
  20. //每个 host 建立的连接数已达到上限,需要进入等待队列
  21. if t.connsPerHostWait == nil {
  22. t.connsPerHostWait = make(map[connectMethodKey]wantConnQueue)
  23. }
  24. q := t.connsPerHostWait[w.key]
  25. q.cleanFront()
  26. q.pushBack(w)
  27. t.connsPerHostWait[w.key] = q
  28. }

这里主要进行参数校验,如果最大连接数限制为零,亦或是每个 host 建立的连接数没达到上限,那么直接异步建立连接。

dialConnFor

  1. func (t *Transport) dialConnFor(w *wantConn) {
  2. defer w.afterDial()
  3. // 建立连接
  4. pc, err := t.dialConn(w.ctx, w.cm)
  5. // 连接绑定 wantConn
  6. delivered := w.tryDeliver(pc, err)
  7. // 建立连接成功,但是绑定 wantConn 失败
  8. // 那么将该连接放置到空闲连接字典或调用 等待获取空闲 connection 字典 中的元素执行
  9. if err == nil && (!delivered || pc.alt != nil) {
  10. t.putOrCloseIdleConn(pc)
  11. }
  12. if err != nil {
  13. t.decConnsPerHost(w.key)
  14. }
  15. }

dialConnFor 会调用 dialConn 进行 TCP 连接创建,创建完毕之后调用 tryDeliver 方法和 wantConn 进行绑定。

dialConn

  1. func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) {
  2. // 创建连接结构体
  3. pconn = &persistConn{
  4. t: t,
  5. cacheKey: cm.key(),
  6. reqch: make(chan requestAndChan, 1),
  7. writech: make(chan writeRequest, 1),
  8. closech: make(chan struct{}),
  9. writeErrCh: make(chan error, 1),
  10. writeLoopDone: make(chan struct{}),
  11. }
  12. ...
  13. if cm.scheme() == "https" && t.hasCustomTLSDialer() {
  14. ...
  15. } else {
  16. // 建立 tcp 连接
  17. conn, err := t.dial(ctx, "tcp", cm.addr())
  18. if err != nil {
  19. return nil, wrapErr(err)
  20. }
  21. pconn.conn = conn
  22. }
  23. ...
  24.  
  25. if s := pconn.tlsState; s != nil && s.NegotiatedProtocolIsMutual && s.NegotiatedProtocol != "" {
  26. if next, ok := t.TLSNextProto[s.NegotiatedProtocol]; ok {
  27. alt := next(cm.targetAddr, pconn.conn.(*tls.Conn))
  28. if e, ok := alt.(http2erringRoundTripper); ok {
  29. // pconn.conn was closed by next (http2configureTransport.upgradeFn).
  30. return nil, e.err
  31. }
  32. return &persistConn{t: t, cacheKey: pconn.cacheKey, alt: alt}, nil
  33. }
  34. }
  35.  
  36. pconn.br = bufio.NewReaderSize(pconn, t.readBufferSize())
  37. pconn.bw = bufio.NewWriterSize(persistConnWriter{pconn}, t.writeBufferSize())
  38. //为每个连接异步处理读写数据
  39. go pconn.readLoop()
  40. go pconn.writeLoop()
  41. return pconn, nil
  42. }

这里会根据 schema 的不同设置不同的连接配置,我上面显示的是我们常用的 HTTP 连接的创建过程。对于 HTTP 来说会建立 tcp 连接,然后为连接异步处理读写数据,最后将创建好的连接返回。

等待响应

这一部分的内容会稍微复杂一些,但确实非常的有趣。

在创建连接的时候会初始化两个 channel :writech 负责写入请求数据,reqch负责读取响应数据。我们在上面创建连接的时候,也提到了会为连接创建两个异步循环 readLoop 和 writeLoop 来负责处理读写数据。

在获取到连接之后,会调用连接的 roundTrip 方法,它首先会将请求数据写入到 writech 管道中,writeLoop 接收到数据之后就会处理请求。

然后 roundTrip 会将 requestAndChan 结构体写入到 reqch 管道中,然后 roundTrip 会循环等待。readLoop 读取到响应数据之后就会通过 requestAndChan 结构体中保存的管道将数据封装成 responseAndError 结构体回写,这样 roundTrip 就可以接受到响应数据结束循环等待并返回。

roundTrip

  1. func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
  2. ...
  3. writeErrCh := make(chan error, 1)
  4. // 将请求数据写入到 writech 管道中
  5. pc.writech <- writeRequest{req, writeErrCh, continueCh}
  6.  
  7. // 用于接收响应的管道
  8. resc := make(chan responseAndError)
  9. // 将用于接收响应的管道封装成 requestAndChan 写入到 reqch 管道中
  10. pc.reqch <- requestAndChan{
  11. req: req.Request,
  12. cancelKey: req.cancelKey,
  13. ch: resc,
  14. ...
  15. }
  16. ...
  17. for {
  18. testHookWaitResLoop()
  19. select {
  20. // 接收到响应数据
  21. case re := <-resc:
  22. if (re.res == nil) == (re.err == nil) {
  23. panic(fmt.Sprintf("internal error: exactly one of res or err should be set; nil=%v", re.res == nil))
  24. }
  25. if debugRoundTrip {
  26. req.logf("resc recv: %p, %T/%#v", re.res, re.err, re.err)
  27. }
  28. if re.err != nil {
  29. return nil, pc.mapRoundTripError(req, startBytesWritten, re.err)
  30. }
  31. // 返回响应数据
  32. return re.res, nil
  33. ...
  34. }
  35. }

这里会封装好 writeRequest 作为发送请求的数据,并将用于接收响应的管道封装成 requestAndChan 写入到 reqch 管道中,然后循环等待接受响应。

然后 writeLoop 会进行请求数据 writeRequest :

  1. func (pc *persistConn) writeLoop() {
  2. defer close(pc.writeLoopDone)
  3. for {
  4. select {
  5. case wr := <-pc.writech:
  6. startBytesWritten := pc.nwrite
  7. // 向 TCP 连接中写入数据,并发送至目标服务器
  8. err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh))
  9. ...
  10. case <-pc.closech:
  11. return
  12. }
  13. }
  14. }

这里会将从 writech 管道中获取到的数据写入到 TCP 连接中,并发送至目标服务器。
readLoop

  1. func (pc *persistConn) readLoop() {
  2. closeErr := errReadLoopExiting // default value, if not changed below
  3. defer func() {
  4. pc.close(closeErr)
  5. pc.t.removeIdleConn(pc)
  6. }()
  7. ...
  8. alive := true
  9. for alive {
  10. pc.readLimit = pc.maxHeaderResponseSize()
  11. // 获取 roundTrip 发送的结构体
  12. rc := <-pc.reqch
  13. trace := httptrace.ContextClientTrace(rc.req.Context())
  14.  
  15. var resp *Response
  16. if err == nil {
  17. // 读取数据
  18. resp, err = pc.readResponse(rc, trace)
  19. } else {
  20. err = transportReadFromServerError{err}
  21. closeErr = err
  22. }
  23.  
  24. ...
  25. // 将响应数据写回到管道中
  26. select {
  27. case rc.ch <- responseAndError{res: resp}:
  28. case <-rc.callerGone:
  29. return
  30. }
  31. ...
  32. }
  33. }

这里是从 TCP 连接中读取到对应的请求响应数据,通过 roundTrip 传入的管道再回写,然后 roundTrip 就会接受到数据并获取的响应数据返回。

http server

我这里继续以一个简单的例子作为开头:

  1. func HelloHandler(w http.ResponseWriter, r *http.Request) {
  2. fmt.Fprintf(w, "Hello World")
  3. }
  4. func main () {
  5. http.HandleFunc("/", HelloHandler)
  6. http.ListenAndServe(":8000", nil)
  7. }

在实现上面我先用一张图进行简要的介绍一下:

其实我们从上面例子的方法名就可以知道一些大致的步骤:

  • 注册处理器到一个 hash 表中,可以通过键值路由匹配;
  • 注册完之后就是开启循环监听,每监听到一个连接就会创建一个 Goroutine;
  • 在创建好的 Goroutine 里面会循环的等待接收请求数据,然后根据请求的地址去处理器路由表中匹配对应的处理器,然后将请求交给处理器处理;注册处理器

处理器的注册如上面的例子所示,是通过调用 HandleFunc 函数来实现的。

HandleFunc 函数会一直调用到 ServeMux 的 Handle 方法中。

  1. func (mux *ServeMux) Handle(pattern string, handler Handler) {
  2. mux.mu.Lock()
  3. defer mux.mu.Unlock()
  4. ...
  5. e := muxEntry{h: handler, pattern: pattern}
  6. mux.m[pattern] = e
  7. if pattern[len(pattern)-1] == '/' {
  8. mux.es = appendSorted(mux.es, e)
  9. }
  10.  
  11. if pattern[0] != '/' {
  12. mux.hosts = true
  13. }
  14. }

Handle 会根据路由作为 hash 表的键来保存 muxEntry 对象,muxEntry封装了 pattern 和 handler。如果路由表达式以'/'结尾,则将对应的muxEntry对象加入到[]muxEntry中。

hash 表是用于路由精确匹配,[]muxEntry用于部分匹配。

监听

监听是通过调用 ListenAndServe 函数,里面会调用 server 的 ListenAndServe 方法:

  1. func (srv *Server) ListenAndServe() error {
  2. if srv.shuttingDown() {
  3. return ErrServerClosed
  4. }
  5. addr := srv.Addr
  6. if addr == "" {
  7. addr = ":http"
  8. }
  9. // 监听端口
  10. ln, err := net.Listen("tcp", addr)
  11. if err != nil {
  12. return err
  13. }
  14. // 循环接收监听到的网络请求
  15. return srv.Serve(ln)
  16. }

Serve

  1. func (srv *Server) Serve(l net.Listener) error {
  2. ...
  3. baseCtx := context.Background()
  4. ctx := context.WithValue(baseCtx, ServerContextKey, srv)
  5. for {
  6. // 接收 listener 过来的网络连接
  7. rw, err := l.Accept()
  8. ...
  9. tempDelay = 0
  10. c := srv.newConn(rw)
  11. c.setState(c.rwc, StateNew)
  12. // 创建协程处理连接
  13. go c.serve(connCtx)
  14. }
  15. }

Serve 这个方法里面会用一个循环去接收监听到的网络连接,然后创建协程处理连接。所以难免就会有一个问题,如果并发很高的话,可能会一次性创建太多协程,导致处理不过来的情况。

处理请求

处理请求是通过为每个连接创建 goroutine 来处理对应的请求:

  1. func (c *conn) serve(ctx context.Context) {
  2. c.remoteAddr = c.rwc.RemoteAddr().String()
  3. ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
  4. ...
  5. ctx, cancelCtx := context.WithCancel(ctx)
  6. c.cancelCtx = cancelCtx
  7. defer cancelCtx()
  8. c.r = &connReader{conn: c}
  9. c.bufr = newBufioReader(c.r)
  10. c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
  11. for {
  12. // 读取请求
  13. w, err := c.readRequest(ctx)
  14. ...
  15. // 根据请求路由调用处理器处理请求
  16. serverHandler{c.server}.ServeHTTP(w, w.req)
  17. w.cancelCtx()
  18. if c.hijacked() {
  19. return
  20. }
  21. w.finishRequest()
  22. ...
  23. }
  24. }

当一个连接建立之后,该连接中所有的请求都将在这个协程中进行处理,直到连接被关闭。在 for 循环里面会循环调用 readRequest 读取请求进行处理。

请求处理是通过调用 ServeHTTP 进行的:

  1. type serverHandler struct {
  2. srv *Server
  3. }
  4.  
  5. func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  6. handler := sh.srv.Handler
  7. if handler == nil {
  8. handler = DefaultServeMux
  9. }
  10. if req.RequestURI == "*" && req.Method == "OPTIONS" {
  11. handler = globalOptionsHandler{}
  12. }
  13. handler.ServeHTTP(rw, req)
  14. }

serverHandler 其实就是 Server 包装了一层。这里的 sh.srv.Handler参数实际上是传入的 ServeMux 实例,所以这里最后会调用到 ServeMux 的 ServeHTTP 方法。

最终会通过 handler 调用到 match 方法进行路由匹配:

  1. func (mux *ServeMux) match(path string) (h Handler, pattern string) {
  2. v, ok := mux.m[path]
  3. if ok {
  4. return v.h, v.pattern
  5. }
  6.  
  7. for _, e := range mux.es {
  8. if strings.HasPrefix(path, e.pattern) {
  9. return e.h, e.pattern
  10. }
  11. }
  12. return nil, ""
  13. }

这个方法里首先会利用进行精确匹配,如果匹配成功那么直接返回;匹配不成功,那么会根据 []muxEntry中保存的和当前路由最接近的已注册的父节点路由进行匹配,否则继续匹配下一个父节点路由,直到根路由/。最后会调用对应的处理器进行处理。

Reference

https://cloud.tencent.com/developer/article/1515297

https://duyanghao.github.io/http-transport

https://draveness.me/golang/docs/part4-advanced/ch09-stdlib/golang-net-http

https://laravelacademy.org/post/21003

https://segmentfault.com/a/1190000021653550

到此这篇关于快速掌握Go 语言 HTTP 标准库的实现方法的文章就介绍到这了,更多相关Go 语言 HTTP 标准库内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号