go封裝了http客戶端,請求遠端資料非常方便,看些原始碼底層如何實現。
resp, err := http.Get("https://baidu.com")
if err != nil {
fmt.Printf("發起請求失敗:%v", err)
return
}
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
請求的大致流程
1.根據請求條件,構建request物件
2.所有的client請求,都會經過client.do()處理
func (c *Client) do(req *Request) (retres *Response, reterr error)
2.1 request請求使用client.send()處理
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error)
resp, didTimeout, err = send(req, c.transport(), deadline)//預設傳DefaultTransport
3.send函式
func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
resp, err = rt.RoundTrip(req)
}
4.DefaultTransport的RoundTrip方法,實際就是Transport的RoundTrip方法
func (t *Transport) roundTrip(req *Request) (*Response, error) {
treq := &transportRequest{Request: req, trace: trace} //封裝新的request
cm, err := t.connectMethodForRequest(treq)
pconn, err := t.getConn(treq, cm) //使用連線池技術,獲取連線物件*persistConn,
resp, err = pconn.roundTrip(treq) //使用連線物件獲取response
}
5.使用連線池技術,獲取連線物件*persistConn
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
w := &wantConn{ //構建連線物件
cm: cm,
key: cm.key(),
ctx: ctx,
ready: make(chan struct{}, 1),
beforeDial: testHookPrePendingDial,
afterDial: testHookPostPendingDial,
}
if delivered := t.queueForIdleConn(w); delivered {//從連線池獲取符合的連線物件,有就返回
pc := w.pc
return pc, nil
}
t.queueForDial(w)//發起連線
select {
case <-w.ready: //連線準備好,就返回連線物件
return w.pc, w.err
}
5.1 Transport.queueForDial發起連線
func (t *Transport) queueForDial(w *wantConn) {
go t.dialConnFor(w)
}
5.2 發起撥號dialConnFor
func (t *Transport) dialConnFor(w *wantConn) {
pc, err := t.dialConn(w.ctx, w.cm) //發起撥號,返回連線物件
delivered := w.tryDeliver(pc, err)
}
5.3 發起撥號
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) {
pconn = &persistConn{ //構建連線物件
t: t,
cacheKey: cm.key(),
reqch: make(chan requestAndChan, 1),
writech: make(chan writeRequest, 1),
closech: make(chan struct{}),
writeErrCh: make(chan error, 1),
writeLoopDone: make(chan struct{}),
}
conn, err := t.dial(ctx, "tcp", cm.addr()) //tcp連線,獲取到net.conn物件
pconn.br = bufio.NewReaderSize(pconn, t.readBufferSize())//可以從conn讀
pconn.bw = bufio.NewWriterSize(persistConnWriter{pconn}, t.writeBufferSize())//寫到conn
go pconn.readLoop()//開啟讀協程
go pconn.writeLoop()//開啟寫協程
return pconn, nil
}
5.4讀協程,雖然是for迴圈,但是一次性就把請求的response讀完了,如果沒有關閉,就會造成協程洩露了
func (pc *persistConn) readLoop() {
alive := true
for alive {
rc := <-pc.reqch //讀取request,寫入的地方在步驟6
resp, err = pc.readResponse(rc, trace) //返回response
//response的body是否可寫,伺服器code101才可寫,所以正常這個是false
bodyWritable := resp.bodyIsWritable()
//response.Close設定迴圈結束,退出協程
if resp.Close || rc.req.Close || resp.StatusCode <= 199 || bodyWritable { alive = false
}
//把response寫入通道,在步驟6會讀取這個通道
select {
case rc.ch <- responseAndError{res: resp}:
case <-rc.callerGone:
return
}
//迴圈結束的一些情況
select {
case bodyEOF := <-waitForBodyRead: //讀完body也會自動結束
case <-rc.req.Cancel:
case <-rc.req.Context().Done():
case <-pc.closech:
alive = false
pc.t.CancelRequest(rc.req)
}
}
5.4.1 pc.readResponse 獲取response
func (pc *persistConn) readResponse(rc requestAndChan, trace *httptrace.ClientTrace) (resp *Response, err error) {
for{
resp, err = ReadResponse(pc.br, rc.req) //獲取response
}
}
5.4.2 ReadResponse讀取response
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) {
tp := textproto.NewReader(r) //可以處理HTTP, NNTP, SMTP協議的內容,方便讀取
resp := &Response{
Request: req,
}
line, err := tp.ReadLine()//讀取第一行,獲取協議,狀態碼
resp.Proto = line[:i]
resp.Status = strings.TrimLeft(line[i+1:], " ")
mimeHeader, err := tp.ReadMIMEHeader()//讀取header頭
resp.Header = Header(mimeHeader)
}
5.5 寫協程
func (pc *persistConn) writeLoop() {
for {
select {
case wr := <-pc.writech:
startBytesWritten := pc.nwrite
err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh))
}
}
6.使用連線物件*persistConn獲取response
func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
var continueCh chan struct{}
resc := make(chan responseAndError) //response通道
pc.writech <- writeRequest{req, writeErrCh, continueCh}//written by roundTrip; read by writeLoop
pc.reqch <- requestAndChan{ //written by roundTrip; read by readLoop
req: req.Request,
ch: resc,
addedGzip: requestedGzip,
continueCh: continueCh,
callerGone: gone,
}
for { //監聽這些通道
testHookWaitResLoop()
select {
case err := <-writeErrCh:
case <-pc.closech:
case re := <-resc: //監聽 response通道,返回response
return re.res, nil
}
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結