You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

sdks.go 7.7 kB

4 months ago
4 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. package sdks
  2. import (
  3. "bytes"
  4. "crypto/sha256"
  5. "fmt"
  6. "io"
  7. "mime"
  8. "mime/multipart"
  9. "net/http"
  10. "net/url"
  11. "strings"
  12. "github.com/google/go-querystring/query"
  13. "gitlink.org.cn/cloudream/common/consts/errorcode"
  14. "gitlink.org.cn/cloudream/common/pkgs/iterator"
  15. "gitlink.org.cn/cloudream/common/utils/http2"
  16. "gitlink.org.cn/cloudream/common/utils/io2"
  17. "gitlink.org.cn/cloudream/common/utils/serder"
  18. )
  19. type CodeMessageError struct {
  20. Code string
  21. Message string
  22. }
  23. func (e *CodeMessageError) Error() string {
  24. return fmt.Sprintf("code: %s, message: %s", e.Code, e.Message)
  25. }
  26. type RequestParam struct {
  27. Method string
  28. Path string
  29. Query url.Values
  30. Header http.Header
  31. Body RequestBody
  32. }
  33. func (p *RequestParam) MakeRequest(baseURL string) (*http.Request, error) {
  34. var body io.ReadCloser
  35. bodyLen := int64(0)
  36. if p.Body != nil {
  37. body = p.Body.IntoStream()
  38. bodyLen = p.Body.Length()
  39. }
  40. req, err := http.NewRequest(p.Method, joinUrlUnsafe(baseURL, p.Path), body)
  41. if err != nil {
  42. return nil, err
  43. }
  44. req.ContentLength = bodyLen
  45. req.URL.RawQuery = p.Query.Encode()
  46. if p.Header != nil {
  47. req.Header = p.Header
  48. }
  49. return req, nil
  50. }
  51. func (p *RequestParam) Do(baseURL string, cli *http.Client) (*http.Response, error) {
  52. req, err := p.MakeRequest(baseURL)
  53. if err != nil {
  54. return nil, err
  55. }
  56. return cli.Do(req)
  57. }
  58. func joinUrlUnsafe(base string, path string) string {
  59. if strings.HasSuffix(base, "/") {
  60. if strings.HasPrefix(path, "/") {
  61. return base + path[1:]
  62. }
  63. return base + path
  64. }
  65. if strings.HasPrefix(path, "/") {
  66. return base + path
  67. }
  68. return base + "/" + path
  69. }
  70. type RequestBody interface {
  71. // 请求体长度,如果长度未知,返回-1
  72. Length() int64
  73. // 将内部值变成一个流,用于发送请求
  74. IntoStream() io.ReadCloser
  75. // 计算Sha256哈希,如果不方便计算,则返回nil
  76. Hash() []byte
  77. }
  78. type StringBody struct {
  79. Value string
  80. }
  81. func (s *StringBody) Length() int64 {
  82. return int64(len(s.Value))
  83. }
  84. func (s *StringBody) IntoStream() io.ReadCloser {
  85. return io.NopCloser(bytes.NewReader([]byte(s.Value)))
  86. }
  87. func (s *StringBody) Hash() []byte {
  88. hash := sha256.Sum256([]byte(s.Value))
  89. return hash[:]
  90. }
  91. type BytesBody struct {
  92. Value []byte
  93. }
  94. func (b *BytesBody) Length() int64 {
  95. return int64(len(b.Value))
  96. }
  97. func (b *BytesBody) IntoStream() io.ReadCloser {
  98. return io.NopCloser(bytes.NewReader(b.Value))
  99. }
  100. func (b *BytesBody) Hash() []byte {
  101. hash := sha256.Sum256(b.Value)
  102. return hash[:]
  103. }
  104. type StreamBody struct {
  105. Stream io.ReadCloser
  106. LengthHint int64 // 长度提示,如果长度未知,可以设置为-1
  107. }
  108. func (s *StreamBody) Length() int64 {
  109. return s.LengthHint
  110. }
  111. func (s *StreamBody) IntoStream() io.ReadCloser {
  112. return s.Stream
  113. }
  114. func (s *StreamBody) Hash() []byte {
  115. return nil
  116. }
  117. type APIRequest interface {
  118. MakeParam() *RequestParam
  119. }
  120. func MakeJSONParam(method string, path string, body any) *RequestParam {
  121. data, err := serder.ObjectToJSONEx(body)
  122. if err != nil {
  123. // 开发人员应该保证param是可序列化的
  124. panic(err)
  125. }
  126. return &RequestParam{
  127. Method: method,
  128. Path: path,
  129. Body: &BytesBody{Value: data},
  130. }
  131. }
  132. func MakeQueryParam(method string, path string, q any) *RequestParam {
  133. values, err := query.Values(q)
  134. if err != nil {
  135. // 开发人员应该保证param是可序列化的
  136. panic(err)
  137. }
  138. return &RequestParam{
  139. Method: method,
  140. Path: path,
  141. Query: values,
  142. }
  143. }
  144. type UploadFileInfo struct {
  145. FileName string
  146. File io.ReadCloser
  147. }
  148. func MakeUploadParam(method string, path string, info any, file UploadFileInfo) *RequestParam {
  149. data, err := serder.ObjectToJSONEx(info)
  150. if err != nil {
  151. // 开发人员应该保证param是可序列化的
  152. panic(err)
  153. }
  154. pr, pw := io.Pipe()
  155. mw := multipart.NewWriter(pw)
  156. go func() {
  157. defer mw.Close()
  158. err := mw.WriteField("info", string(data))
  159. if err != nil {
  160. pw.CloseWithError(err)
  161. return
  162. }
  163. fw, err := mw.CreateFormFile("file", file.FileName)
  164. if err != nil {
  165. pw.CloseWithError(err)
  166. return
  167. }
  168. _, err = io.Copy(fw, file.File)
  169. if err != nil {
  170. pw.CloseWithError(err)
  171. return
  172. }
  173. }()
  174. headers := http.Header{}
  175. headers.Set("Content-Type", fmt.Sprintf("%s;boundary=%s", http2.ContentTypeMultiPart, mw.Boundary()))
  176. return &RequestParam{
  177. Method: method,
  178. Path: path,
  179. Header: headers,
  180. Body: &StreamBody{
  181. Stream: pr,
  182. LengthHint: -1,
  183. },
  184. }
  185. }
  186. type MultiUploadIter = iterator.Iterator[UploadFileInfo]
  187. func MakeMultiUploadParam(method string, path string, info any, files MultiUploadIter) *RequestParam {
  188. data, err := serder.ObjectToJSONEx(info)
  189. if err != nil {
  190. // 开发人员应该保证param是可序列化的
  191. panic(err)
  192. }
  193. pr, pw := io.Pipe()
  194. mw := multipart.NewWriter(pw)
  195. go func() {
  196. defer mw.Close()
  197. err := mw.WriteField("info", string(data))
  198. if err != nil {
  199. pw.CloseWithError(err)
  200. return
  201. }
  202. for {
  203. file, err := files.MoveNext()
  204. if err == iterator.ErrNoMoreItem {
  205. break
  206. }
  207. if err != nil {
  208. pw.CloseWithError(fmt.Errorf("opening file: %w", err))
  209. return
  210. }
  211. w, err := mw.CreateFormFile("files", url.PathEscape(file.FileName))
  212. if err != nil {
  213. file.File.Close()
  214. pw.CloseWithError(fmt.Errorf("create form file failed, err: %w", err))
  215. return
  216. }
  217. _, err = io.Copy(w, file.File)
  218. if err != nil {
  219. file.File.Close()
  220. pw.CloseWithError(err)
  221. return
  222. }
  223. file.File.Close()
  224. }
  225. }()
  226. headers := http.Header{}
  227. headers.Set("Content-Type", fmt.Sprintf("%s;boundary=%s", http2.ContentTypeMultiPart, mw.Boundary()))
  228. return &RequestParam{
  229. Method: method,
  230. Path: path,
  231. Header: headers,
  232. Body: &StreamBody{
  233. Stream: pr,
  234. LengthHint: -1,
  235. },
  236. }
  237. }
  238. type APIResponse interface {
  239. ParseResponse(resp *http.Response) error
  240. }
  241. type CodeDataResponse[T any] struct {
  242. Code string `json:"code"`
  243. Message string `json:"message"`
  244. Data T `json:"data"`
  245. }
  246. func (r *CodeDataResponse[T]) Unwarp() (T, error) {
  247. if r.Code == errorcode.OK {
  248. return r.Data, nil
  249. }
  250. var def T
  251. return def, &CodeMessageError{Code: r.Code, Message: r.Message}
  252. }
  253. func ParseCodeDataJSONResponse[T any](resp *http.Response, ret T) error {
  254. contType := resp.Header.Get("Content-Type")
  255. if strings.Contains(contType, http2.ContentTypeJSON) {
  256. var err error
  257. var r CodeDataResponse[T]
  258. r.Data = ret
  259. if err = serder.JSONToObjectStreamExRaw(resp.Body, &r); err != nil {
  260. return fmt.Errorf("parsing response: %w", err)
  261. }
  262. if r.Code != errorcode.OK {
  263. return &CodeMessageError{Code: r.Code, Message: r.Message}
  264. }
  265. return nil
  266. }
  267. cont, err := io2.ReadMost(resp.Body, 200)
  268. if err != nil {
  269. return fmt.Errorf("unknow response content type: %s, status: %d", contType, resp.StatusCode)
  270. }
  271. strCont := string(cont)
  272. return fmt.Errorf("unknow response content type: %s, status: %d, body[:200]: %s", contType, resp.StatusCode, strCont)
  273. }
  274. func ParseFileResponse(resp *http.Response) (string, io.ReadCloser, error) {
  275. contType := resp.Header.Get("Content-Type")
  276. if strings.Contains(contType, http2.ContentTypeJSON) {
  277. var err error
  278. var r CodeDataResponse[any]
  279. if err = serder.JSONToObjectStreamExRaw(resp.Body, &r); err != nil {
  280. return "", nil, fmt.Errorf("parsing response: %w", err)
  281. }
  282. return "", nil, &CodeMessageError{Code: r.Code, Message: r.Message}
  283. }
  284. _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
  285. if err != nil {
  286. return "", nil, fmt.Errorf("parsing content disposition: %w", err)
  287. }
  288. return params["filename"], resp.Body, nil
  289. }
  290. func IsErrorCode(err error, code string) bool {
  291. if e, ok := err.(*CodeMessageError); ok {
  292. return e.Code == code
  293. }
  294. return false
  295. }