package httputil import ( "bytes" "context" "encoding/json" "io" "mime/multipart" "net/http" "net/url" "os" "path/filepath" "strings" ) func ClientPost(connTimeout int, url string, headers map[string]string, data any) (*http.Response, error) { req, err := getPostRequest(url, headers, data) if err != nil { return nil, err } cl := getClientByTimeoutSet(connTimeout) return cl.Do(req) } func DefaultClientPost(url string, headers map[string]string, data any) (*http.Response, error) { return ClientPost(defaultTimeOut, url, headers, data) } func ClientPostWithCtx(ctx context.Context, connTimeout int, url string, headers map[string]string, data any) (*http.Response, error) { req, err := getPostRequest(url, headers, data) if err != nil { return nil, err } req = req.WithContext(ctx) cl := getClientByTimeoutSet(connTimeout) return cl.Do(req) } func DefaultClientPostWithCtx(ctx context.Context, url string, headers map[string]string, data any) (*http.Response, error) { return ClientPostWithCtx(ctx, defaultTimeOut, url, headers, data) } func ClientPostWithProxy(connTimeout int, p *ProxyCfg, url string, headers map[string]string, data any) (*http.Response, error) { if p == nil { return nil, errProxyNil } pUrl, pHeaders, err := p.Build(url, headers) if err != nil { return nil, err } return ClientPost(connTimeout, pUrl, pHeaders, data) } func ClientPostWithResp(connTimeout int, bind any, url string, headers map[string]string, data any) (int, error) { resp, err := ClientPost(connTimeout, url, headers, data) if err != nil { return 0, err } ct, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { return resp.StatusCode, err } return resp.StatusCode, json.Unmarshal(ct, &bind) } func DefaultClientPostWithResp(bind any, url string, headers map[string]string, data any) (int, error) { return ClientPostWithResp(defaultTimeOut, bind, url, headers, data) } func ClientPostWithRespWithCtx(ctx context.Context, connTimeout int, bind any, url string, headers map[string]string, data any) (int, error) { resp, err := ClientPostWithCtx(ctx, connTimeout, url, headers, data) if err != nil { return 0, err } ct, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { return resp.StatusCode, err } return resp.StatusCode, json.Unmarshal(ct, &bind) } func DefaultClientPostWithRespWithCtx(ctx context.Context, bind any, url string, headers map[string]string, data any) (int, error) { return ClientPostWithRespWithCtx(ctx, defaultTimeOut, bind, url, headers, data) } func ClientPostWithRespWithProxy(connTimeout int, p *ProxyCfg, bind any, url string, headers map[string]string, data any) (int, error) { resp, err := ClientPostWithProxy(connTimeout, p, url, headers, data) if err != nil { return 0, err } ct, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { return resp.StatusCode, err } return resp.StatusCode, json.Unmarshal(ct, &bind) } func DefaultClientPostWithRespWithProxy(p *ProxyCfg, bind any, url string, headers map[string]string, data any) (int, error) { return ClientPostWithRespWithProxy(defaultTimeOut, p, &bind, url, headers, data) } func getPostRequest(url string, headers map[string]string, data any) (*http.Request, error) { switch data.(type) { case []byte, string, *bytes.Reader, *bytes.Buffer: req, err := http.NewRequest(http.MethodPost, url, toReader(data)) if err != nil { return nil, err } for k, v := range headers { req.Header.Set(k, v) } return req, nil default: } paramsValues := toUrlValues(data) if checkParamFile(paramsValues) { body := &bytes.Buffer{} writer := multipart.NewWriter(body) for k, v := range paramsValues { for _, vv := range v { // is file if k[0] == '@' { if err := addFormFile(writer, k[1:], vv); err != nil { return nil, err } continue } _ = writer.WriteField(k, vv) } } req, err := http.NewRequest(http.MethodPost, url, body) if err != nil { return nil, err } for k, v := range headers { req.Header.Set(k, v) } req.Header.Set("Content-Type", writer.FormDataContentType()) if err = writer.Close(); err != nil { return nil, err } return req, nil } req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(paramsValues.Encode())) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") for k, v := range headers { req.Header.Set(k, v) } return req, nil } func toReader(v interface{}) *bytes.Reader { switch t := v.(type) { case []byte: return bytes.NewReader(t) case string: return bytes.NewReader([]byte(t)) case *bytes.Buffer: return bytes.NewReader(t.Bytes()) case *bytes.Reader: return t case nil: return bytes.NewReader(nil) default: panic("Invalid value") } } // Does the params contain a file? func checkParamFile(params url.Values) bool { for k := range params { if k[0] == '@' { return true } } return false } // Add a file to a multipart writer. func addFormFile(writer *multipart.Writer, name, path string) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() part, err := writer.CreateFormFile(name, filepath.Base(path)) if err != nil { return err } _, err = io.Copy(part, file) return err }