blob: 3e280206502d76fbfa8934e03933b074b8f134f5 [file] [log] [blame]
/*
* 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
}