blob: 6fc15d15643a6b86e335b9eca866377ee259825e [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"
"encoding/json"
"fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
"io"
"io/ioutil"
"net"
"net/http"
"reflect"
"time"
)
const (
DEFAULT_TLS_HANDSHAKE_TIMEOUT = 30 * time.Second
DEFAULT_HTTP_RESPONSE_TIMEOUT = 10 * time.Second
DEFAULT_REQUEST_TIMEOUT = 300 * time.Second
HTTP_ERROR_STATUS_CODE = 600
)
type HttpClient struct {
gzip bool
Client *http.Client
}
func NewDialer() *net.Dialer {
return &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}
}
func NewTransport() *http.Transport {
return &http.Transport{
Dial: NewDialer().Dial,
MaxIdleConnsPerHost: 5,
ResponseHeaderTimeout: DEFAULT_HTTP_RESPONSE_TIMEOUT,
TLSHandshakeTimeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
}
}
/**
获取普通HTTP客户端
*/
func GetHttpClient(gzip bool) (client *HttpClient, err error) {
return &HttpClient{
gzip: gzip,
Client: &http.Client{
Transport: NewTransport(),
Timeout: DEFAULT_REQUEST_TIMEOUT,
},
}, nil
}
func GetClient() (*HttpClient, error) {
return GetHttpClient(false)
}
func (client *HttpClient) getHeaders(method string, headers map[string]string, body interface{}) map[string]string {
newHeaders := make(map[string]string)
if body != nil {
newHeaders[HEADER_CONTENT_TYPE] = CONTENT_TYPE_JSON
newHeaders[HEADER_ACCEPT] = ACCEPT_JSON
}
if client.gzip {
newHeaders[HEADER_ACCEPT_ENCODING] = ENCODING_GZIP
newHeaders[HEADER_CONTENT_ENCODING] = ENCODING_GZIP
}
if headers != nil {
for key, value := range headers {
newHeaders[key] = value
}
}
return newHeaders
}
func gzipCompress(src []byte) (dst []byte) {
var byteBuffer bytes.Buffer
func() {
gzipWriter := gzip.NewWriter(&byteBuffer)
defer gzipWriter.Close()
gzipWriter.Write(src)
}()
return byteBuffer.Bytes()
}
func readAndGunzip(reader io.Reader) (dst []byte, err error) {
gzipReader, err := gzip.NewReader(reader)
if err != nil {
util.Logger().Errorf(err, "duplicate gzip reader failed.")
return nil, err
}
defer gzipReader.Close()
dst, err = ioutil.ReadAll(gzipReader)
if err != nil {
util.Logger().Errorf(err, "read from gzip reader failed.")
return nil, err
}
return dst, nil
}
func (client *HttpClient) httpDo(method string, url string, headers map[string]string, body interface{}) (int, string) {
status, result := HTTP_ERROR_STATUS_CODE, ""
var bodyBytes []byte = nil
var err error = nil
var bodyReader io.Reader = nil
if body != nil {
if headers == nil || len(headers[HEADER_CONTENT_TYPE]) == 0 {
// 如果请求头未传入Content-Type,则按照json格式进行编码(如果是非json类型,需要自行在headers里指定类型)
bodyBytes, err = json.Marshal(body)
if err != nil {
util.Logger().Errorf(err, "marshal object failed.")
return status, result
}
} else {
// 如果指定了Content-Type类型,则传入的body必须为byte流
var ok bool = false
bodyBytes, ok = body.([]byte)
if !ok {
util.Logger().Errorf(nil, "invalid body type '%s'(%s), body must type of byte array if Content-Type specified.", reflect.TypeOf(body), headers[HEADER_CONTENT_TYPE])
return status, result
}
}
//如果配置了gzip压缩,则对body压缩一次(如果请求头里传入已经gzip压缩了,则不重复压缩)
if client.gzip && (headers == nil || headers[HEADER_CONTENT_ENCODING] != ENCODING_GZIP) {
bodyBytes = gzipCompress(bodyBytes)
}
bodyReader = bytes.NewBuffer(bodyBytes)
}
req, err := http.NewRequest(method, url, bodyReader)
if err != nil {
util.Logger().Errorf(err, "create request failed.")
return status, result
}
newHeaders := client.getHeaders(method, headers, body)
for key, value := range newHeaders {
req.Header.Set(key, value)
}
resp, err := client.Client.Do(req)
if err != nil {
util.Logger().Errorf(err, "invoke request failed.")
return status, result
}
defer resp.Body.Close()
status = resp.StatusCode
var respBody []byte
if resp.Header.Get(HEADER_CONTENT_ENCODING) == ENCODING_GZIP {
// 如果响应头里包含了响应消息的压缩格式为gzip,则在返回前先解压缩
respBody, _ = readAndGunzip(resp.Body)
} else {
respBody, _ = ioutil.ReadAll(resp.Body)
}
result = util.BytesToStringWithNoCopy(respBody)
return status, result
}
func (client *HttpClient) HttpDo(method string, url string, headers map[string]string, body interface{}) (*http.Response, error) {
var bodyBytes []byte = nil
var err error = nil
var bodyReader io.Reader = nil
if body != nil {
if headers == nil || len(headers[HEADER_CONTENT_TYPE]) == 0 {
// 如果请求头未传入Conent-Type,则按照json格式进行编码(如果是非json类型,需要自行在headers里指定类型)
bodyBytes, err = json.Marshal(body)
if err != nil {
util.Logger().Errorf(err, "marshal object failed.")
return nil, err
}
} else {
// 如果指定了Content-Type类型,则传入的body必须为byte流
var ok bool = false
bodyBytes, ok = body.([]byte)
if !ok {
err := fmt.Errorf("invalid body type '%s'(%s), body must type of byte array if Content-Type specified.",
reflect.TypeOf(body), headers[HEADER_CONTENT_TYPE])
util.Logger().Errorf(err, "")
return nil, err
}
}
//如果配置了gzip压缩,则对body压缩一次(如果请求头里传入已经gzip压缩了,则不重复压缩)
if client.gzip && (headers == nil || headers[HEADER_CONTENT_ENCODING] != ENCODING_GZIP) {
bodyBytes = gzipCompress(bodyBytes)
}
bodyReader = bytes.NewBuffer(bodyBytes)
}
req, err := http.NewRequest(method, url, bodyReader)
if err != nil {
util.Logger().Errorf(err, "create request failed.")
return nil, err
}
newHeaders := client.getHeaders(method, headers, body)
for key, value := range newHeaders {
req.Header.Set(key, value)
}
resp, err := client.Client.Do(req)
if err != nil {
util.Logger().Errorf(err, "Request -----> %s failed.", url)
return resp, err
}
return resp, err
}
func (client *HttpClient) Get(url string, headers map[string]string) (int, string) {
return client.httpDo(http.MethodGet, url, headers, nil)
}
func (client *HttpClient) Put(url string, headers map[string]string, body interface{}) (int, string) {
return client.httpDo(http.MethodPut, url, headers, body)
}
func (client *HttpClient) Post(url string, headers map[string]string, body interface{}) (int, string) {
return client.httpDo(http.MethodPost, url, headers, body)
}
func (client *HttpClient) Delete(url string, headers map[string]string) (int, string) {
return client.httpDo(http.MethodDelete, url, headers, nil)
}
func (client *HttpClient) Do(req *http.Request) (*http.Response, error) {
return client.Client.Do(req)
}