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.
175 lines
5.4 KiB
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
|
|
}
|