1 Star 1 Fork 0

bon-ami / eztools

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
rest.go 10.70 KB
一键复制 编辑 原始数据 按行查看 历史
package eztools
import (
"bytes"
"crypto/tls"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/mongodb-forks/digest"
)
const (
AUTH_NONE = iota
AUTH_PLAIN
AUTH_BASIC
AUTH_DIGEST
METHOD_GET = "GET"
METHOD_PUT = "PUT"
METHOD_POST = "POST"
METHOD_DEL = "DELETE"
BODY_TYPE_JSON = "json"
BODY_TYPE_FILE = "file"
BODY_TYPE_TEXT = "text"
)
var AUTH_INSECURE_TLS bool
type AuthInfo struct {
Type int
User, Pass string
}
func genFile(defRdr io.Reader, fType, fName string) (string, io.Reader) {
if len(fName) < 1 {
if len(fType) < 1 {
return "application/json; charset=utf-8", defRdr
}
return fType, defRdr
}
if len(fType) < 1 {
fType = "file"
}
var err error
defer func() {
if err != nil {
LogPrint(err)
}
}()
// New multipart writer.
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormFile(fType, filepath.Base(fName))
if err != nil {
return fType, defRdr
}
file, err := os.Open(fName)
if err != nil {
return fType, defRdr
}
_, err = io.Copy(fw, file)
if err != nil {
return fType, defRdr
}
writer.Close()
return writer.FormDataContentType(), bytes.NewReader(body.Bytes())
}
func genReq(method, url string, bodyReq io.Reader, fType, fName string,
hdrs map[string]string) (req *http.Request, err error) {
bodyType, bodyReq := genFile(bodyReq, fType, fName)
req, err = http.NewRequest(method, url, bodyReq)
if err != nil {
if Debugging {
ShowStrln("failed to create " + method)
}
return
}
addHdr := func(nm, vl string) {
_, ok := hdrs[nm]
if ok {
return
}
req.Header.Add(nm, vl)
}
if bodyReq != nil {
addHdr("Content-Type", bodyType)
/*if Debugging && Verbose > 2 {
ShowStrln("body type=" + bodyType)
}*/
}
addHdr("Accept", "*/*")
for n, v := range hdrs {
req.Header.Add(n, v)
/*if Debugging && Verbose > 2 {
ShowStrln("adding header:" + n + "=" + v)
}*/
}
return
}
// RestGetBody get body from response, stripping magic.
// Return values: bodyType and statusCode are returned as long as resp is provided.
//
// bodyBytes is nil if Content-Length is 0 in header.
// ErrInvalidInput=no response input
// ErrOutOfBound=magic not matched
// ErrNoValidResults=bad status code (non 2xx)
// other errors from io.ReadAll()
func RestGetBody(resp *http.Response, magic []byte) (bodyType string,
bodyBytes []byte, statusCode int, err error) {
if resp == nil {
err = ErrInvalidInput
return
}
defer resp.Body.Close()
bodyType = resp.Header.Get("Content-Type")
statusCode = resp.StatusCode
bodyBytes, err = io.ReadAll(resp.Body)
if statusCode < http.StatusOK || statusCode >= http.StatusBadRequest {
if Debugging && Verbose > 1 {
ShowStrln("failure response " + strconv.Itoa(statusCode))
}
err = ErrNoValidResults
/*var b []byte
if resp.ContentLength > 0 {
b = make([]byte, resp.ContentLength)
resp.Body.Read(b)
}
return "", b, statusCode, errors.New(resp.Status)*/
return
}
if err != nil { // body not read
//LogErrPrint(err)
return
}
/*if Debugging && Verbose > 2 {
ShowStrln("resp code=" + strconv.Itoa(statusCode))
}*/
if cl := resp.Header.Get("Content-Length"); cl == "0" {
/*if Debugging && Verbose > 2 {
ShowStrln("no body in response")
}*/
return
}
if bodyBytes == nil || len(bodyBytes) < 1 {
/*if Debugging && Verbose > 2 {
LogPrintWtTime("no body")
}*/
return
}
if len(magic) > 0 {
/*if Debugging && Verbose > 1 {
ShowStrln("stripping magic")
}*/
if bytes.HasPrefix(bodyBytes, magic) {
bodyBytes = bytes.TrimLeft(bytes.TrimPrefix(bodyBytes,
magic), "\n\r")
} else {
err = ErrOutOfBound
return
}
}
/*if Debugging && Verbose > 2 {
LogPrintWtTime("type", bodyType, "body", bodyBytes)
}*/
return
}
// RestParseBody parses body from response to json or file.
//
// text body is not processed.
//
// Return values: ErrIncomplete=not parsed because type not recognized
//
// other errors are from RestGetBody(), json.Unmarshal() or FileWrite()
func RestParseBody(resp *http.Response, fileName string, strucOut interface{},
magic []byte) (recognized, bodyType string, bodyBytes []byte, statusCode int, err error) {
bodyType, bodyBytes, statusCode, err = RestGetBody(resp, magic)
if err != nil || (bodyBytes == nil || len(bodyBytes) < 1) {
return
}
switch {
case strings.Contains(bodyType, "application/json"):
recognized = BODY_TYPE_JSON
if strucOut != nil {
err = json.Unmarshal(bodyBytes, strucOut)
}
case strings.Contains(bodyType, "text/plain"),
strings.Contains(bodyType, "application/xml"),
strings.Contains(bodyType, "application/xhtml+xml"),
strings.Contains(bodyType, "application/javascript"),
strings.Contains(bodyType, "text/javascript"):
recognized = BODY_TYPE_TEXT
// bytes are maybe better than string, so leave them as are
/*if Debugging && Verbose > 2 {
LogPrintWtTime("type", bodyType, "body", bodyBytes)
}*/
case strings.Contains(bodyType, "file"),
strings.Contains(bodyType, "audio"),
strings.Contains(bodyType, "video"),
strings.Contains(bodyType, "application"),
strings.Contains(bodyType, "image"):
recognized = BODY_TYPE_FILE
if len(fileName) > 0 {
err = FileWrite(fileName, bodyBytes)
/*var out *os.File
out, err = os.Create(fileName)
if err != nil {
break
}
defer out.Close()
_, err = io.Copy(out, bytes.NewReader(bodyBytes))*/
}
default:
err = ErrIncomplete
}
return
}
func restSend(method, url string, to time.Duration,
bodyReq io.Reader, fType, fName string, hdrs map[string]string,
funcReqProc func(req *http.Request),
funcReqSend func(req *http.Request) (*http.Response,
error)) (resp *http.Response, err error) {
/*if Debugging && Verbose > 1 {
ShowStrln(method + " " + url)
}*/
req, err := genReq(method, url, bodyReq, fType, fName, hdrs)
if err != nil {
return
}
if funcReqProc != nil {
funcReqProc(req)
}
if Debugging && Verbose > 2 {
LogWtTime(*req)
}
//ShowSthln(req.ContentLength)
if funcReqSend == nil {
tc := &tls.Config{InsecureSkipVerify: AUTH_INSECURE_TLS}
tr := &http.Transport{TLSClientConfig: tc}
cli := &http.Client{Timeout: to, Transport: tr}
funcReqSend = cli.Do
}
resp, err = funcReqSend(req)
if err != nil {
if Debugging {
ShowStrln("failed to send/get")
}
/*} else {
if Debugging && Verbose > 2 {
LogWtTime(*resp)
}*/
}
return
}
const defRestGetTO = 60 * time.Second
func restSendNParseResp(method, url string, authInfo AuthInfo,
bodyReq io.Reader, fType, fileOut string,
hdrs map[string]string) (resp *http.Response, err error) {
switch authInfo.Type {
case AUTH_DIGEST:
resp, err = restSend(method, url, 0, bodyReq,
fType, fileOut, hdrs, nil,
func(req *http.Request) (*http.Response, error) {
t := digest.NewTransport(authInfo.User, authInfo.Pass)
return t.RoundTrip(req)
})
case AUTH_BASIC:
resp, err = restSend(method, url, defRestGetTO, bodyReq,
fType, fileOut, hdrs,
func(req *http.Request) {
if len(authInfo.Pass) > 0 {
req.SetBasicAuth(authInfo.User, authInfo.Pass)
}
}, nil)
case AUTH_PLAIN:
resp, err = restSend(method, url, defRestGetTO, bodyReq,
fType, fileOut, hdrs,
func(req *http.Request) {
if len(authInfo.Pass) > 0 {
req.Header.Set("authorization", "Basic "+authInfo.Pass)
}
}, nil)
default: // AUTH_NONE
resp, err = restSend(method, url, defRestGetTO, bodyReq,
fType, fileOut, hdrs, nil, nil)
}
//statusCode = resp.StatusCode
return
}
/*
RestSendFileNHdr sends Restful API and returns the result.
Specify a file with name and type to be sent as body, and/or extra headers.
If something wrong with the file, a request will be sent without it anyway.
*/
func RestSendFileNHdr(method, url string, authInfo AuthInfo,
fType, fName string, hdrs map[string]string) (*http.Response, error) {
return restSendNParseResp(method,
url, authInfo, nil, fType, fName, hdrs)
}
// RestSendHdr sends Restful API and returns the result.
// With body and/or extra headers.
func RestSendHdr(method, url string, authInfo AuthInfo,
bodyReq io.Reader, hdrs map[string]string) (*http.Response, error) {
return restSendNParseResp(method,
url, authInfo, bodyReq, "", "", hdrs)
}
// RestSend sends Restful API request and returns the result.
// = HttpSend + AuthInfo
func RestSend(method, url string, authInfo AuthInfo,
bodyReq io.Reader) (*http.Response, error) {
return restSendNParseResp(method,
url, authInfo, bodyReq, "", "", nil)
}
// HttpSend sends HTTP request and returns the result.
// It does not need AuthInfo as RestSend.
// Set AUTH_INSECURE_TLS to true to skip TLS (X509) verification
func HttpSend(method, url string, bodyReq io.Reader) (*http.Response, error) {
return restSendNParseResp(method, url,
AuthInfo{}, bodyReq, "", "", nil)
}
/*
RangeStrMap iterate through map[string]interface{} obj, calling fun for
each element recursively. When fun returns true, it stops.
false is returned if no element found.
*/
func RangeStrMap(obj interface{}, fun func(k string, v interface{}) bool) bool {
//if the argument is not a map, ignore it
mobj, ok := obj.(map[string]interface{})
if !ok {
return false
}
for k, v := range mobj {
//key match, return value
if fun(k, v) {
return true
}
//if the value is a map, search recursively
if m, ok := v.(map[string]interface{}); ok {
if RangeStrMap(m, fun) {
return true
}
}
//if the value is an array, search recursively
//from each element
if va, ok := v.([]interface{}); ok {
for _, a := range va {
if RangeStrMap(a, fun) {
return true
}
}
}
}
//element not found
return false
}
/*
FindStrMap find string key in map[string]interface{} obj,
returning the value and true or nil and false.
*/
func FindStrMap(obj interface{}, key string) (interface{}, bool) {
//if the argument is not a map, ignore it
mobj, ok := obj.(map[string]interface{})
if !ok {
return nil, false
}
for k, v := range mobj {
//key match, return value
if k == key {
return v, true
}
//if the value is a map, search recursively
if m, ok := v.(map[string]interface{}); ok {
if res, ok := FindStrMap(m, key); ok {
return res, true
}
}
//if the value is an array, search recursively
//from each element
if va, ok := v.([]interface{}); ok {
for _, a := range va {
if res, ok := FindStrMap(a, key); ok {
return res, true
}
}
}
}
//element not found
return nil, false
}
Go
1
https://gitee.com/bon-ami/eztools.git
git@gitee.com:bon-ami/eztools.git
bon-ami
eztools
eztools
v4.11.0

搜索帮助