美文网首页
golang http请求

golang http请求

作者: wangshanshi | 来源:发表于2020-03-29 14:59 被阅读0次

1. http1请求

参考文章:

  1. <<etcd技术内幕>>
  2. Go Http包解析:为什么需要response.Body.Close()
  3. 为什么Response.Body需要被关闭

1.1 server端

启动一个server:

package main

import (
    "io"
    "net"
    "net/http"
)

func main() {
    // Hello world, the web server

    helloHandler := func(w http.ResponseWriter, req *http.Request) {
        io.WriteString(w, "Hello, world!\n")
    }

    http.HandleFunc("/hello", helloHandler)
    ln, _ := net.Listen("tcp", ":8080")
    http.Server{
        Addr:              "",
        Handler:           nil,
        TLSConfig:         nil,
        ReadTimeout:       0,
        ReadHeaderTimeout: 0,
        WriteTimeout:      0,
        IdleTimeout:       0,
        MaxHeaderBytes:    0,
        TLSNextProto:      nil,
        ConnState:         nil,
        ErrorLog:          nil,
        BaseContext:       nil,
        ConnContext:       nil,
    }.Serve(ln)
}

http.Server.Serve

根据代码中的注释,主要干了这么一件事:

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
image.png image.png
image.png

剩下的步骤就比较明显了,DefaultServerMux会将请求路由到对应的handler

小结

整体结构如下:


image.png

1.2 client端

client端比较复杂,有conn缓存的问题存在。贯彻其中的一个问题是需要调用resp.Body.Close()吗?

参考文档:
https://segmentfault.com/a/1190000020086816
https://www.cnblogs.com/cobbliu/p/4517598.html

1.2.1 client结构

func main() {
    transport := http.Transport{
    }

    client := http.Client{
        Transport: &transport,
    }
    
    api := "https://www.baidu.com"
    request, _ := http.NewRequest(http.MethodGet, api, nil)
    resp, _ := client.Do(request)
    defer resp.Body.Close()

}

Transport层
Transport是RoundTripper的一种实现

// RoundTripper is an interface representing the ability to execute a
// single HTTP transaction, obtaining the Response for a given Request.
//
// A RoundTripper must be safe for concurrent use by multiple
// goroutines.

RoundTrip(*Request) (*Response, error)

所以关键就是RoundTrip这个函数,Transport采用了一系列复杂的策略来高效的实现这个函数。

type Transport struct {
    idleMu     sync.Mutex
    wantIdle   bool                                // user has requested to close all idle conns
  // 空闲的连接 缓存的地方
    idleConn   map[connectMethodKey][]*persistConn // most recently used at end
  // connectMethodKey => 空闲连接的chan 形成的map
  // 有空闲连接放入的时候,首先尝试放入这个chan,方便另一个可能需要连接的goroutine直接使用,如果没有goroutine需要连接,就放入到上面的idleConn里面,便于后面的请求连接复用
    idleConnCh map[connectMethodKey]chan *persistConn
    // DisableKeepAlives, if true, disables HTTP keep-alives and
    // will only use the connection to the server for a single
    // HTTP request.
    //
    // This is unrelated to the similarly named TCP keep-alives.
  // 是否开启 keepAlive,为true的话,连接不会被复用
    DisableKeepAlives bool
    // MaxIdleConns controls the maximum number of idle (keep-alive)
    // connections across all hosts. Zero means no limit.
  // 所有hosts对应的最大的连接总数
    MaxIdleConns int
  // 每一个host对应的最大的空闲连接数
    MaxIdleConnsPerHost int
  // 每一个host对应的最大连接数
    MaxConnsPerHost int
}

pconnect

persistConn是普通网络连接net.Conn上的一层封装,一般表示的是TCP长连接: 在建立连接并传输数据完成之后,不调用close()方法关闭连接,后续即可复用该TCP连接。

