blob: 0d8c0dd1e4e734e5d415845fcd915f00c18a7645 [file] [log] [blame]
//Copyright 2017 Huawei Technologies Co., Ltd
//
//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.
package rest
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"github.com/ServiceComb/service-center/util"
"github.com/ServiceComb/service-center/util/errors"
"github.com/astaxie/beego"
"io"
"io/ioutil"
"net"
"net/http"
"reflect"
"time"
)
type HttpClient struct {
gzip bool
client *http.Client
}
func getTLSTransport(verifyPeer bool, supplyCert bool, verifyCN bool) (transport *http.Transport, err error) {
tlsConfig, err := GetClientTLSConfig(verifyPeer, supplyCert, verifyCN)
if err != nil {
return nil, err
}
transport = &http.Transport{
TLSClientConfig: tlsConfig,
TLSHandshakeTimeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
ResponseHeaderTimeout: DEFAULT_HTTP_RESPONSE_TIMEOUT,
}
return transport, nil
}
/**
获取普通HTTP客户端
*/
var httpClient = &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
var maxIdleConnsPerHost = beego.AppConfig.DefaultInt("max_idle_conns_per_host", 200)
var transport = &http.Transport{
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
ResponseHeaderTimeout: 10 * time.Second,
}
func GetHttpClient(gzip bool) (client *HttpClient, err error) {
return &HttpClient{
gzip: gzip,
client: httpClient,
}, nil
}
/**
获取TLS认证HTTP客户端
gzip 控制是否支持压缩
verifyPeer 控制是否认证客户端
supplyCert 控制是否加载和发送证书
verifyCN 控制是否认证对端CN
*/
func getHttpsClient(gzip, verifyPeer, supplyCert, verifyCN bool) (client *HttpClient, err error) {
transport, err := getTLSTransport(verifyPeer, supplyCert, verifyCN)
if err != nil {
util.LOGGER.Errorf(err, "get tls transport failed.")
}
client = &HttpClient{
gzip: gzip,
client: &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
},
}
return client, nil
}
/**
获取TLS认证HTTP客户端(支持压缩,提供证书,是否认证对端通过参数控制)
*/
func GetHttpsClient(verifyPeer bool) (client *HttpClient, err error) {
return getHttpsClient(true, verifyPeer, true, false)
}
/**
获取匿名认证HTTP客户端(支持压缩,不提供证书,也不认证对端)
*/
func GetAnnoHttpsClient(gzip bool) (client *HttpClient, err error) {
return getHttpsClient(gzip, false, false, 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["Content-Type"] = "application/json;utf-8"
newHeaders["Accept"] = "application/json"
}
if client.gzip {
newHeaders["Accept-Encoding"] = "gzip"
newHeaders["Content-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["Content-Type"]) == 0 {
// 如果请求头未传入Conent-Type,则按照json格式进行编码(如果是非json类型,需要自行在headers里指定类型)
bodyBytes, err = json.Marshal(body)
if err != nil {
util.LOGGER.Errorf(err, "mashal 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["Content-Type"])
return status, result
}
}
//如果配置了gzip压缩,则对body压缩一次(如果请求头里传入已经gzip压缩了,则不重复压缩)
if client.gzip && (headers == nil || headers["Content-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("Content-Encoding") == "gzip" {
// 如果响应头里包含了响应消息的压缩格式为gzip,则在返回前先解压缩
respBody, _ = readAndGunzip(resp.Body)
} else {
respBody, _ = ioutil.ReadAll(resp.Body)
}
result = string(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["Content-Type"]) == 0 {
// 如果请求头未传入Conent-Type,则按照json格式进行编码(如果是非json类型,需要自行在headers里指定类型)
bodyBytes, err = json.Marshal(body)
if err != nil {
util.LOGGER.Errorf(err, "mashal object failed.")
return nil, err
}
} else {
// 如果指定了Content-Type类型,则传入的body必须为byte流
var ok bool = false
bodyBytes, ok = body.([]byte)
if !ok {
err := errors.New(fmt.Sprintf("invalid body type '%s'(%s), body must type of byte array if Content-Type specified.", reflect.TypeOf(body), headers["Content-Type"]))
util.LOGGER.Errorf(err, "")
return nil, err
}
}
//如果配置了gzip压缩,则对body压缩一次(如果请求头里传入已经gzip压缩了,则不重复压缩)
if client.gzip && (headers == nil || headers["Content-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
}
if resp.StatusCode != 200 && resp.StatusCode != 201 {
util.LOGGER.Errorf(nil, "Request -----> %s not ok, status is %s", url, resp.Status)
return resp, errors.New(fmt.Sprintf("Request failed, status is %s", resp.Status))
}
return resp, err
}
func (client *HttpClient) Get(url string, headers map[string]string) (int, string) {
return client.httpDo(HTTP_METHOD_GET, url, headers, nil)
}
func (client *HttpClient) Put(url string, headers map[string]string, body interface{}) (int, string) {
return client.httpDo(HTTP_METHOD_PUT, url, headers, body)
}
func (client *HttpClient) Post(url string, headers map[string]string, body interface{}) (int, string) {
return client.httpDo(HTTP_METHOD_POST, url, headers, body)
}
func (client *HttpClient) Delete(url string, headers map[string]string) (int, string) {
return client.httpDo(HTTP_METHOD_DELETE, url, headers, nil)
}
func (client *HttpClient) Do(req *http.Request) (*http.Response, error) {
return client.client.Do(req)
}