package http import ( "bytes" "context" "encoding/base64" "encoding/json" "errors" "fmt" "io" "io/ioutil" "mime/multipart" "net/http" "reflect" "time" ) var ( client = http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ MaxIdleConnsPerHost: 5, MaxConnsPerHost: 100, }, } ) type ByteSize int64 const ( _ = iota // ignore first value by assigning to blank identifier KB ByteSize = 1 << (10 * iota) MB ) var ( outOfBodySizeErr = errors.New("out of body size limit") ) type ResponseBody struct { StatusCode int Data []byte } type RequestFormData map[string]string // Post // Note: 只会读取小于1M的数据 func Post(url string, header map[string]string, body io.Reader) (fr *ResponseBody, err error) { fr, err = do(http.MethodPost, url, header, body, int64(1024*MB)) return } // PostFormData 提交标准的form表单 func PostFormData(url string, fromdata RequestFormData) (fr *ResponseBody, err error) { var b bytes.Buffer writer := multipart.NewWriter(&b) for k, v := range fromdata { if err = writer.WriteField(k, v); err != nil { return } } if err = writer.Close(); err != nil { return } header := map[string]string{"Content-Type": writer.FormDataContentType()} fr, err = do(http.MethodPost, url, header, &b, 2*int64(KB)) return } // Get // Note: 只会读取小于1M的数据 func Get(url string, header map[string]string) (fr *ResponseBody, err error) { fr, err = do(http.MethodGet, url, header, nil, int64(KB)) return } func GetWithBody(url string, header map[string]string, body io.Reader) (fr *ResponseBody, err error) { fr, err = do(http.MethodGet, url, header, body, int64(20*MB)) return } // GetLargeFile 获取大文件。2M func GetLargeFile(url string, header map[string]string) (fr *ResponseBody, err error) { fr, err = do(http.MethodGet, url, header, nil, int64(20*MB)) return } func do(method string, url string, header map[string]string, body io.Reader, size int64) (fr *ResponseBody, err error) { req, err := http.NewRequest(method, url, body) if err != nil { return } for k, v := range header { req.Header.Set(k, v) } resp, err := client.Do(req) if err != nil { return } defer resp.Body.Close() if resp.ContentLength > size { err = outOfBodySizeErr return } data, err := ioutil.ReadAll(io.LimitReader(resp.Body, size)) if err != nil { return } fr = &ResponseBody{ StatusCode: resp.StatusCode, Data: data, } return } // 推送对象到文件服务器 func PostObject(ctx context.Context, url string, header map[string]string, body []byte, objName string) (string, error) { base64data := base64.StdEncoding.EncodeToString(body) req := struct { Name string `json:"fileName"` Data string `json:"fileData"` }{ Name: objName, Data: base64data, } jsonBytes, _ := json.Marshal(req) resp, err := Post(url, header, bytes.NewReader(jsonBytes)) if err != nil { return "", err } type Obj struct { Domain string `json:"domain"` Name string `json:"fileName"` } fsResp := struct { Code int `json:"code"` Object Obj `json:"data"` Msg string `json:"msg"` }{} if err = json.Unmarshal(resp.Data, &fsResp); err != nil { return "", err } if fsResp.Code != 200 || fsResp.Object.Name == "" { return "", fmt.Errorf("上传fs失败") } return fsResp.Object.Name, nil } func PostWithBind(ctx context.Context, bind interface{}, url string, body io.Reader) error { if err := verifyBind(bind); err != nil { return err } req, err := http.NewRequestWithContext(ctx, "POST", url, body) if err != nil { return err } req.Header.Add("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { return err } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("response status code:%d", resp.StatusCode) } data, err := ioutil.ReadAll(resp.Body) if err != nil { return err } return json.Unmarshal(data, bind) } func verifyBind(bind interface{}) error { bType := reflect.TypeOf(bind) if bType.Kind() != reflect.Ptr { return errors.New("bind must be a Ptr") } return nil }