A estrutura de dados de http.Client
Nós já introduziram, http.Get()
, http.Post()
, http.PostForm()
e http.Head()
métodos são realmente http.DefaultClient
realizado com base da chamada.
http.DefaultClient
É a implementação padrão do cliente HTTP fornecido pelo pacote net / http :
// DefaultClient is the default Client and is used by Get, Head, and Post.
var DefaultClient = &Client{}
Na verdade, também podemos implementá-lo com base em http.Client
um cliente HTTP personalizado. Antes disso, vamos dar uma olhada na Client
estrutura de dados do tipo:
type Client struct {
// Transport 用于指定单次 HTTP 请求响应的完整流程
// 默认值是 DefaultTransport
Transport RoundTripper
// CheckRedirect 用于定义重定向处理策略
// 它是一个函数类型,接收 req 和 via 两个参数,分别表示即将发起的请求和已经发起的所有请求,最早的已发起请求在最前面
// 如果不为空,客户端将在跟踪 HTTP 重定向前调用该函数
// 如果返回错误,客户端将直接返回错误,不会再发起该请求
// 如果为空,Client 将采用一种确认策略,会在 10 个连续请求后终止
CheckRedirect func(req *Request, via []*Request) error
// Jar 用于指定请求和响应头中的 Cookie
// 如果该字段为空,则只有在请求中显式设置的 Cookie 才会被发送
Jar CookieJar
// 指定单次 HTTP 请求响应事务的超时时间
// 未设置的话使用 Transport 的默认设置,为零的话表示不设置超时时间
Timeout time.Duration
}
O Transport
campo deve implementar a http.RoundTripper
interface, Transport
especificando o fluxo completo de uma transação HTTP (resposta da solicitação). Se não for especificado Transport
, http.DefaultTransport
a implementação padrão será usada por padrão. Por exemplo http.DefaultClient
, este é o caso, e discutiremos http.DefaultTransport
a implementação subjacente em profundidade mais tarde .
CheckRedirect
As funções são usadas para definir estratégias para lidar com o redirecionamento. Ao enviar uma solicitação HTTP usando o método Get()
ou fornecido pelo cliente HTTP padrão Head()
, se o código de status de resposta for 30x
(como 301
, 302
etc.), o cliente HTTP chamará esta CheckRedirect
função antes de seguir a regra de redirecionamento .
Jar
Pode ser usado para definir cookies no cliente HTTP.O Jar
tipo deve implementar uma http.CookieJar
interface, que tem predefinidos SetCookies()
e Cookies()
dois métodos. Se não houver configuração no cliente HTTP Jar
, o cookie será ignorado e não será enviado ao cliente. Na verdade, geralmente usamos http.SetCookie()
métodos para definir cookies.
Timeout
O campo é usado para especificar Transport
o período de tempo limite, se não for especificado, Transport
uma configuração personalizada é usada .
A implementação subjacente de http.Transport
A seguir, usaremos http.DefaultTransport
a implementação para focar , ela será usada quando http.Transport
não houver uma configuração explícita do Transport
campo DefaultTransport
:
func (c *Client) transport() RoundTripper {
if c.Transport != nil {
return c.Transport
}
return DefaultTransport
}
DefaultTransport
Sim Transport
, a implementação padrão, o código de inicialização correspondente é o seguinte:
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
Aqui, apenas Transport
parte dos atributos são definidos e Transport
a estrutura de dados completa do tipo é a seguinte:
type Transport struct {
...
// 定义 HTTP 代理策略
Proxy func(*Request) (*url.URL, error)
// 用于指定创建未加密 TCP 连接的上下文参数(通过 net.Dial()创建连接时使用)
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// 已废弃,使用 DialContext 替代
Dial func(network, addr string) (net.Conn, error)
// 创建加密 TCP 连接
DialTLS func(network, addr string) (net.Conn, error)
// 指定 tls.Client 所使用的 TLS 配置
TLSClientConfig *tls.Config
// TLS 握手超时时间
TLSHandshakeTimeout time.Duration
// 是否禁用 HTTP 长连接
DisableKeepAlives bool
// 是否对 HTTP 报文进行压缩传输(gzip)
DisableCompression bool
// 最大空闲连接数(支持长连接时有效)
MaxIdleConns int
// 单个服务(域名)最大空闲连接数
MaxIdleConnsPerHost int
// 单个服务(域名)最大连接数
MaxConnsPerHost int
// 空闲连接超时时间
IdleConnTimeout time.Duration
// 从客户端把请求完全提交给服务器到从服务器接收到响应报文头的超时时间
ResponseHeaderTimeout time.Duration
// 包含 "Expect: 100-continue" 请求头的情况下从客户端把请求完全提交给服务器到从服务器接收到响应报文头的超时时间
ExpectContinueTimeout time.Duration
...
}
Transport
Observe DefaultTransport
as configurações em conjunto com a estrutura de dados :
- Ao
net.Dialer
inicializar a configuração do contexto de discagem, o período de tempo limite padrão é definido para 30 segundos; - Via
MaxIdleConns
conexão 100 para especificar o número máximo de ociosos, não explicitamente definidoMaxIdleConnsPerHost
eMaxConnsPerHost
,MaxIdleConnsPerHost
com um valor padrão,http.DefaultMaxIdleConnsPerHost
definindo o valor padrão correspondente é 2; - Ao
IdleConnTimeout
especificar o tempo máximo de conexão ociosa de 90 segundos, ou seja, quando uma conexão ociosa não é reutilizado por mais de 90 segundos, ele será destruído. As necessidades de conexão ociosaDisableKeepAlives
parafalse
estar disponível, ou seja, é válido no HTTP longa estado da conexão (HTTP / 1.1 e superior) Suporte a conexão longa, correspondendo ao cabeçalho da solicitaçãoConnection:keep-alive
); - Ao
TLSHandshakeTimeout
especificar que uma conexão TCP segura com base no protocolo TLS é estabelecida, o período de tempo limite da fase de handshake é de 10 segundos; - Ao
ExpectContinueTimeout
especificar que o cliente deseja usar uma solicitação POST para enviar um corpo de mensagem grande ao servidor, ele primeiroExpect: 100-continue
pergunta ao servidor se deseja receber o período de tempo limite correspondente a esse corpo de mensagem grande enviando um cabeçalho de solicitação incluído . configuração aqui é de 1 segundo.
Além disso, Transport
a RoundTrip
implementação do método está incluída , de forma que a RoundTripper
interface é implementada . Vejamos Transport
a RoundTrip
implementação do método.
Implementação do método Transport.RoundTrip ()
Primeiro, vamos dar uma olhada na http.RoundTripper
definição específica da interface:
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
Como você pode ver no código acima, a http.RoundTripper
interface é muito simples e define apenas um RoundTrip
método chamado . RoundTrip()
O método é usado para realizar uma transação HTTP independente, aceitar o *Request
valor da solicitação de entrada como um parâmetro e retornar o *Response
valor de resposta correspondente , bem como um error
valor.
Ao implementar um RoundTrip()
método específico , você não deve tentar analisar as informações de resposta HTTP nesta função. Se a resposta for bem-sucedida, error
o valor deve ser nil
, independentemente do código de status HTTP retornado. Se a resposta do servidor não puder ser obtida com sucesso, ela error
deve ser um valor diferente de zero. Da mesma forma, você não deve tentar RoundTrip()
lidar com detalhes no nível do protocolo, como redirecionamento, autenticação ou cookies.
Se não for necessário, o RoundTrip()
objeto de solicitação recebido ( *Request
) não deve ser reescrito no método. O conteúdo da solicitação (como URL e cabeçalho, etc.) deve ser RoundTrip()
organizado e inicializado antes de ser passado .
Qualquer RoundTrip()
tipo que implementa um método implementa uma http.RoundTripper
interface. http.Transport
É o RoundTrip()
método que implementa o método e, em seguida, implementa a interface. Na camada inferior, Go implementa uma única transação de resposta de solicitação HTTP por meio da API de busca WHATWG :
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
if useFakeNetwork() {
return t.roundTrip(req)
}
ac := js.Global().Get("AbortController")
if ac != js.Undefined() {
// Some browsers that support WASM don't necessarily support
// the AbortController. See
// https://developer.mozilla.org/en-US/docs/Web/API/AbortController#Browser_compatibility.
ac = ac.New()
}
opt := js.Global().Get("Object").New()
// See https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
// for options available.
opt.Set("method", req.Method)
opt.Set("credentials", "same-origin")
if h := req.Header.Get(jsFetchCreds); h != "" {
opt.Set("credentials", h)
req.Header.Del(jsFetchCreds)
}
if h := req.Header.Get(jsFetchMode); h != "" {
opt.Set("mode", h)
req.Header.Del(jsFetchMode)
}
if ac != js.Undefined() {
opt.Set("signal", ac.Get("signal"))
}
headers := js.Global().Get("Headers").New()
for key, values := range req.Header {
for _, value := range values {
headers.Call("append", key, value)
}
}
opt.Set("headers", headers)
if req.Body != nil {
// TODO(johanbrandhorst): Stream request body when possible.
// See https://bugs.chromium.org/p/chromium/issues/detail?id=688906 for Blink issue.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1387483 for Firefox issue.
// See https://github.com/web-platform-tests/wpt/issues/7693 for WHATWG tests issue.
// See https://developer.mozilla.org/en-US/docs/Web/API/Streams_API for more details on the Streams API
// and browser support.
body, err := ioutil.ReadAll(req.Body)
if err != nil {
req.Body.Close() // RoundTrip must always close the body, including on errors.
return nil, err
}
req.Body.Close()
a := js.TypedArrayOf(body)
defer a.Release()
opt.Set("body", a)
}
respPromise := js.Global().Call("fetch", req.URL.String(), opt)
var (
respCh = make(chan *Response, 1)
errCh = make(chan error, 1)
)
success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
result := args[0]
header := Header{}
// https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
headersIt := result.Get("headers").Call("entries")
for {
n := headersIt.Call("next")
if n.Get("done").Bool() {
break
}
pair := n.Get("value")
key, value := pair.Index(0).String(), pair.Index(1).String()
ck := CanonicalHeaderKey(key)
header[ck] = append(header[ck], value)
}
contentLength := int64(0)
if cl, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64); err == nil {
contentLength = cl
}
b := result.Get("body")
var body io.ReadCloser
// The body is undefined when the browser does not support streaming response bodies (Firefox),
// and null in certain error cases, i.e. when the request is blocked because of CORS settings.
if b != js.Undefined() && b != js.Null() {
body = &streamReader{stream: b.Call("getReader")}
} else {
// Fall back to using ArrayBuffer
// https://developer.mozilla.org/en-US/docs/Web/API/Body/arrayBuffer
body = &arrayReader{arrayPromise: result.Call("arrayBuffer")}
}
select {
case respCh <- &Response{
Status: result.Get("status").String() + " " + StatusText(result.Get("status").Int()),
StatusCode: result.Get("status").Int(),
Header: header,
ContentLength: contentLength,
Body: body,
Request: req,
}:
case <-req.Context().Done():
}
return nil
})
defer success.Release()
failure := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
err := fmt.Errorf("net/http: fetch() failed: %s", args[0].String())
select {
case errCh <- err:
case <-req.Context().Done():
}
return nil
})
defer failure.Release()
respPromise.Call("then", success, failure)
select {
case <-req.Context().Done():
if ac != js.Undefined() {
// Abort the Fetch request
ac.Call("abort")
}
return nil, req.Context().Err()
case resp := <-respCh:
return resp, nil
case err := <-errCh:
return nil, err
}
}
Como http.RoundTripper
o código que implementa a interface geralmente precisa ser executado simultaneamente em várias goroutines, devemos garantir a segurança do thread do código de implementação.
Os itens acima são os http.Client
principais componentes da implementação subjacente e suas implementações padrão. O foco está em http.Transport
. Ele define o processo completo de uma transação HTTP. Podemos personalizar Transport
a solicitação do cliente HTTP por meio da personalização. Basta entendê-la. No desenvolvimento real, geralmente só precisamos chamar alguns métodos fornecidos no tutorial anterior , a menos que precisemos fazer um desenvolvimento e customização de baixo nível, caso contrário, geralmente não os envolveremos.