Files
cloudpods/pkg/util/httputils/httputils.go
T
万垚奇 28c0452851 Merge pull request #984 in YUNIONIO/onecloud from ~WANYAOQI/onecloud:feature/host-baremetal-v2 to release/2.6.0
* commit '06446f778364e97cd2df66d8ed40f3c0c4d6574e': (135 commits)
  baremetal: ipmitool passowrd EscapeEchoString
  add hostmetrics
  add hostmetrics
  types struct 符合命名规范
  修正:centos 7宿主机格式化的ext4和xfs能够被centos 6虚拟机挂载
  add bsd raw socket code
  fix code
  fix code
  dep add gopsutil/mem
  fix dhcp server
  baremetal: quit signal handle
  baremetal: quit signal handle
  fix dhcp server
  gendoc
  remove jsonutils NewPair
  fix qmp montior cmd
  snapshot recycle, disk recycle, dhcp relay use raw socket
  fix code, test migrate snapshot, fix deploy
  add migrate presend arp
  add ethernet pkg
  ...
2019-01-28 20:04:24 +08:00

285 lines
6.6 KiB
Go

package httputils
import (
"context"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/fatih/color"
"github.com/moul/http2curl"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/trace"
)
type THttpMethod string
const (
USER_AGENT = "yunioncloud-go/201708"
GET = THttpMethod("GET")
HEAD = THttpMethod("HEAD")
POST = THttpMethod("POST")
PUT = THttpMethod("PUT")
PATCH = THttpMethod("PATCH")
DELETE = THttpMethod("DELETE")
OPTION = THttpMethod("OPTION")
)
var (
red = color.New(color.FgRed, color.Bold).PrintlnFunc()
green = color.New(color.FgGreen, color.Bold).PrintlnFunc()
yellow = color.New(color.FgYellow, color.Bold).PrintlnFunc()
cyan = color.New(color.FgHiCyan, color.Bold).PrintlnFunc()
)
type Error struct {
Id string
Fields []string
}
type JSONClientError struct {
Code int
Class string
Details string
Data Error
}
type JSONClientErrorMsg struct {
Error *JSONClientError
}
func (e *JSONClientError) Error() string {
errMsg := JSONClientErrorMsg{Error: e}
return jsonutils.Marshal(errMsg).String()
}
func headerExists(header *http.Header, key string) bool {
keyu := strings.ToUpper(key)
for k := range *header {
if strings.ToUpper(k) == keyu {
return true
}
}
return false
}
func GetAddrPort(urlStr string) (string, int, error) {
parts, err := url.Parse(urlStr)
if err != nil {
return "", 0, err
}
host := parts.Host
commaPos := strings.IndexByte(host, ':')
if commaPos > 0 {
port, err := strconv.ParseInt(host[commaPos+1:], 10, 32)
if err != nil {
return "", 0, err
} else {
return host[:commaPos], int(port), nil
}
} else {
switch parts.Scheme {
case "http":
return parts.Host, 80, nil
case "https":
return parts.Host, 443, nil
default:
return "", 0, fmt.Errorf("Unknown schema %s", parts.Scheme)
}
}
}
func GetClient(insecure bool) *http.Client {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
}
return &http.Client{Transport: tr}
}
func GetTimeoutClient(timeout time.Duration) *http.Client {
client := GetClient(true)
client.Timeout = timeout
return client
}
var defaultHttpClient *http.Client
func init() {
defaultHttpClient = GetClient(true)
}
func GetDefaultClient() *http.Client {
return defaultHttpClient
}
func Request(client *http.Client, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
if client == nil {
client = defaultHttpClient
}
if header == nil {
header = http.Header{}
}
ctxData := appctx.FetchAppContextData(ctx)
var clientTrace *trace.STrace
if !ctxData.Trace.IsZero() {
addr, port, err := GetAddrPort(urlStr)
if err != nil {
return nil, err
}
clientTrace = trace.StartClientTrace(&ctxData.Trace, addr, port, ctxData.ServiceName)
clientTrace.AddClientRequestHeader(header)
}
if len(ctxData.RequestId) > 0 {
header.Set("X-Request-Id", ctxData.RequestId)
}
req, err := http.NewRequest(string(method), urlStr, body)
if err != nil {
return nil, err
}
req.Header.Add("User-Agent", USER_AGENT)
if body == nil {
req.Header.Set("Content-Length", "0")
} else {
if headerExists(&header, "Content-Length") {
clen := header.Get("Content-Length")
req.ContentLength, _ = strconv.ParseInt(clen, 10, 64)
}
}
if header != nil {
for k, v := range header {
req.Header[k] = v
}
}
if debug {
yellow("Request", method, urlStr, req.Header, body)
// 忽略掉上传文件的请求,避免大量日志输出
if header.Get("Content-Type") != "application/octet-stream" {
curlCmd, _ := http2curl.GetCurlCommand(req)
cyan("CURL:", curlCmd)
}
}
resp, err := client.Do(req)
if err == nil && clientTrace != nil {
clientTrace.EndClientTraceHeader(resp.Header)
}
return resp, err
}
func JSONRequest(client *http.Client, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body jsonutils.JSONObject, debug bool) (http.Header, jsonutils.JSONObject, error) {
bodystr := ""
if !gotypes.IsNil(body) {
bodystr = body.String()
}
jbody := strings.NewReader(bodystr)
if header == nil {
header = http.Header{}
}
header.Add("Content-Type", "application/json")
resp, err := Request(client, ctx, method, urlStr, header, jbody, debug)
return ParseJSONResponse(resp, err, debug)
}
func ParseJSONResponse(resp *http.Response, err error, debug bool) (http.Header, jsonutils.JSONObject, error) {
if err != nil {
ce := JSONClientError{}
ce.Code = 499
ce.Details = err.Error()
return nil, nil, &ce
}
defer resp.Body.Close()
if debug {
if resp.StatusCode < 300 {
green("Status:", resp.StatusCode)
green(resp.Header)
} else if resp.StatusCode < 400 {
yellow("Status:", resp.StatusCode)
yellow(resp.Header)
} else {
red("Status:", resp.StatusCode)
red(resp.Header)
}
}
rbody, err := ioutil.ReadAll(resp.Body)
if debug {
fmt.Fprintf(os.Stderr, "Response body: %s\n", string(rbody))
}
if err != nil {
return nil, nil, fmt.Errorf("Fail to read body: %s", err)
}
var jrbody jsonutils.JSONObject = nil
if len(rbody) > 0 {
var err error
jrbody, err = jsonutils.Parse(rbody)
if err != nil && debug {
fmt.Fprintf(os.Stderr, "parsing json failed: %s", err)
}
}
if resp.StatusCode < 300 {
return resp.Header, jrbody, nil
} else if resp.StatusCode >= 300 && resp.StatusCode < 400 {
ce := JSONClientError{}
ce.Code = resp.StatusCode
ce.Details = resp.Header.Get("Location")
ce.Class = "redirect"
return nil, nil, &ce
} else {
ce := JSONClientError{}
if jrbody == nil {
ce.Code = resp.StatusCode
ce.Details = resp.Status
return nil, nil, &ce
}
err = jrbody.Unmarshal(&ce)
if len(ce.Class) > 0 && ce.Code >= 400 && len(ce.Details) > 0 {
return nil, nil, &ce
}
jrbody1, err := jrbody.GetMap()
if err != nil {
err = jrbody.Unmarshal(&ce)
if err != nil {
ce.Details = err.Error()
}
return nil, nil, &ce
}
var jrbody2 jsonutils.JSONObject
if len(jrbody1) > 1 {
jrbody2 = jsonutils.Marshal(jrbody1)
} else {
for _, v := range jrbody1 {
jrbody2 = v
}
}
if ecode, _ := jrbody2.GetString("code"); len(ecode) > 0 {
code, err := strconv.Atoi(ecode)
if err != nil {
ce.Class = ecode
} else {
ce.Code = code
}
}
if edetail := jsonutils.GetAnyString(jrbody2, []string{"message", "detail", "error_msg"}); len(edetail) > 0 {
ce.Details = edetail
}
if eclass := jsonutils.GetAnyString(jrbody2, []string{"title", "type", "error_code"}); len(eclass) > 0 {
ce.Class = eclass
}
return nil, nil, &ce
}
}