|
@@ -0,0 +1,230 @@
|
|
|
+package request
|
|
|
+
|
|
|
+import (
|
|
|
+ "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) (buf []byte, err error) {
|
|
|
+ var (
|
|
|
+ ok bool
|
|
|
+ s string
|
|
|
+ reader io.Reader
|
|
|
+ )
|
|
|
+ kind := reflect.Indirect(reflect.ValueOf(body)).Type().Kind()
|
|
|
+ if reader, ok = r.body.(io.Reader); ok {
|
|
|
+ buf, err = io.ReadAll(reader)
|
|
|
+ goto __end
|
|
|
+ }
|
|
|
+ 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:
|
|
|
+ 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
|
|
|
+}
|