1. http1请求
参考文章:
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.



剩下的步骤就比较明显了,DefaultServerMux会将请求路由到对应的handler
小结
整体结构如下:

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



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

roundTrip




1.2.4 服务端断开连接
readLoop

这会导致roudTrip:

这又会导致readLoop:


perconnect close将会导致writeLoop和roundTrip退出,同时removeIdleConn将会导致连接移除
1.2.5 客户端写失败
writeLoop

从而导致readLoop和roundTrip退出
1.2.6 完成一次http请求
当读取所有的响应数据之后,再Read将会导致逻辑上的io.EOF, 此时会调用


从而导致pconn被复用:

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连接时,有:

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