Files
cloudpods/pkg/util/httputils/httputils.go
wanyaoqi 4f2b50a869 - local disk snapshot create and delete
- auto snapshot everyday
- rewrite cornman
- make dep and some fix
- fix conflict
2018-09-06 16:22:36 +08:00

251 lines
5.9 KiB
Go

package httputils
import (
"context"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/moul/http2curl"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/trace"
"yunion.io/x/onecloud/pkg/appctx"
)
const (
USER_AGENT = "yunioncloud-go/201708"
)
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
}
func (e *JSONClientError) Error() string {
return fmt.Sprintf("JSONClientError: %s %d %s", e.Details, e.Code, e.Class)
}
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}
}
var defaultHttpClient *http.Client
func init() {
defaultHttpClient = GetClient(true)
}
func GetDefaultClient() *http.Client {
return defaultHttpClient
}
func Request(client *http.Client, ctx context.Context, method string, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
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)
}
method = strings.ToUpper(method)
req, err := http.NewRequest(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 string, 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.Println(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 {
jrbody, err = jsonutils.Parse(rbody)
if err != nil && debug {
log.Errorf("parse JSON body %s fail: %s", rbody, err)
}
///// XXX: ignore error case
// if err != nil && resp.StatusCode < 300 {
// return nil, nil, fmt.Errorf("Fail to decode body: %s", err)
// }
if jrbody != nil && debug {
fmt.Println(jrbody)
}
}
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 if resp.StatusCode >= 400 {
ce := JSONClientError{}
if jrbody == nil {
ce.Code = resp.StatusCode
ce.Details = resp.Status
return nil, nil, &ce
} else {
jrbody2, e := jrbody.Get("error")
if e == nil {
ecode, e := jrbody2.Int("code")
if e == nil {
ce.Code = int(ecode)
ce.Details, _ = jrbody2.GetString("message")
ce.Class, _ = jrbody2.GetString("title")
return nil, nil, &ce
} else {
ce.Code = resp.StatusCode
ce.Details = jrbody2.String()
return nil, nil, &ce
}
} else {
err = jrbody.Unmarshal(&ce)
if err != nil {
return nil, nil, err
} else {
return nil, nil, &ce
}
}
}
} else {
return resp.Header, jrbody, nil
}
}