mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
* 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 ...
285 lines
6.6 KiB
Go
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
|
|
}
|
|
}
|