Go Standard library Anatomy 1 (transport HTTP request bearer)

Source: Internet
Author: User
Tags loop case
This is a creation in Article, where the information may have evolved or changed.

Send HTTP requests using the Golang Net/http Library, and finally the roundtrip method that calls transport

type RoundTripper interface {    RoundTrip(*Request) (*Response, error)}

RoundTrip executes a single HTTP transaction, returning the Response for the request req.(roundtrip represents an HTTP transaction that returns a response to a request)
Plainly, you give it a request, it gives you a response

Next we look at his implementation, corresponding to the source file net/http/transport.go , I feel here is the essence of the HTTP package inside, go inside a struct is like a class, transport this kind of long

type Transport struct {    idleMu     sync.Mutex    wantIdle   bool // user has requested to close all idle conns    idleConn   map[connectMethodKey][]*persistConn    idleConnCh map[connectMethodKey]chan *persistConn    reqMu       sync.Mutex    reqCanceler map[*Request]func()    altMu    sync.RWMutex    altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper    //Dial获取一个tcp 连接,也就是net.Conn结构,你就记住可以往里面写request    //然后从里面搞到response就行了    Dial func(network, addr string) (net.Conn, error)}

Space limit, HTTPS and agent-related I ignored, two for, is to map idleConn idleConnCh idleConn save from Connectmethodkey (representing different protocols different host, that is, different requests) to persistconn mapping, c5/> is used to send persistent connections between multiple Goroutine in concurrent HTTP requests, that is, these persistent connections can be reused, and your HTTP request is used by some to persistConn channel send to other HTTP requests using this persistConn And then we find transport the RoundTrip method

func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {    ...    pconn, err := t.getConn(req, cm)    if err != nil {        t.setReqCanceler(req, nil)        req.closeBody()        return nil, err    }    return pconn.roundTrip(treq)}

Before facing the input error handling part we ignore, in fact, 2 steps, first get a TCP long connection, so-called TCP long connection is three times the handshake to establish a connection, close but always keep the reuse (save the environment) and then call this persistent connection Persistconn The roundtrip method of this struct

We follow the first step.

Func (t *transport) getconn (req *request, CM Connectmethod) (*persistconn, error) {if PC: = t.getidleconn (cm); pc! = N  Il {//Set request Canceler to some non-nil function so we//can detect whether it is cleared between now  And when//we enter roundtrip T.setreqcanceler (req, func () {}) return PC, nil} type Dialres    struct {PC *persistconn err error} Dialc: = Make (chan dialres)//define a channel to send Persistconn Prependingdial: = prependingdial postpendingdial: = postpendingdial handlependingdial: = Func () {if prePend                Ingdial! = nil {prependingdial ()} go func () {if V: = <-dialc; V.err = = Nil {            T.putidleconn (V.PC)} if postpendingdial! = nil {postpendingdial () }} ()} CANCELC: = Make (chan struct{}) T.setreqcanceler (req, func () {Close (CANCELC)})//initiates a Goroutine, this goroutine wonTake the inside call Dialconn get//Persistconn, then send to the above established channel Dialc inside, go func () {pc, err: = T.dialconn (cm) Dialc <-dialres{pc, Err}} () Idleconnch: = t.getidleconnch (cm) Select {Case V: = <-dialc://di ALC Our dial method first got through the Dialc channel sent over the return v.pc, V.err case PC: = <-idleconnch://Here on behalf of the other HTTP request used up the returned per Sistconn is sent by idleconnch this//channel to the Handlependingdial () return PC, nil case <-req. Cancel:handlependingdial () return nil, errors. New ("Net/http:request canceled while waiting for connection") case <-cancelc:handlependingdial () ret Urn nil, errors. New ("Net/http:request canceled while waiting for Connection")}}

The code in this is very fastidious, the above code inside I also commented, defined a Send persistConn channel dialc , started a goroutine , this goroutine gets inside the call to get dialConn persistConn , and then sent to the dialc inside, the main process goroutine in selectlisten to a plurality channel of inside, see which channel inside first send over persistConn , use which, then return .

Note here is that the idleConnCh channel is sent to the other HTTP request is used up persistConn , if from the channel inside, dialc This channel is also waiting for the hair, can not be wasted, through handlePendingDial this method to the dialc channel inside the persistConnalso sent to idleConnCh , waiting for follow-up to other HTTP requests to use.

Also, the reader can flip the code, each new persistconn to the TCP connection in the input stream, and the output stream with BR ( br *bufio.Reader ), and BW ( bw *bufio.Writer ) wrapped a bit, to bw write to the TCP input stream, read the output stream is also read through the BR, and started the read loop and write loop

pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF})pconn.bw = bufio.NewWriter(pconn.conn)go pconn.readLoop()go pconn.writeLoop()

We follow the second step pconn.roundTrip to call this persistent connection to persistconn the struct's roundTrip method.
Look at persistConn this struct first.

Type persistconn struct {t *transport cacheKey connectmethodkey Conn Net. Conn tlsstate *tls. ConnectionState BR *bufio. Reader//From the TCP output stream read saweof bool//Whether we ' ve seen EOF from Conn; Owned by Readloop bw *bufio.                                      Writer//write to TCP input stream Reqch Chan Requestandchan//main goroutine to Channnel inside write, read loop from                                                                       Channnel inside Accept Writech Chan writerequest//main goroutine go to Channnel inside write Write loops receive Closech Chan struct{} from the channel inside the channel Writeerrch cha that notifies off TCP connections N Error lk Sync.    Mutex//Guards following fields numexpectedresponses int closed BOOL//Whether Conn have been closed broken BOOL//An error had happened on this connection;    Marked broken so it's not reused.   canceled BOOL//Whether this conn is broken due a cancelrequest Mutateheaderfunc is a optional func to modify extra//headers on each outbound request before it ' s written. (The//original Request given to roundtrip are not modified) Mutateheaderfunc func (Header)}

Inside is a variety of channel, with a superb, you have to understand, I paint here

There are three goroutine, respectively, with three circles, and the channel is represented by an arrow

There are two channel writeRequest andrequestAndChan

type writeRequest struct {    req *transportRequest    ch  chan<- error}

The Lord Goroutine to writerequest inside, writing loops from the writerequest inside to accept

type responseAndError struct {    res *Response    err error}type requestAndChan struct {    req *Request    ch  chan responseAndError    addedGzip bool}

The main goroutine is written inside the Requestandchan, and the reading cycle is received from the Requestandchan.

Note that the channel here is a two-way channel, that is, the channel's struct has an Chan type of field, such as the reqch chan requestAndChan Requestandchan inside ch chan responseAndError .

This is a very bull fork, the main goroutine sent through the reqch Requestandchan to read the loop, and then read the loop to get response after Requestandchan Inside the channel Responseanderror response back to the main goroutine, so I drew a two-way arrow.

Let's look at the code, and I understand that it's actually the process of three goroutine working through the channel.

Main loop:

Func (PC *persistconn) roundtrip (req *transportrequest) (resp *response, err error) {... Ignore/Write the request concurrently with waiting for a response,//In case the server decides to reply before read    ing our full/request body. Writeerrch: = Make (chan error, 1) Pc.writech <-writerequest{req, writeerrch}//Send request to write loop Resc: = Made (ch An responseanderror, 1) pc.reqch <-requestandchan{req. Request, Resc, requestedgzip}//Sent to read cycle Var re responseanderror var Respheadertimer <-chan time. Time Cancelchan: = req. Request.CancelWaitResponse:for {Select {case err: = <-writeerrch:if Isnetwriteerror (err {//write loop report error through this channel select {Case re = <-resc:pc. Close () break Waitresponse case <-time.                After (Time.millisecond)://Fall through. }} If Err ! = Nil {re = Responseanderror{nil, err} pc.close () Break Waitresponse } if D: = Pc.t.responseheadertimeout; D > 0 {timer: = time. Newtimer (d) Defer timer. Stop ()//prevent leaks Respheadertimer = timer. C} case <-pc.closech://If the long connection is hung up, here the channel has data, enter in this case to process SE                    lect {case re = <-resc:if fn: = testhookpersistconnclosedgotres; fn! = nil { fn ()} default:re = responseanderror{err:errclosed} if pc.i Scanceled () {re = responseanderror{err:errrequestcanceled}}} b Reak waitresponse Case <-respheadertimer:pc.close () Re = Responseanderror{err:errtimeout } Break Waitresponse//If timeout, the channel here has data,Break off for loop case re = <-resc:break waitresponse//Get response to read loop, break off for loop CA Se <-cancelchan:pc.t.cancelrequest (req. Request) Cancelchan = nil}} if Re.err! = nil {Pc.t.setreqcanceler (req. Request, Nil)} return re.res, Re.err}

This code is basically doing three things.

    • Master Goroutine->requestandchan-read cycle goroutine

    • Main Goroutine->writerequest-> Write loop goroutine

    • The main goroutine through select to listen to the data on each channel, such as request cancellation, timeout, long connection hung, write out the wrong, read out the wrong, are the other goroutine sent over, and the same as the interruption, and then the corresponding treatment, the above also mentioned, Some of the channel is the channel that the main goroutine through the channel sends to other Goroutine's struct, for example case err := <-writeErrCh:case re = <-resc:

Read Loop code:

 func (PC *persistconn) Readloop () {... Ignore Alive: = True for alive {... Ignore RC: = <-pc.reqch var resp *response if Err = = Nil {resp, err = Readresponse (pc.br, Rc.req) If Err = = Nil && resp. StatusCode = = {//100 Continue The initial request has been accepted, the customer shall continue to send the request for its//remainder RESP, err = Readresponse (pc.br, rc.req)//Read the data in pc.br (TCP output stream), here the code in response//parse StatusCode, header field, turn into Standard in-memory response type//HTTP in TCP data flow, head and body separated by/r/n/r/n, each header//field separated by/r/n} } if resp! = nil {resp. TLS = pc.tlsstate} ... Ignore//Above some logical behavior of some HTTP protocols, rc.ch <-RESPONSEANDERROR{RESP, err}//Return the read response to Main Goroutine. Ignore//Ignore section, handle cancel req Interrupt, send idleconnch to return PC (persistent connection) to persistent connection pool (map) pc.close ()} 

Irrelevant code ignores that this code is mostly doing a thing

The Read loop Goroutine accepts the request () sent by the main Goroutine via the channel Requestandchan rc := <-pc.reqch and reads the response from the TCP output stream, then deserializes into the struct, and finally passes the channel Return to main goroutine ( rc.ch <- responseAndError{resp, err} )

func (pc *persistConn) writeLoop() {    for {        select {        case wr := <-pc.writech:   //接受主goroutine的 request            if pc.isBroken() {                wr.ch <- errors.New("http: can't write HTTP request on broken connection")                continue            }            err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra)   //写入tcp输入流            if err == nil {                err = pc.bw.Flush()            }            if err != nil {                pc.markBroken()                wr.req.Request.closeBody()            }            pc.writeErrCh <- err             wr.ch <- err         //  出错的时候返给主goroutineto         case <-pc.closech:            return        }    }}

The write loop is much simpler, the request of the master Gouroutine in the Select channel is then written to the TCP input stream, and the channel notifies the caller if something goes wrong.

Overall look down, the process is very simple, but there is a lot of code to learn, such as high concurrent request how to reuse TCP connection, here is the practice of connection pooling, if using multiple goroutine to cooperate with each other to complete an HTTP request, how to notify the caller interrupt error when an error occurs, Code style also has a lot to learn from the place.

I'm going to write a series that thoroughly analyzes the highlights of the Go standard library and shares it with everyone.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.