type persistConn struct {
   // alt optionally specifies the TLS NextProto RoundTripper.
   // This is used for HTTP/2 today and future protocols later.
   // If it's non-nil, the rest of the fields are unused.
   alt RoundTripper

   t         *Transport
   cacheKey  connectMethodKey
   conn      net.Conn
   tlsState  *tls.ConnectionState
   br        *bufio.Reader       // from conn
   bw        *bufio.Writer       // to conn
   nwrite    int64               // bytes written
   // roundTrip 往 这个chan 里写入request,readLoop从这个 chan 读取request
   reqch     chan requestAndChan // written by roundTrip; read by readLoop
   // roundTrip 往 这个chan 里写入request 和 writeErrCh,writeLoop从这个 chan 读取request写入大盘 连接 里,并写入 err 到 writeErrCh chan
   writech   chan writeRequest   // written by roundTrip; read by writeLoop
   closech   chan struct{}       // closed when conn closed
   // 判断body是否读取完
   sawEOF    bool  // whether we've seen EOF from conn; owned by readLoop
   // writeErrCh passes the request write error (usually nil)
   // from the writeLoop goroutine to the readLoop which passes
   // it off to the res.Body reader, which then uses it to decide
   // whether or not a connection can be reused. Issue 7569.
   // writeLoop 写入 err的 chan
   writeErrCh chan error
     // writeLoop 结束的时候关闭
   writeLoopDone chan struct{} // closed when write loop ends
}

1.2.2 roundtrip 结构

当一个请求发出时,最终调用的是

  // 调用 Transport.RoundTrip 来处理请求
    resp, err = rt.RoundTrip(req)

主要流程如下:

func (t *Transport) roundTrip(req *Request) (*Response, error) {

      // Get the cached or newly-created connection to either the
      // host (for http or https), the http proxy, or the http proxy
      // pre-CONNECTed to https server. In any case, we'll be ready
      // to send it requests.
      // 根据请求和connectMethod获取一个可用的连接,重要,后面会具体分析
      pconn, err := t.getConn(treq, cm)
      if err != nil {
         t.setReqCanceler(req, nil)
         req.closeBody()
         return nil, err
      }

      var resp *Response
      if pconn.alt != nil {
         // HTTP/2 path.
         // http2 使用,这里不展开
         t.decHostConnCount(cm.key()) // don't count cached http2 conns toward conns per host
         t.setReqCanceler(req, nil)   // not cancelable with CancelRequest
         resp, err = pconn.alt.RoundTrip(req)
      } else {
         // 获取response,这里是重点,后面展开
         resp, err = pconn.roundTrip(treq)
      }
         
         // 判断获取response是否有误及错误处理等操作,无关紧要,忽略
     
   }
}

获取一个persist connect并调用其roundTrip函数。

1.2.3 获取长连接并roundTrip

image.png
image.png
image.png

此时,对于一个persist connect,有三个协程 roudTrip、readLoop、writeLoop。


image.png

roundTrip

image.png
image.png image.png image.png

1.2.4 服务端断开连接

readLoop

image.png

这会导致roudTrip:


image.png

这又会导致readLoop:


image.png
image.png

perconnect close将会导致writeLoop和roundTrip退出,同时removeIdleConn将会导致连接移除

1.2.5 客户端写失败

writeLoop

image.png

从而导致readLoop和roundTrip退出

1.2.6 完成一次http请求

当读取所有的响应数据之后,再Read将会导致逻辑上的io.EOF, 此时会调用


image.png image.png

从而导致pconn被复用:


image.png

1.2.7 resp.Body.Close的作用

从实现上看只要body被读完,连接就能被回收,只有需要抛弃body时才需要close,似乎不关闭也可以。但那些正常情况能读完的body,即第一种情况,在出现错误时就不会被读完,即转为第二种情况。而分情况处理则增加了维护者的心智负担,所以始终close body是最佳选择。

当异常情况下调用resp.Body.Close时, 此时err != io.EOF,将会导致readLoop返回从而导致pconn被回收。

1.2.8 设置net.Conn为短链接

transport := http.Transport{
        Dial:              dialTimeout,
        DisableKeepAlives: true,
}

将会导致在建立http连接时,有:


image.png

此时,短链接只用于一次性请求并且不能被reuse

相关文章

网友评论

      本文标题:golang http请求

      本文链接:https://www.haomeiwen.com/subject/mmpyuhtx.html