前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 源码分析(20)http/client

golang 源码分析(20)http/client

作者头像
golangLeetcode
发布2022-08-02 16:37:33
5810
发布2022-08-02 16:37:33
举报
文章被收录于专栏:golang算法架构leetcode技术php

一、net/http的httpclient发起http请求

方法

get请求

func httpGet() {

resp, err := http.Get("http://www.01happy.com/demo/accept.php?id=1")

if err != nil {

// handle error

}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {

// handle error

}

fmt.Println(string(body))

}

post请求

方法一:http.Post方法

func httpPost() {

resp, err := http.Post("http://www.01happy.com/demo/accept.php",

"application/x-www-form-urlencoded",

strings.NewReader("name=cjb"))

if err != nil {

fmt.Println(err)

}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {

// handle error

}

fmt.Println(string(body))

}

使用这个方法,第二个参数(contentType)必须设置为"application/x-www-form-urlencoded",否则post参数无法传递

方法二:http.PostForm方法

func httpPostForm() {

resp, err := http.PostForm("http://www.01happy.com/demo/accept.php",

url.Values{"key": {"Value"}, "id": {"123"}})

if err != nil {

// handle error

}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {

// handle error

}

fmt.Println(string(body))

}

复杂的请求:若需要设置请求头参数,cookie之类的数据,就使用http.Do方法

func httpDo() {

client := &http.Client{}

req, err := http.NewRequest("POST", "http://www.01happy.com/demo/accept.php", strings.NewReader("name=cjb"))

if err != nil {

// handle error

}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

req.Header.Set("Cookie", "name=anny")

resp, err := client.Do(req)

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {

// handle error

}

fmt.Println(string(body))

}

Head请求:Head方法,只返回页面的首部

注意:

要调用resp.Body.Close()关闭response.body。如果resp.body没有关闭,则Client底层RoundTripper将无法重用存在的TCP连接去服务接下来的请求

第二步:Do/Get/Post方法的实现(以Do为例)

处理请求,添加referer、method字段

调用send方法,向request添加cookie

检查http头是否合法,若合法调用transport的RoundTrip方法

第三步:精髓:调用transport的RoundTrip方法

++transport.go:++

struct:

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)

}

两个map:

idleConn:保存从 connectMethodKey (代表着不同的协议 不同的host,也就是不同的请求)到 persistConn 的映射

idleConnCh:用来在并发http请求的时候在多个 goroutine 里面相互发送持久连接,也就是说, 这些持久连接是可以重复利用的, 你的http请求用某个persistConn用完了,通过这个channel发送给其他http请求使用这个persistConn

==连接池:==

RoundTrip方法:

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)

}

省略前面对参数的检查部分,主要有两步:

第一步:获取TCP长连接pconn, err := t.getConn(req, cm)

func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error) {

...

type dialRes struct {

pc *persistConn

err error

}

dialc := make(chan dialRes)

//定义了一个发送 persistConn的channel

...

// 启动了一个goroutine, 这个goroutine 获取里面调用dialConn搞到

// persistConn, 然后发送到上面建立的channel dialc里面,

go func() {

pc, err := t.dialConn(cm)

dialc <- dialRes{pc, err}

}()

idleConnCh := t.getIdleConnCh(cm)

select {

case v := <-dialc:

// dialc 我们的 dial 方法先搞到通过 dialc通道发过来了

return v.pc, v.err

case pc := <-idleConnCh:

// 这里代表其他的http请求用完了归还的persistConn通过idleConnCh这个

// channel发送来的

handlePendingDial()

return pc, nil

case <-req.Cancel:

handlePendingDial()

return nil, errors.New("net/http: request canceled while waiting for connection")

case <-cancelc:

handlePendingDial()

return nil, errors.New("net/http: request canceled while waiting for connection")

}

}

定义一个发送 persistConn的channel dialc

启动了一个goroutine, 这个goroutine 获取里面调用dialConn搞到persistConn, 然后发送到dialc里面

主协程goroutine在 select里面监听多个channel,看看哪个通道里面先发过来 persistConn,就用哪个,然后return

第二步:调用这个持久连接persistConn 这个struct的roundTrip方法

三个goroutine通过channel互相协作的过程,

1. 主goroutine ->requestAndChan -> 读循环goroutine:读循环goroutine 通过channel requestAndChan 接受主goroutine发送的request(rc := <-pc.reqch), 并从tcp输出流中读取response, 然后反序列化到结构体中, 最后通过channel 返给主goroutine (rc.ch <- responseAndError{resp, err} )

2. 主goroutine ->writeRequest-> 写循环goroutine:select channel中主gouroutine的request,然后写入tcp输入流,如果出错了,channel 通知调用者

3. 主goroutine 通过select 监听各个channel上的数据, 比如请求取消, timeout,长连接挂了,写流出错,读流出错, 都是其他goroutine 发送过来的, 跟中断一样,然后相应处理

二、使用net/http的参数设置:

粗粒度:

使用http.Client的 Timeout字段。

它的时间计算包括从连接(Dial)到读完response body。

细粒度:细粒度只对于单次连接起作用

net.Dialer.Timeout 限制建立TCP连接的时间

http.Transport.TLSHandshakeTimeout 限制 TLS握手的时间

http.Transport.ResponseHeaderTimeout 限制读取response header的时间

http.Transport.ExpectContinueTimeout 限制client在发送包含 Expect: 100-continue的header到收到继续发送body的response之间的时间等待。

http.Transport.IdleConnTimeout,控制连接池中一个连接可以idle多长时间。

http.Client的默认超时时限是0,不超时,可以设置。

实际上是一个连接池,全局复用。初始化Transport,然后复用

参数:

DisableKeepAlives默认为false(长连接)

MaxIdleConns连接池对所有host的最大连接数量,默认无穷大

MaxIdleConnsPerHHost连接池对每个host的最大连接数量。(一个host的情况下最好与上面保持一致)

IdleConnTimeout空闲时间设置

函数:

DialContext用于创建(http)连接。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档