NOTE

http

1. 是什么 可以简单的使用http.Get、http.Post、http.PostForm,底层调用的都是 client.Do client是线程安全的 constant预设了状态码和method 使用NewRequest构造Request,这个对象有各种方法可以设置header、param、request body等;使用Client发起请求;返回的是Response,Response有各种属性 2. 使用 2.1. http bu

Go约 1 分钟读完historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 是什么

  • 可以简单的使用http.Get、http.Post、http.PostForm,底层调用的都是 client.Do
  • client是线程安全的
  • constant预设了状态码和method
  • 使用NewRequest构造Request,这个对象有各种方法可以设置header、param、request body等;使用Client发起请求;返回的是Response,Response有各种属性

2. 使用

2.1. http builder

package builder

import (
	"bytes"
	"context"
	"net/http"
	"time"
)

const DefaultTimeout = 5

type HttpBuilder struct {
	method      string
	url         string
	headers     map[string]string
	queryParams map[string]string
	body        string
	timeout     int
}

func NewHttpBuilder() *HttpBuilder {
	return &HttpBuilder{
		headers:     make(map[string]string),
		queryParams: make(map[string]string),
	}
}

func (h *HttpBuilder) Method(method string) *HttpBuilder {
	h.method = method
	return h
}

func (h *HttpBuilder) Url(url string) *HttpBuilder {
	h.url = url
	return h
}

func (h *HttpBuilder) QueryParam(key string, value string) *HttpBuilder {
	h.queryParams[key] = value
	return h
}

func (h *HttpBuilder) Body(body string) *HttpBuilder {
	h.body = body
	return h
}

func (h *HttpBuilder) Timeout(timeout int) *HttpBuilder {
	h.timeout = timeout
	return h
}

//构造http client并且请求目标地址
//error不为nil时返回的resposne、CancelFunc在使用完毕后记得用defer释放资源
func (h *HttpBuilder) DoRequest() (*http.Response, context.CancelFunc, error) {
	req, err := http.NewRequest(h.method, h.url, bytes.NewBuffer([]byte(h.body)))
	if err != nil {
		return nil, nil, err
	}

	//组装header
	for k, v := range h.headers {
		req.Header.Set(k, v)
	}

	//组装query params
	query := req.URL.Query()
	for k, v := range h.queryParams {
		query.Add(k, v)
	}
	req.URL.RawQuery = query.Encode()

	//设置timeout
	timeout := DefaultTimeout
	if h.timeout > 0 {
		timeout = h.timeout
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
	req = req.WithContext(ctx)

	//构造client发起请求
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		defer cancel()
		return nil, nil, err
	}

	return resp, cancel, nil
}
  • 测试
func main() {
	resp, cancel, err := builder.NewHttpBuilder().
		Method(http.MethodGet).
		Url("https://www.baidu.com/s").
		QueryParam("wd", "test").
		DoRequest()
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	defer cancel()

	fmt.Println("response Status:", resp.Status)
	fmt.Println("response Headers:", resp.Header)
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	ioutil.WriteFile("test.html", body, 0644)
}

3. HTTP原理分析

http.Client包含了Transport,结构如下:

type Transport struct {

默认构造的&http.Client{}使用的Transport是DefaultTransport,

var DefaultTransport RoundTripper = &Transport{
	Proxy: ProxyFromEnvironment,
	DialContext: (&net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}).DialContext,
	ForceAttemptHTTP2:     true,
	MaxIdleConns:          100,
	IdleConnTimeout:       90 * time.Second,
	TLSHandshakeTimeout:   10 * time.Second,
	ExpectContinueTimeout: 1 * time.Second,
}

DisableKeepAlives没有设置,默认为false,表示开启了HTTP KeepAlive。即开启了HTTP长连接 MaxIdleConns为100表示连接池中空闲连接数目最多100个;MaxIdleConnsPerHost为0则默认使用DefaultMaxIdleConnsPerHost=2,表示连接池中对每个Host(IP:Port)的空闲连接数为2。即开启了HTTP连接池 MaxConnsPerHost为0表示连接数无限制。即发起新的HTTP Request如果没有空闲连接,那么创建新的连接,高并发下会出现TIME_WAIT 像trpc的http client使用的transport是这样的

var StdHTTPTransport = &stdhttp.Transport{
	Proxy: stdhttp.ProxyFromEnvironment,
	DialContext: (&net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}).DialContext,
	ForceAttemptHTTP2:     true,
	MaxIdleConns:          200,
	IdleConnTimeout:       90 * time.Second,
	TLSHandshakeTimeout:   10 * time.Second,
	MaxIdleConnsPerHost:   100,
	MaxConnsPerHost:       200,
	ExpectContinueTimeout: time.Second,
}

HTTP连接池和HTTP长连接是不同的概念 HTTP长链接指的是HTTP Request和HTTP SubRequest复用同一个TCP连接 HTTP连接池指的是把HTTP连接放入连接池中复用,这个连接只能是长连接,原因在于短连接用完就关闭了底层的TCP Connection,然后放进池子,再从池子中取的就是关闭了的Connection,根本用不了。所以只能把HTTP长连接放入连接池中复用

4. 参考

讨论

使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看