236 lines
4.9 KiB
Go
236 lines
4.9 KiB
Go
package request
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
JSON = "application/json"
|
|
XML = "application/xml"
|
|
|
|
plainTextType = "text/plain; charset=utf-8"
|
|
jsonContentType = "application/json"
|
|
formContentType = "application/x-www-form-urlencoded"
|
|
)
|
|
|
|
var (
|
|
jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json\-.*)(;|$))`)
|
|
xmlCheck = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`)
|
|
)
|
|
|
|
type Request struct {
|
|
context context.Context
|
|
method string
|
|
uri string
|
|
url *url.URL
|
|
body any
|
|
query url.Values
|
|
formData url.Values
|
|
header http.Header
|
|
contentType string
|
|
authorization Authorization
|
|
client *Client
|
|
rawRequest *http.Request
|
|
rawResponse *http.Response
|
|
}
|
|
|
|
func (r *Request) detectContentType(body interface{}) string {
|
|
contentType := plainTextType
|
|
kind := reflect.Indirect(reflect.ValueOf(body)).Type().Kind()
|
|
switch kind {
|
|
case reflect.Struct, reflect.Map:
|
|
contentType = jsonContentType
|
|
case reflect.String:
|
|
contentType = plainTextType
|
|
default:
|
|
if b, ok := body.([]byte); ok {
|
|
contentType = http.DetectContentType(b)
|
|
} else if kind == reflect.Slice {
|
|
contentType = jsonContentType
|
|
}
|
|
}
|
|
return contentType
|
|
}
|
|
|
|
func (r *Request) readRequestBody(contentType string, body any) (reader io.Reader, err error) {
|
|
var (
|
|
ok bool
|
|
s string
|
|
buf []byte
|
|
)
|
|
kind := reflect.Indirect(reflect.ValueOf(body)).Type().Kind()
|
|
if reader, ok = r.body.(io.Reader); ok {
|
|
return reader, nil
|
|
}
|
|
if buf, ok = r.body.([]byte); ok {
|
|
goto __end
|
|
}
|
|
if s, ok = r.body.(string); ok {
|
|
buf = []byte(s)
|
|
goto __end
|
|
}
|
|
if jsonCheck.MatchString(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
|
|
buf, err = json.Marshal(r.body)
|
|
goto __end
|
|
}
|
|
if xmlCheck.MatchString(contentType) && (kind == reflect.Struct) {
|
|
buf, err = xml.Marshal(r.body)
|
|
goto __end
|
|
}
|
|
err = fmt.Errorf("unmarshal content type %s", contentType)
|
|
__end:
|
|
if err == nil {
|
|
if len(buf) > 0 {
|
|
return bytes.NewReader(buf), nil
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *Request) SetContext(ctx context.Context) *Request {
|
|
r.context = ctx
|
|
return r
|
|
}
|
|
|
|
func (r *Request) AddQuery(k, v string) *Request {
|
|
r.query.Add(k, v)
|
|
return r
|
|
}
|
|
|
|
func (r *Request) SetQuery(vs map[string]string) *Request {
|
|
for k, v := range vs {
|
|
r.query.Set(k, v)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *Request) AddFormData(k, v string) *Request {
|
|
r.contentType = formContentType
|
|
r.formData.Add(k, v)
|
|
return r
|
|
}
|
|
|
|
func (r *Request) SetFormData(vs map[string]string) *Request {
|
|
r.contentType = formContentType
|
|
for k, v := range vs {
|
|
r.formData.Set(k, v)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *Request) SetBody(v any) *Request {
|
|
r.body = v
|
|
return r
|
|
}
|
|
|
|
func (r *Request) SetContentType(v string) *Request {
|
|
r.contentType = v
|
|
return r
|
|
}
|
|
|
|
func (r *Request) AddHeader(k, v string) *Request {
|
|
r.header.Add(k, v)
|
|
return r
|
|
}
|
|
|
|
func (r *Request) SetHeader(h http.Header) *Request {
|
|
r.header = h
|
|
return r
|
|
}
|
|
|
|
func (r *Request) Do() (res *http.Response, err error) {
|
|
var s string
|
|
s = r.formData.Encode()
|
|
if len(s) > 0 {
|
|
r.body = s
|
|
}
|
|
r.url.RawQuery = r.query.Encode()
|
|
r.uri = r.url.String()
|
|
return r.client.execute(r)
|
|
}
|
|
|
|
func (r *Request) Response(v any) (err error) {
|
|
var (
|
|
res *http.Response
|
|
buf []byte
|
|
contentType string
|
|
)
|
|
if res, err = r.Do(); err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = res.Body.Close()
|
|
}()
|
|
if res.StatusCode/100 != 2 {
|
|
if buf, err = io.ReadAll(res.Body); err == nil && len(buf) > 0 {
|
|
err = fmt.Errorf("http response %s(%d): %s", res.Status, res.StatusCode, string(buf))
|
|
} else {
|
|
err = fmt.Errorf("http response %d: %s", res.StatusCode, res.Status)
|
|
}
|
|
return
|
|
}
|
|
contentType = strings.ToLower(res.Header.Get("Content-Type"))
|
|
extName := path.Ext(r.rawRequest.URL.String())
|
|
if strings.Contains(contentType, JSON) || extName == ".json" {
|
|
err = json.NewDecoder(res.Body).Decode(v)
|
|
} else if strings.Contains(contentType, XML) || extName == ".xml" {
|
|
err = xml.NewDecoder(res.Body).Decode(v)
|
|
} else {
|
|
err = fmt.Errorf("unsupported content type: %s", contentType)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *Request) Download(s string) (err error) {
|
|
var (
|
|
fp *os.File
|
|
res *http.Response
|
|
)
|
|
if res, err = r.Do(); err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = res.Body.Close()
|
|
}()
|
|
if fp, err = os.OpenFile(s, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = fp.Close()
|
|
}()
|
|
_, err = io.Copy(fp, res.Body)
|
|
return
|
|
}
|
|
|
|
func newRequest(method string, uri string, client *Client) *Request {
|
|
var (
|
|
err error
|
|
)
|
|
r := &Request{
|
|
context: context.Background(),
|
|
method: method,
|
|
uri: uri,
|
|
header: make(http.Header),
|
|
formData: make(url.Values),
|
|
client: client,
|
|
}
|
|
if r.url, err = url.Parse(uri); err == nil {
|
|
r.query = r.url.Query()
|
|
} else {
|
|
r.query = make(url.Values)
|
|
}
|
|
return r
|
|
}
|