You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-library/vendor/github.com/baidubce/bce-sdk-go/http/client.go

175 lines
5.4 KiB

/*
* Copyright 2017 Baidu, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
// client.go - define the execute function to send http request and get response
// Package http defines the structure of request and response which used to access the BCE services
// as well as the http constant headers and and methods. And finally implement the `Execute` funct-
// ion to do the work.
package http
import (
"net"
"net/http"
"net/url"
"sync"
"time"
)
const (
defaultMaxIdleConnsPerHost = 500
defaultResponseHeaderTimeout = 60 * time.Second
defaultDialTimeout = 30 * time.Second
defaultSmallInterval = 600 * time.Second
defaultLargeInterval = 1200 * time.Second
)
// The httpClient is the global variable to send the request and get response
// for reuse and the Client provided by the Go standard library is thread safe.
var (
httpClient *http.Client
transport *http.Transport
)
type timeoutConn struct {
conn net.Conn
smallInterval time.Duration
largeInterval time.Duration
}
func (c *timeoutConn) Read(b []byte) (n int, err error) {
c.SetReadDeadline(time.Now().Add(c.smallInterval))
n, err = c.conn.Read(b)
c.SetReadDeadline(time.Now().Add(c.largeInterval))
return n, err
}
func (c *timeoutConn) Write(b []byte) (n int, err error) {
c.SetWriteDeadline(time.Now().Add(c.smallInterval))
n, err = c.conn.Write(b)
c.SetWriteDeadline(time.Now().Add(c.largeInterval))
return n, err
}
func (c *timeoutConn) Close() error { return c.conn.Close() }
func (c *timeoutConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
func (c *timeoutConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
func (c *timeoutConn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) }
func (c *timeoutConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) }
func (c *timeoutConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) }
type ClientConfig struct {
RedirectDisabled bool
}
var customizeInit sync.Once
func InitClient(config ClientConfig) {
customizeInit.Do(func() {
httpClient = &http.Client{}
transport = &http.Transport{
MaxIdleConnsPerHost: defaultMaxIdleConnsPerHost,
ResponseHeaderTimeout: defaultResponseHeaderTimeout,
Dial: func(network, address string) (net.Conn, error) {
conn, err := net.DialTimeout(network, address, defaultDialTimeout)
if err != nil {
return nil, err
}
tc := &timeoutConn{conn, defaultSmallInterval, defaultLargeInterval}
tc.SetReadDeadline(time.Now().Add(defaultLargeInterval))
return tc, nil
},
}
httpClient.Transport = transport
if config.RedirectDisabled {
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
})
}
// Execute - do the http requset and get the response
//
// PARAMS:
// - request: the http request instance to be sent
// RETURNS:
// - response: the http response returned from the server
// - error: nil if ok otherwise the specific error
func Execute(request *Request) (*Response, error) {
// Build the request object for the current requesting
httpRequest := &http.Request{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
}
// Set the connection timeout for current request
httpClient.Timeout = time.Duration(request.Timeout()) * time.Second
// Set the request method
httpRequest.Method = request.Method()
// Set the request url
internalUrl := &url.URL{
Scheme: request.Protocol(),
Host: request.Host(),
Path: request.Uri(),
RawQuery: request.QueryString()}
httpRequest.URL = internalUrl
// Set the request headers
internalHeader := make(http.Header)
for k, v := range request.Headers() {
val := make([]string, 0, 1)
val = append(val, v)
internalHeader[k] = val
}
httpRequest.Header = internalHeader
if request.Body() != nil {
if request.Length() > 0 {
httpRequest.ContentLength = request.Length()
httpRequest.Body = request.Body()
} else if request.Length() < 0 {
// if set body and ContentLength <= 0, will be chunked
httpRequest.Body = request.Body()
} // else {} body == nil and ContentLength == 0
}
// Set the proxy setting if needed
if len(request.ProxyUrl()) != 0 {
transport.Proxy = func(_ *http.Request) (*url.URL, error) {
return url.Parse(request.ProxyUrl())
}
}
// Perform the http request and get response
// It needs to explicitly close the keep-alive connections when error occurs for the request
// that may continue sending request's data subsequently.
start := time.Now()
httpResponse, err := httpClient.Do(httpRequest)
end := time.Now()
if err != nil {
transport.CloseIdleConnections()
return nil, err
}
if httpResponse.StatusCode >= 400 &&
(httpRequest.Method == PUT || httpRequest.Method == POST) {
transport.CloseIdleConnections()
}
response := &Response{httpResponse, end.Sub(start)}
return response, nil
}