service/vendor/github.com/go-pay/gopay/pkg/xhttp/client.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
}