|
- // Copyright 2025 The HuaTuo Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package request
-
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "runtime"
- "strings"
- "time"
-
- "github.com/pkg/errors"
- )
-
- const (
- defaultReqTimeout = 10 * time.Second
- scheme = "http"
- respBodyMax = 1 * 1024 * 1024 // 1 MiB
- )
-
- // ServerResponse is a wrapper for http API responses
- type ServerResponse struct {
- Body io.ReadCloser
- Header http.Header
- StatusCode int
- ReqURL *url.URL
- }
-
- // ErrorResponse Represents an error.
- type ErrorResponse struct {
- // The error message.
- Message string `json:"message"`
- }
-
- // Close to close response.Body, it will be automatically released even if it is not actively called
- func (sresp *ServerResponse) Close() {
- if sresp.Body == nil {
- return
- }
-
- sresp.Body.Close()
- sresp.Body = nil
- }
-
- func buildRequest(host, method, path string, body io.Reader, headers http.Header) (*http.Request, error) {
- req, err := http.NewRequest(method, path, body)
- if err != nil {
- return nil, err
- }
-
- if headers != nil {
- req.Header = headers.Clone()
- }
-
- // add authorization to header
- req.URL.Host = host
- req.URL.Scheme = scheme
-
- return req, nil
- }
-
- func doRequest(req *http.Request) (*ServerResponse, error) {
- serverResp := &ServerResponse{StatusCode: -1, ReqURL: req.URL}
- defer runtime.SetFinalizer(serverResp, (*ServerResponse).Close)
-
- client := &http.Client{
- Timeout: defaultReqTimeout,
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return serverResp, err
- }
- defer resp.Body.Close()
-
- serverResp.StatusCode = resp.StatusCode
- serverResp.Body = resp.Body
- serverResp.Header = resp.Header
-
- return serverResp, nil
- }
-
- func sendRequest(host, method, path string, query url.Values, body io.Reader, headers http.Header) (*ServerResponse, error) {
- p := (&url.URL{Path: path, RawQuery: query.Encode()}).String()
- req, err := buildRequest(host, method, p, body, headers)
- if err != nil {
- return nil, err
- }
-
- resp, err := doRequest(req)
- if err != nil {
- return resp, err
- }
-
- if err = checkResponseErr(resp); err != nil {
- return resp, err
- }
-
- return resp, nil
- }
-
- func encodeData(data any) (*bytes.Buffer, error) {
- params := bytes.NewBuffer(nil)
- if data != nil {
- if err := json.NewEncoder(params).Encode(data); err != nil {
- return nil, err
- }
- }
-
- return params, nil
- }
-
- func encodeBody(obj any, headers http.Header) (io.Reader, http.Header, error) {
- if obj == nil {
- return nil, headers, nil
- }
- body, err := encodeData(obj)
- if err != nil {
- return nil, headers, err
- }
-
- if headers == nil {
- headers = http.Header{}
- }
- headers.Set("Content-Type", "application/json")
-
- return body, headers, nil
- }
-
- func checkResponseErr(serverResp *ServerResponse) error {
- if serverResp.StatusCode >= http.StatusOK && serverResp.StatusCode < http.StatusBadRequest {
- return nil
- }
-
- var body []byte
- var err error
- if serverResp.Body != nil {
- bodyR := &io.LimitedReader{
- R: serverResp.Body,
- N: int64(respBodyMax),
- }
- body, err = io.ReadAll(bodyR)
- if err != nil {
- return err
- }
- if bodyR.N == 0 {
- return fmt.Errorf(`request returned %s with a message (> %d bytes) for API route and version %s,
- check if the server supports the requested API version`,
- http.StatusText(serverResp.StatusCode), respBodyMax, serverResp.ReqURL)
- }
- }
- if len(body) == 0 {
- return fmt.Errorf(`request returned %s for API route and version %s,
- check if the server supports the requested API version`,
- http.StatusText(serverResp.StatusCode), serverResp.ReqURL)
- }
-
- var contentType string
- if serverResp.Header != nil {
- contentType = serverResp.Header.Get("Content-Type")
- }
-
- var errorMessage string
- var errResponse ErrorResponse
- if !strings.Contains(contentType, "application/json") {
- errorMessage = strings.TrimSpace(string(body))
- return errors.Wrap(errors.New(errorMessage), "Error response from HuaTuo")
- }
-
- if err := json.Unmarshal(body, &errResponse); err != nil {
- errorMessage = strings.TrimSpace("unmarshal errResponse to JSON failed, output body: " + string(body))
- } else {
- errorMessage = strings.TrimSpace(errResponse.Message)
- }
-
- return errors.Wrap(errors.New(errorMessage), "Error response from HuaTuo")
- }
-
- // HTTPGet Get sends an http request to the huatuo API using the method GET
- func HTTPGet(host, path string, query url.Values, headers http.Header) (*ServerResponse, error) {
- return sendRequest(host, http.MethodGet, path, query, nil, headers)
- }
-
- // HTTPPut Put sends an http request to the huatuo API using the method PUT
- func HTTPPut(host, path string, query url.Values, obj any, headers http.Header) (*ServerResponse, error) {
- body, headers, err := encodeBody(obj, headers)
- if err != nil {
- return nil, err
- }
-
- return sendRequest(host, http.MethodPut, path, query, body, headers)
- }
-
- // HTTPPost Post sends an http request to the huatuo API using the method POST
- func HTTPPost(host, path string, query url.Values, obj any, headers http.Header) (*ServerResponse, error) {
- body, headers, err := encodeBody(obj, headers)
- if err != nil {
- return nil, err
- }
-
- return sendRequest(host, http.MethodPost, path, query, body, headers)
- }
-
- // HTTPDelete Delete sends an http request to the huatuo API using the method DELETE
- func HTTPDelete(host, path string, query url.Values, headers http.Header) (*ServerResponse, error) {
- return sendRequest(host, http.MethodDelete, path, query, nil, headers)
- }
-
- // HTTPErrorMesg deal with http error
- func HTTPErrorMesg(body io.ReadCloser) string {
- resp := ErrorResponse{}
- text, _ := io.ReadAll(body)
- err := json.Unmarshal(text, &resp)
- if err != nil {
- fmt.Println("error message json.Unmarshal err: ", err)
- }
- return resp.Message
- }
|