370 lines
8.2 KiB
Go
370 lines
8.2 KiB
Go
|
package xhttp
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"crypto/tls"
|
||
|
"encoding/json"
|
||
|
"encoding/xml"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"mime/multipart"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/go-pay/gopay"
|
||
|
"github.com/go-pay/gopay/pkg/util"
|
||
|
)
|
||
|
|
||
|
type Client struct {
|
||
|
HttpClient *http.Client
|
||
|
Transport *http.Transport
|
||
|
Header http.Header
|
||
|
Timeout time.Duration
|
||
|
Host string
|
||
|
bodySize int // body size limit(MB), default is 10MB
|
||
|
url string
|
||
|
method string
|
||
|
requestType RequestType
|
||
|
FormString string
|
||
|
ContentType string
|
||
|
unmarshalType string
|
||
|
multipartBodyMap map[string]interface{}
|
||
|
jsonByte []byte
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
// NewClient , default tls.Config{InsecureSkipVerify: true}
|
||
|
func NewClient() (client *Client) {
|
||
|
client = &Client{
|
||
|
HttpClient: &http.Client{
|
||
|
Timeout: 60 * time.Second,
|
||
|
Transport: &http.Transport{
|
||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||
|
DisableKeepAlives: true,
|
||
|
Proxy: http.ProxyFromEnvironment,
|
||
|
},
|
||
|
},
|
||
|
Transport: nil,
|
||
|
Header: make(http.Header),
|
||
|
bodySize: 10, // default is 10MB
|
||
|
requestType: TypeJSON,
|
||
|
unmarshalType: string(TypeJSON),
|
||
|
}
|
||
|
return client
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetTransport(transport *http.Transport) (client *Client) {
|
||
|
c.Transport = transport
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetTLSConfig(tlsCfg *tls.Config) (client *Client) {
|
||
|
c.Transport = &http.Transport{TLSClientConfig: tlsCfg, DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetTimeout(timeout time.Duration) (client *Client) {
|
||
|
c.Timeout = timeout
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetHost(host string) (client *Client) {
|
||
|
c.Host = host
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
// set body size (MB), default is 10MB
|
||
|
func (c *Client) SetBodySize(sizeMB int) (client *Client) {
|
||
|
c.bodySize = sizeMB
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) Type(typeStr RequestType) (client *Client) {
|
||
|
if _, ok := types[typeStr]; ok {
|
||
|
c.requestType = typeStr
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) Get(url string) (client *Client) {
|
||
|
c.method = GET
|
||
|
c.url = url
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) Post(url string) (client *Client) {
|
||
|
c.method = POST
|
||
|
c.url = url
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) Put(url string) (client *Client) {
|
||
|
c.method = PUT
|
||
|
c.url = url
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) Delete(url string) (client *Client) {
|
||
|
c.method = DELETE
|
||
|
c.url = url
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) Patch(url string) (client *Client) {
|
||
|
c.method = PATCH
|
||
|
c.url = url
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) SendStruct(v interface{}) (client *Client) {
|
||
|
if v == nil {
|
||
|
return c
|
||
|
}
|
||
|
bs, err := json.Marshal(v)
|
||
|
if err != nil {
|
||
|
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, v)
|
||
|
return c
|
||
|
}
|
||
|
switch c.requestType {
|
||
|
case TypeJSON:
|
||
|
c.jsonByte = bs
|
||
|
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||
|
body := make(map[string]interface{})
|
||
|
if err = json.Unmarshal(bs, &body); err != nil {
|
||
|
c.err = fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||
|
return c
|
||
|
}
|
||
|
c.FormString = FormatURLParam(body)
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) SendBodyMap(bm map[string]interface{}) (client *Client) {
|
||
|
if bm == nil {
|
||
|
return c
|
||
|
}
|
||
|
switch c.requestType {
|
||
|
case TypeJSON:
|
||
|
bs, err := json.Marshal(bm)
|
||
|
if err != nil {
|
||
|
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm)
|
||
|
return c
|
||
|
}
|
||
|
c.jsonByte = bs
|
||
|
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||
|
c.FormString = FormatURLParam(bm)
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) SendMultipartBodyMap(bm map[string]interface{}) (client *Client) {
|
||
|
if bm == nil {
|
||
|
return c
|
||
|
}
|
||
|
switch c.requestType {
|
||
|
case TypeJSON:
|
||
|
bs, err := json.Marshal(bm)
|
||
|
if err != nil {
|
||
|
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm)
|
||
|
return c
|
||
|
}
|
||
|
c.jsonByte = bs
|
||
|
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||
|
c.FormString = FormatURLParam(bm)
|
||
|
case TypeMultipartFormData:
|
||
|
c.multipartBodyMap = bm
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
// encodeStr: url.Values.Encode() or jsonBody
|
||
|
func (c *Client) SendString(encodeStr string) (client *Client) {
|
||
|
switch c.requestType {
|
||
|
case TypeJSON:
|
||
|
c.jsonByte = []byte(encodeStr)
|
||
|
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||
|
c.FormString = encodeStr
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Client) EndStruct(ctx context.Context, v interface{}) (res *http.Response, err error) {
|
||
|
res, bs, err := c.EndBytes(ctx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if res.StatusCode != http.StatusOK {
|
||
|
return res, fmt.Errorf("StatusCode(%d) != 200", res.StatusCode)
|
||
|
}
|
||
|
|
||
|
switch c.unmarshalType {
|
||
|
case string(TypeJSON):
|
||
|
err = json.Unmarshal(bs, &v)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||
|
}
|
||
|
return res, nil
|
||
|
case string(TypeXML):
|
||
|
err = xml.Unmarshal(bs, &v)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||
|
}
|
||
|
return res, nil
|
||
|
default:
|
||
|
return nil, errors.New("unmarshalType Type Wrong")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Client) EndBytes(ctx context.Context) (res *http.Response, bs []byte, err error) {
|
||
|
if c.err != nil {
|
||
|
return nil, nil, c.err
|
||
|
}
|
||
|
var (
|
||
|
body io.Reader
|
||
|
bw *multipart.Writer
|
||
|
)
|
||
|
// multipart-form-data
|
||
|
if c.requestType == TypeMultipartFormData {
|
||
|
body = &bytes.Buffer{}
|
||
|
bw = multipart.NewWriter(body.(io.Writer))
|
||
|
}
|
||
|
|
||
|
reqFunc := func() (err error) {
|
||
|
switch c.method {
|
||
|
case GET:
|
||
|
switch c.requestType {
|
||
|
case TypeJSON:
|
||
|
c.ContentType = types[TypeJSON]
|
||
|
case TypeForm, TypeFormData, TypeUrlencoded:
|
||
|
c.ContentType = types[TypeForm]
|
||
|
case TypeMultipartFormData:
|
||
|
c.ContentType = bw.FormDataContentType()
|
||
|
case TypeXML:
|
||
|
c.ContentType = types[TypeXML]
|
||
|
c.unmarshalType = string(TypeXML)
|
||
|
default:
|
||
|
return errors.New("Request type Error ")
|
||
|
}
|
||
|
case POST, PUT, DELETE, PATCH:
|
||
|
switch c.requestType {
|
||
|
case TypeJSON:
|
||
|
if c.jsonByte != nil {
|
||
|
body = strings.NewReader(string(c.jsonByte))
|
||
|
}
|
||
|
c.ContentType = types[TypeJSON]
|
||
|
case TypeForm, TypeFormData, TypeUrlencoded:
|
||
|
body = strings.NewReader(c.FormString)
|
||
|
c.ContentType = types[TypeForm]
|
||
|
case TypeMultipartFormData:
|
||
|
for k, v := range c.multipartBodyMap {
|
||
|
// file 参数
|
||
|
if file, ok := v.(*util.File); ok {
|
||
|
fw, err := bw.CreateFormFile(k, file.Name)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
_, _ = fw.Write(file.Content)
|
||
|
continue
|
||
|
}
|
||
|
// text 参数
|
||
|
vs, ok2 := v.(string)
|
||
|
if ok2 {
|
||
|
_ = bw.WriteField(k, vs)
|
||
|
} else if ss := util.ConvertToString(v); ss != "" {
|
||
|
_ = bw.WriteField(k, ss)
|
||
|
}
|
||
|
}
|
||
|
_ = bw.Close()
|
||
|
c.ContentType = bw.FormDataContentType()
|
||
|
case TypeXML:
|
||
|
body = strings.NewReader(c.FormString)
|
||
|
c.ContentType = types[TypeXML]
|
||
|
c.unmarshalType = string(TypeXML)
|
||
|
default:
|
||
|
return errors.New("Request type Error ")
|
||
|
}
|
||
|
default:
|
||
|
return errors.New("Only support GET and POST and PUT and DELETE ")
|
||
|
}
|
||
|
|
||
|
req, err := http.NewRequestWithContext(ctx, c.method, c.url, body)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
req.Header = c.Header
|
||
|
req.Header.Set("Content-Type", c.ContentType)
|
||
|
if c.Transport != nil {
|
||
|
c.HttpClient.Transport = c.Transport
|
||
|
}
|
||
|
if c.Host != "" {
|
||
|
req.Host = c.Host
|
||
|
}
|
||
|
if c.Timeout > 0 {
|
||
|
c.HttpClient.Timeout = c.Timeout
|
||
|
}
|
||
|
res, err = c.HttpClient.Do(req)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer res.Body.Close()
|
||
|
bs, err = ioutil.ReadAll(io.LimitReader(res.Body, int64(c.bodySize<<20))) // default 10MB change the size you want
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if err = reqFunc(); err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
return res, bs, nil
|
||
|
}
|
||
|
|
||
|
func FormatURLParam(body map[string]interface{}) (urlParam string) {
|
||
|
var (
|
||
|
buf strings.Builder
|
||
|
keys []string
|
||
|
)
|
||
|
for k := range body {
|
||
|
keys = append(keys, k)
|
||
|
}
|
||
|
sort.Strings(keys)
|
||
|
for _, k := range keys {
|
||
|
v, ok := body[k].(string)
|
||
|
if !ok {
|
||
|
v = convertToString(body[k])
|
||
|
}
|
||
|
if v != "" {
|
||
|
buf.WriteString(url.QueryEscape(k))
|
||
|
buf.WriteByte('=')
|
||
|
buf.WriteString(url.QueryEscape(v))
|
||
|
buf.WriteByte('&')
|
||
|
}
|
||
|
}
|
||
|
if buf.Len() <= 0 {
|
||
|
return ""
|
||
|
}
|
||
|
return buf.String()[:buf.Len()-1]
|
||
|
}
|
||
|
|
||
|
func convertToString(v interface{}) (str string) {
|
||
|
if v == nil {
|
||
|
return ""
|
||
|
}
|
||
|
var (
|
||
|
bs []byte
|
||
|
err error
|
||
|
)
|
||
|
if bs, err = json.Marshal(v); err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
str = string(bs)
|
||
|
return
|
||
|
}
|