pkg/rest/client.go (193 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package rest import ( "bytes" "compress/gzip" "crypto/tls" "fmt" "io" "net/http" "net/http/httputil" "net/url" "os" "strings" "time" "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/buffer" "github.com/apache/servicecomb-service-center/pkg/util" "github.com/go-chassis/foundation/tlsutil" "context" ) var defaultURLClientOption = URLClientOption{ Compressed: true, VerifyPeer: true, SSLVersion: tls.VersionTLS12, HandshakeTimeout: 10 * time.Second, ResponseHeaderTimeout: 30 * time.Second, RequestTimeout: 60 * time.Second, ConnsPerHost: DefaultConnPoolPerHostSize, } type URLClientOption struct { SSLEnabled bool Compressed bool VerifyPeer bool CAFile string CertFile string CertKeyFile string CertKeyPWD string SSLVersion uint16 HandshakeTimeout time.Duration ResponseHeaderTimeout time.Duration RequestTimeout time.Duration ConnsPerHost int } type gzipBodyReader struct { *gzip.Reader Body io.ReadCloser } func (w *gzipBodyReader) Close() error { w.Reader.Close() return w.Body.Close() } func NewGZipBodyReader(body io.ReadCloser) (io.ReadCloser, error) { reader, err := gzip.NewReader(body) if err != nil { return nil, err } return &gzipBodyReader{reader, body}, nil } type URLClient struct { *http.Client TLS *tls.Config Cfg URLClientOption } func (client *URLClient) HTTPDoWithContext(ctx context.Context, method string, rawURL string, headers http.Header, body []byte) (resp *http.Response, err error) { if strings.HasPrefix(rawURL, "https") { if transport, ok := client.Client.Transport.(*http.Transport); ok { transport.TLSClientConfig = client.TLS } } if headers == nil { headers = make(http.Header) } if _, ok := headers[HeaderHost]; !ok { parsedURL, err := url.Parse(rawURL) if err != nil { return nil, err } headers.Set(HeaderHost, parsedURL.Host) } if _, ok := headers[HeaderAccept]; !ok { headers.Set(HeaderAccept, AcceptAny) } if _, ok := headers[HeaderAcceptEncoding]; !ok && client.Cfg.Compressed { headers.Set(HeaderAcceptEncoding, "deflate, gzip") } req, err := http.NewRequest(method, rawURL, bytes.NewBuffer(body)) if err != nil { return nil, fmt.Errorf("create request failed: %s", err.Error()) } req = req.WithContext(ctx) req.Header = headers DumpRequestOut(req) resp, err = client.Client.Do(req) if err != nil { return nil, err } DumpResponse(resp) switch resp.Header.Get(HeaderContentEncoding) { case "gzip": reader, err := NewGZipBodyReader(resp.Body) if err != nil { _, err = io.Copy(io.Discard, resp.Body) if err != nil { log.Error("", err) resp.Body.Close() return nil, err } resp.Body.Close() return nil, err } resp.Body = reader } return resp, nil } func DumpRequestOut(req *http.Request) { if req == nil || !util.StringTRUE(os.Getenv("DEBUG_MODE")) { return } fmt.Println(">", req.URL.String()) b, _ := httputil.DumpRequestOut(req, true) err := buffer.ReadLine(bytes.NewBuffer(b), func(line string) bool { fmt.Println(">", line) return true }) if err != nil { log.Error("", err) } } func DumpResponse(resp *http.Response) { if resp == nil || !util.StringTRUE(os.Getenv("DEBUG_MODE")) { return } b, _ := httputil.DumpResponse(resp, true) err := buffer.ReadLine(bytes.NewBuffer(b), func(line string) bool { fmt.Println("<", line) return true }) if err != nil { log.Error("", err) } } func (client *URLClient) HTTPDo(method string, rawURL string, headers http.Header, body []byte) (resp *http.Response, err error) { return client.HTTPDoWithContext(context.Background(), method, rawURL, headers, body) } func DefaultURLClientOption() URLClientOption { return defaultURLClientOption } func setOptionDefaultValue(o *URLClientOption) URLClientOption { if o == nil { return defaultURLClientOption } option := *o if option.RequestTimeout <= 0 { option.RequestTimeout = defaultURLClientOption.RequestTimeout } if option.HandshakeTimeout <= 0 { option.HandshakeTimeout = defaultURLClientOption.HandshakeTimeout } if option.ResponseHeaderTimeout <= 0 { option.ResponseHeaderTimeout = defaultURLClientOption.ResponseHeaderTimeout } if option.SSLVersion == 0 { option.SSLVersion = defaultURLClientOption.SSLVersion } return option } func GetURLClient(o URLClientOption) (client *URLClient, err error) { option := setOptionDefaultValue(&o) client = &URLClient{ Client: &http.Client{ Transport: &http.Transport{ MaxIdleConnsPerHost: option.ConnsPerHost, TLSHandshakeTimeout: option.HandshakeTimeout, ResponseHeaderTimeout: option.ResponseHeaderTimeout, DisableCompression: !option.Compressed, }, Timeout: option.RequestTimeout, }, Cfg: option, } if option.SSLEnabled { opts := append(tlsutil.DefaultClientTLSOptions(), tlsutil.WithVerifyPeer(option.VerifyPeer), tlsutil.WithCA(option.CAFile), tlsutil.WithCert(option.CertFile), tlsutil.WithKey(option.CertKeyFile), tlsutil.WithKeyPass(option.CertKeyPWD)) client.TLS, err = tlsutil.GetClientTLSConfig(opts...) if err != nil { return nil, err } } return }