go标准库---net/http客户端

       go的net/http标准库提供了一套完整的http请求和响应服务

       服务端部分:go标准库---net/http服务端

       今天从客户端的角度来学习分析下,受限介绍Client相关的数据结构。

1、数据结构

        (1)对应服务端,客户端也封装了一个对象,相较Server,Client的成员少一些,其中最主要的就是Transport,负责客户端请求发起到返回响应的实现。

type Client struct {
	Transport RoundTripper  //http请求的具体实现

	Jar CookieJar         // cookie

	Timeout time.Duration   //超时
}

       (2) Transport是一个RoundTripper的接口类型,该接口类型实现一个RoundTrip方法,该方法负责实现传入请求,返回请求对应的响应,是客户端请求的核心

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

      (3) Request是请求的结构体,包含了请求方法、请求体、请求地址,协议等信息

type Request struct {
	Method string   //请求方法

	URL *url.URL  //请求的地址信息

	Header Header   //请求头

    Proto string // 协议

	Body io.ReadCloser //请求体

	Host string  //服务器主机

	Response *Response

    // ...

}

        (4)Response响应体,包含请求状态、协议、响应头、响应体、请求体等

type Response struct {
	StatusCode int    // 请求状态

	Proto      string // 协议

	Header Header  //相应头
 
	Body io.ReadCloser // 响应体

	Request *Request // 对应的请求体

    // ...
}

2、流程分析

        以Post请求为例,按照如下的请求函数进行梳理,如果直接调用标准库的请求函数,将使用默认的客户端实例DefaultClient,入参有路径和文本类型一级请求体参数

var DefaultClient = &Client{}

func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
	return DefaultClient.Post(url, contentType, body)
}

        在Client.Post中,首先根据传入的参数构建一个Request结构体,在请求头中传入文本类型,最后执行Client.Do函数

func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
	req, err := NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", contentType)
	return c.Do(req)
}

        首先介绍下NewRequest这个函数,主要是构造Request实例,其中调用的是NewRequestWithContext这个函数,先检查请求的方法,如果为空默认是Get方法,接着检验方法的合法性,并格式化处理地址,最终返回一个Request实例

func NewRequest(method, url string, body io.Reader) (*Request, error) {
	return NewRequestWithContext(context.Background(), method, url, body)
}


func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
	if method == "" {
		method = "GET"
	}
	if !validMethod(method) {
		return nil, fmt.Errorf("net/http: invalid method %q", method)
	}
    // ...
	u, err := urlpkg.Parse(url)
    // ...
	req := &Request{
		ctx:        ctx,
		Method:     method,
		URL:        u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(Header),
		Body:       rc,
		Host:       u.Host,
	}
    // ...
	return req, nil
}

        返回Request实例后作为参数依次执行Client.Do、Client.do,通过Client.send发送请求并处理cookie,send方法中传入c.transport()。返回transport实例

func (c *Client) Do(req *Request) (*Response, error) {
	return c.do(req)
}

func (c *Client) do(req *Request) (retres *Response, reterr error) {
    // ...

	var (
		deadline      = c.deadline()
		reqs          []*Request
		resp          *Response
		copyHeaders   = c.makeHeadersCopier(req)
	)
    // ...
	for {
        // ...
		if resp, didTimeout, err = c.send(req, deadline);
        // ...
	}
}

func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
	if c.Jar != nil {
		for _, cookie := range c.Jar.Cookies(req.URL) {
			req.AddCookie(cookie)
		}
	}
	resp, didTimeout, err = send(req, c.transport(), deadline)
	if err != nil {
		return nil, didTimeout, err
	}
	if c.Jar != nil {
		if rc := resp.Cookies(); len(rc) > 0 {
			c.Jar.SetCookies(req.URL, rc)
		}
	}
	return resp, nil, nil
}

func (c *Client) transport() RoundTripper {
	if c.Transport != nil {
		return c.Transport
	}
	return DefaultTransport
}

        在send中,通过RoundTrip实现请求连接和交互,每次请求都会创建一个transportRequest请求,通过Transport.getConn获取一个请求连接

func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
    // ...
	resp, err = rt.RoundTrip(req)
    // ...
	return resp, nil, nil
}

