Files
MonkeyCode/backend/pkg/request/request.go
2025-07-23 19:26:40 +08:00

198 lines
4.0 KiB
Go

package request
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/uuid"
)
type Client struct {
scheme string
host string
client *http.Client
tr *http.Transport
debug bool
}
func NewClient(scheme string, host string, timeout time.Duration, opts ...ReqOpt) *Client {
req := &Client{
scheme: scheme,
host: host,
client: &http.Client{
Timeout: timeout,
},
debug: false,
}
for _, opt := range opts {
opt(req)
}
if req.tr != nil {
req.client.Transport = req.tr
}
return req
}
func (c *Client) SetDebug(debug bool) {
c.debug = debug
}
func (c *Client) SetTransport(tr *http.Transport) {
c.client.Transport = tr
}
func sendRequest[T any](c *Client, method, path string, opts ...Opt) (*T, error) {
u := url.URL{
Scheme: c.scheme,
Host: c.host,
Path: path,
}
ctx := &Ctx{}
rid := uuid.NewString()
for _, opt := range opts {
opt(ctx)
}
if len(ctx.query) > 0 {
values := u.Query()
for k, v := range ctx.query {
values.Add(k, v)
}
u.RawQuery = values.Encode()
}
if c.debug {
log.Printf("[REQ:%s] url: %s", rid, u.String())
}
var body io.Reader
var writer *multipart.Writer
if ctx.body != nil {
bs, err := json.Marshal(ctx.body)
if err != nil {
return nil, err
}
switch ctx.contentType {
case "multipart/form-data":
m := make(map[string]string)
if err := json.Unmarshal(bs, &m); err != nil {
return nil, err
}
buf := &bytes.Buffer{}
writer = multipart.NewWriter(buf)
for k, v := range m {
if err := writer.WriteField(k, v); err != nil {
return nil, err
}
}
writer.Close()
body = buf
case "application/x-www-form-urlencoded":
m := make(map[string]string)
if err := json.Unmarshal(bs, &m); err != nil {
return nil, err
}
data := url.Values{}
for k, v := range m {
data.Add(k, v)
}
body = strings.NewReader(data.Encode())
default:
body = bytes.NewBuffer(bs)
}
if c.debug {
buf := &bytes.Buffer{}
json.Indent(buf, bs, "", " ")
log.Printf("[REQ:%s] body: %s", rid, buf.String())
}
}
req, err := http.NewRequest(method, u.String(), body)
if err != nil {
return nil, err
}
for k, v := range ctx.header {
req.Header.Add(k, v)
}
switch ctx.contentType {
case "multipart/form-data":
req.Header.Set("Content-Type", writer.FormDataContentType())
case "application/x-www-form-urlencoded":
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
default:
req.Header.Set("Content-Type", "application/json")
}
if c.debug {
log.Printf("[REQ:%s] headers: %+v", rid, req.Header)
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if c.debug {
buf := &bytes.Buffer{}
if err := json.Indent(buf, b, "", " "); err != nil {
log.Printf("[REQ:%s] resp: %s", rid, string(b))
} else {
log.Printf("[REQ:%s] resp: %s", rid, buf.String())
}
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status code: %d", resp.StatusCode)
}
var rr T
if err := json.Unmarshal(b, &rr); err != nil {
return nil, err
}
return &rr, nil
}
func Get[T any](c *Client, path string, opts ...Opt) (*T, error) {
return sendRequest[T](c, http.MethodGet, path, opts...)
}
func Post[T any](c *Client, path string, body any, opts ...Opt) (*T, error) {
opts = append(opts, WithBody(body))
return sendRequest[T](c, http.MethodPost, path, opts...)
}
func Put[T any](c *Client, path string, body any, opts ...Opt) (*T, error) {
opts = append(opts, WithBody(body))
return sendRequest[T](c, http.MethodPut, path, opts...)
}
func Delete[T any](c *Client, path string, opts ...Opt) (*T, error) {
return sendRequest[T](c, http.MethodDelete, path, opts...)
}
func GetHeaderMap(header string) map[string]string {
headerMap := make(map[string]string)
for _, h := range strings.Split(header, "\n") {
if key, value, ok := strings.Cut(h, "="); ok {
headerMap[key] = value
}
}
return headerMap
}