|
- package sdks
-
- import (
- "bytes"
- "fmt"
- "io"
- "mime"
- "mime/multipart"
- "net/http"
- "net/url"
- "strings"
-
- "github.com/google/go-querystring/query"
- "gitlink.org.cn/cloudream/common/consts/errorcode"
- "gitlink.org.cn/cloudream/common/utils/http2"
- "gitlink.org.cn/cloudream/common/utils/io2"
- "gitlink.org.cn/cloudream/common/utils/serder"
- )
-
- type CodeMessageError struct {
- Code string
- Message string
- }
-
- func (e *CodeMessageError) Error() string {
- return fmt.Sprintf("code: %s, message: %s", e.Code, e.Message)
- }
-
- type RequestParam struct {
- Method string
- Path string
- Query url.Values
- Header http.Header
- Body RequestBody
- }
-
- func (p *RequestParam) MakeRequest(baseURL string) (*http.Request, error) {
- var body io.ReadCloser
- bodyLen := int64(0)
- if p.Body != nil {
- body = p.Body.IntoStream()
- bodyLen = p.Body.Length()
- }
-
- req, err := http.NewRequest(p.Method, joinUrlUnsafe(baseURL, p.Path), body)
- if err != nil {
- return nil, err
- }
- req.ContentLength = bodyLen
- req.URL.RawQuery = p.Query.Encode()
-
- if p.Header != nil {
- req.Header = p.Header
- }
-
- return req, nil
- }
-
- func (p *RequestParam) Do(baseURL string, cli *http.Client) (*http.Response, error) {
- req, err := p.MakeRequest(baseURL)
- if err != nil {
- return nil, err
- }
-
- return cli.Do(req)
- }
-
- func joinUrlUnsafe(base string, path string) string {
- if strings.HasSuffix(base, "/") {
- if strings.HasPrefix(path, "/") {
- return base + path[1:]
- }
-
- return base + path
- }
-
- if strings.HasPrefix(path, "/") {
- return base + path
- }
-
- return base + "/" + path
- }
-
- type RequestBody interface {
- // 请求体长度,如果长度未知,返回-1
- Length() int64
- // 将内部值变成一个流,用于发送请求
- IntoStream() io.ReadCloser
- }
-
- type StringBody struct {
- Value string
- }
-
- func (s *StringBody) Length() int64 {
- return int64(len(s.Value))
- }
-
- func (s *StringBody) IntoStream() io.ReadCloser {
- return io.NopCloser(bytes.NewReader([]byte(s.Value)))
- }
-
- type BytesBody struct {
- Value []byte
- }
-
- func (b *BytesBody) Length() int64 {
- return int64(len(b.Value))
- }
-
- func (b *BytesBody) IntoStream() io.ReadCloser {
- return io.NopCloser(bytes.NewReader(b.Value))
- }
-
- type StreamBody struct {
- Stream io.ReadCloser
- LengthHint int64 // 长度提示,如果长度未知,可以设置为-1
- }
-
- func (s *StreamBody) Length() int64 {
- return s.LengthHint
- }
-
- func (s *StreamBody) IntoStream() io.ReadCloser {
- return s.Stream
- }
-
- type APIRequest interface {
- MakeParam() *RequestParam
- }
-
- func MakeJSONParam(method string, path string, body any) *RequestParam {
- data, err := serder.ObjectToJSONEx(body)
- if err != nil {
- // 开发人员应该保证param是可序列化的
- panic(err)
- }
-
- return &RequestParam{
- Method: method,
- Path: path,
- Body: &BytesBody{Value: data},
- }
- }
-
- func MakeQueryParam(method string, path string, q any) *RequestParam {
- values, err := query.Values(q)
- if err != nil {
- // 开发人员应该保证param是可序列化的
- panic(err)
- }
-
- return &RequestParam{
- Method: method,
- Path: path,
- Query: values,
- }
- }
-
- func MakeMultipartParam(method string, path string, info any, stream io.ReadCloser) *RequestParam {
- data, err := serder.ObjectToJSONEx(info)
- if err != nil {
- // 开发人员应该保证param是可序列化的
- panic(err)
- }
-
- pr, pw := io.Pipe()
- mw := multipart.NewWriter(pw)
- go func() {
- defer mw.Close()
- err := mw.WriteField("info", string(data))
- if err != nil {
- pw.CloseWithError(err)
- return
- }
-
- fw, err := mw.CreateFormFile("file", "file")
- if err != nil {
- pw.CloseWithError(err)
- return
- }
-
- _, err = io.Copy(fw, stream)
- if err != nil {
- pw.CloseWithError(err)
- return
- }
- }()
-
- headers := http.Header{}
- headers.Set("Content-Type", fmt.Sprintf("%s;boundary=%s", http2.ContentTypeMultiPart, mw.Boundary()))
-
- return &RequestParam{
- Method: method,
- Path: path,
- Header: headers,
- Body: &StreamBody{
- Stream: pr,
- LengthHint: -1,
- },
- }
- }
-
- type APIResponse interface {
- ParseResponse(resp *http.Response) error
- }
-
- type CodeDataResponse[T any] struct {
- Code string `json:"code"`
- Message string `json:"message"`
- Data T `json:"data"`
- }
-
- func (r *CodeDataResponse[T]) Unwarp() (T, error) {
- if r.Code == errorcode.OK {
- return r.Data, nil
- }
- var def T
- return def, &CodeMessageError{Code: r.Code, Message: r.Message}
- }
-
- func ParseCodeDataJSONResponse[T any](resp *http.Response, ret T) error {
- contType := resp.Header.Get("Content-Type")
- if strings.Contains(contType, http2.ContentTypeJSON) {
- var err error
- var r CodeDataResponse[T]
- r.Data = ret
-
- if err = serder.JSONToObjectStreamExRaw(resp.Body, &r); err != nil {
- return fmt.Errorf("parsing response: %w", err)
- }
-
- if r.Code != errorcode.OK {
- return &CodeMessageError{Code: r.Code, Message: r.Message}
- }
-
- return nil
- }
-
- cont, err := io2.ReadMost(resp.Body, 200)
- if err != nil {
- return fmt.Errorf("unknow response content type: %s, status: %d", contType, resp.StatusCode)
- }
- strCont := string(cont)
-
- return fmt.Errorf("unknow response content type: %s, status: %d, body[:200]: %s", contType, resp.StatusCode, strCont)
- }
-
- func ParseFileResponse(resp *http.Response) (string, io.ReadCloser, error) {
- contType := resp.Header.Get("Content-Type")
- if strings.Contains(contType, http2.ContentTypeJSON) {
- var err error
- var r CodeDataResponse[any]
-
- if err = serder.JSONToObjectStreamExRaw(resp.Body, &r); err != nil {
- return "", nil, fmt.Errorf("parsing response: %w", err)
- }
-
- return "", nil, &CodeMessageError{Code: r.Code, Message: r.Message}
- }
-
- _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
- if err != nil {
- return "", nil, fmt.Errorf("parsing content disposition: %w", err)
- }
-
- return params["filename"], resp.Body, nil
- }
-
- func IsErrorCode(err error, code string) bool {
- if e, ok := err.(*CodeMessageError); ok {
- return e.Code == code
- }
-
- return false
- }
|