func (t *Transport) roundTrip(req *Request) (*Response, error) {
    // ...
	for {
        // ...
		treq := &transportRequest{Request: req, trace: trace, cancelKey: cancelKey}
		cm, err := t.connectMethodForRequest(treq)

		pconn, err := t.getConn(treq, cm)

		var resp *Response
		if pconn.alt != nil {
			resp, err = pconn.alt.RoundTrip(req)
		} else {
			resp, err = pconn.roundTrip(treq)
		}
        // ...
	}
}

        getConn中,辉县创建一个wantConn对象,通过queueForIdleConn处理限制连接队列并返回一个空闲可用的连接*persistConn,如果没有找到,调用queueForDial会创建新的连接,如果连接准备好了就返回

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,
	}
	defer func() {
		if err != nil {
			w.cancel(t, err)
		}
	}()


	if delivered := t.queueForIdleConn(w); delivered {
		pc := w.pc
		if pc.alt == nil && trace != nil && trace.GotConn != nil {
			trace.GotConn(pc.gotIdleConnTrace(pc.idleAt))
		}
		t.setReqCanceler(treq.cancelKey, func(error) {})
		return pc, nil
	}

	t.queueForDial(w)

	select {
	case <-w.ready:
        // ...
		return w.pc, w.err
	case <-req.Cancel:
        // ...
	case <-req.Context().Done():
        // ...
	case err := <-cancelc:
        // ...
	}
}

接下来介绍queueForIdleConn和queueForDial,从现有的连接队列中获取空闲连接,如果有的话,尝试是否能够绑定到wantConn上,并关闭wantConn的ready通道,唤醒其他地方的ready等待

func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
    // ...

	if list, ok := t.idleConn[w.key]; ok {

		for len(list) > 0 && !stop {
			pconn := list[len(list)-1]

			delivered = w.tryDeliver(pconn, nil)
			if delivered {
                    // ...
				}
			}
			stop = true
		}

		if stop {
			return delivered
		}
	}
    // ...
}

func (w *wantConn) tryDeliver(pc *persistConn, err error) bool {
    // ...
	w.ctx = nil
	w.pc = pc
	w.err = err
	close(w.ready)
	return true
}

queueForDial中会创建新的连接,异步执行dialConnFor,dialConn和tryDeliver配合绑定

func (t *Transport) queueForDial(w *wantConn) {
	if t.MaxConnsPerHost <= 0 {
		go t.dialConnFor(w)
		return
	}
	if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {
		go t.dialConnFor(w)
        return
	}
}

func (t *Transport) dialConnFor(w *wantConn) {
    // ...
	pc, err := t.dialConn(ctx, w.cm)
	delivered := w.tryDeliver(pc, err)
    // ...
}

其中dailConn首先创建一个persistConn实例,dial创建连接,赋值给pconn并返回,同时一部读写请求和响应

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())

	pconn.conn = conn

	go pconn.readLoop()
	go pconn.writeLoop()
	return pconn, nil
}

readLoop负责读取响应

func (pc *persistConn) readLoop() { 
    // ...
    alive := true
    for alive {
        // ...
        rc := <-pc.reqch
        // ...
        var resp *Response
        // ...
        resp, err = pc.readResponse(rc, trace)
        // ...      
    }
}

writeLoop负责接收请求的信息,将数据传递到服务端

func (pc *persistConn) writeLoop() {    
    for {
        select {
        case wr := <-pc.writech:
            // ...
            err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh))
            // ...       
    }
}

 roundTrip与上面的读写异步配合,将请求写入writech,在writeLoop中接受并传递给服务端,同时也通过reqch在readLoop中接受服务端的响应数据。

func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
    // ...
	writeErrCh := make(chan error, 1)
	pc.writech <- writeRequest{req, writeErrCh, continueCh}
    
	resc := make(chan responseAndError)
	pc.reqch <- requestAndChan{
		req:        req.Request,
		cancelKey:  req.cancelKey,
		ch:         resc,
		addedGzip:  requestedGzip,
		continueCh: continueCh,
		callerGone: gone,
	}
    // ...
    for {       
        select {
        // ...
        case re := <-resc:
            // ...
            return re.res, nil
        // ...
        }
    }
}

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值