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.

http.go 10 kB

1 year ago
2 years ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. package http
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "mime"
  7. "mime/multipart"
  8. "net/http"
  9. "net/textproto"
  10. ul "net/url"
  11. "reflect"
  12. "strings"
  13. "github.com/mitchellh/mapstructure"
  14. "gitlink.org.cn/cloudream/common/pkgs/iterator"
  15. "gitlink.org.cn/cloudream/common/utils/math2"
  16. "gitlink.org.cn/cloudream/common/utils/serder"
  17. )
  18. const (
  19. ContentTypeJSON = "application/json"
  20. ContentTypeForm = "application/x-www-form-urlencoded"
  21. ContentTypeMultiPart = "multipart/form-data"
  22. ContentTypeOctetStream = "application/octet-stream"
  23. )
  24. type RequestParam struct {
  25. Header any
  26. Query any
  27. Body any
  28. }
  29. func GetJSON(url string, param RequestParam) (*http.Response, error) {
  30. req, err := http.NewRequest(http.MethodGet, url, nil)
  31. if err != nil {
  32. return nil, err
  33. }
  34. if err = prepareQuery(req, param.Query); err != nil {
  35. return nil, err
  36. }
  37. if err = prepareHeader(req, param.Header); err != nil {
  38. return nil, err
  39. }
  40. if err = prepareJSONBody(req, param.Body); err != nil {
  41. return nil, err
  42. }
  43. return http.DefaultClient.Do(req)
  44. }
  45. func GetForm(url string, param RequestParam) (*http.Response, error) {
  46. req, err := http.NewRequest(http.MethodGet, url, nil)
  47. if err != nil {
  48. return nil, err
  49. }
  50. if err = prepareQuery(req, param.Query); err != nil {
  51. return nil, err
  52. }
  53. if err = prepareHeader(req, param.Header); err != nil {
  54. return nil, err
  55. }
  56. if err = prepareFormBody(req, param.Body); err != nil {
  57. return nil, err
  58. }
  59. return http.DefaultClient.Do(req)
  60. }
  61. func PostJSON(url string, param RequestParam) (*http.Response, error) {
  62. req, err := http.NewRequest(http.MethodPost, url, nil)
  63. if err != nil {
  64. return nil, err
  65. }
  66. if err = prepareQuery(req, param.Query); err != nil {
  67. return nil, err
  68. }
  69. if err = prepareHeader(req, param.Header); err != nil {
  70. return nil, err
  71. }
  72. if err = prepareJSONBody(req, param.Body); err != nil {
  73. return nil, err
  74. }
  75. return http.DefaultClient.Do(req)
  76. }
  77. func PostForm(url string, param RequestParam) (*http.Response, error) {
  78. req, err := http.NewRequest(http.MethodPost, url, nil)
  79. if err != nil {
  80. return nil, err
  81. }
  82. if err = prepareQuery(req, param.Query); err != nil {
  83. return nil, err
  84. }
  85. if err = prepareHeader(req, param.Header); err != nil {
  86. return nil, err
  87. }
  88. if err = prepareFormBody(req, param.Body); err != nil {
  89. return nil, err
  90. }
  91. return http.DefaultClient.Do(req)
  92. }
  93. func ParseJSONResponse[TBody any](resp *http.Response) (TBody, error) {
  94. var ret TBody
  95. contType := resp.Header.Get("Content-Type")
  96. if strings.Contains(contType, ContentTypeJSON) {
  97. if err := serder.JSONToObjectStream(resp.Body, &ret); err != nil {
  98. return ret, fmt.Errorf("parsing response: %w", err)
  99. }
  100. return ret, nil
  101. }
  102. cont, err := io.ReadAll(resp.Body)
  103. if err != nil {
  104. return ret, fmt.Errorf("unknow response content type: %s, status: %d", contType, resp.StatusCode)
  105. }
  106. strCont := string(cont)
  107. return ret, fmt.Errorf("unknow response content type: %s, status: %d, body(prefix): %s", contType, resp.StatusCode, strCont[:math2.Min(len(strCont), 200)])
  108. }
  109. type MultiPartFile struct {
  110. FieldName string
  111. FileName string
  112. File io.ReadCloser
  113. Header textproto.MIMEHeader
  114. }
  115. type multiPartFileIterator struct {
  116. mr *multipart.Reader
  117. firstFile *multipart.Part
  118. }
  119. func (m *multiPartFileIterator) MoveNext() (*MultiPartFile, error) {
  120. if m.firstFile != nil {
  121. f := m.firstFile
  122. m.firstFile = nil
  123. fileName, err := ul.PathUnescape(f.FileName())
  124. if err != nil {
  125. return nil, fmt.Errorf("unescape file name: %w", err)
  126. }
  127. return &MultiPartFile{
  128. FieldName: f.FormName(),
  129. FileName: fileName,
  130. File: f,
  131. Header: f.Header,
  132. }, nil
  133. }
  134. for {
  135. part, err := m.mr.NextPart()
  136. if err == io.EOF {
  137. return nil, iterator.ErrNoMoreItem
  138. }
  139. if err != nil {
  140. return nil, err
  141. }
  142. fileName, err := ul.PathUnescape(part.FileName())
  143. if err != nil {
  144. return nil, fmt.Errorf("unescape file name: %w", err)
  145. }
  146. if part.FileName() != "" {
  147. return &MultiPartFile{
  148. FieldName: part.FormName(),
  149. FileName: fileName,
  150. File: part,
  151. Header: part.Header,
  152. }, nil
  153. }
  154. }
  155. }
  156. func (m *multiPartFileIterator) Close() {}
  157. // 解析multipart/form-data响应,只支持form参数在头部的情况
  158. func ParseMultiPartResponse(resp *http.Response) (map[string]string, iterator.Iterator[*MultiPartFile], error) {
  159. mtype, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
  160. if err != nil {
  161. return nil, nil, fmt.Errorf("parse content type: %w", err)
  162. }
  163. if mtype != ContentTypeMultiPart {
  164. return nil, nil, fmt.Errorf("unknow content type: %s", mtype)
  165. }
  166. boundary := params["boundary"]
  167. if boundary == "" {
  168. return nil, nil, fmt.Errorf("no boundary in content type: %s", mtype)
  169. }
  170. formValues := make(map[string]string)
  171. rd := multipart.NewReader(resp.Body, boundary)
  172. var firstFile *multipart.Part
  173. for {
  174. part, err := rd.NextPart()
  175. if err == io.EOF {
  176. return formValues, iterator.Empty[*MultiPartFile](), nil
  177. }
  178. if err != nil {
  179. return nil, nil, err
  180. }
  181. formName := part.FormName()
  182. fileName := part.FileName()
  183. if formName == "" {
  184. continue
  185. }
  186. if fileName != "" {
  187. firstFile = part
  188. break
  189. }
  190. data, err := io.ReadAll(part)
  191. if err != nil {
  192. return nil, nil, err
  193. }
  194. formValues[formName] = string(data)
  195. }
  196. return formValues, &multiPartFileIterator{
  197. mr: rd,
  198. firstFile: firstFile,
  199. }, nil
  200. }
  201. type MultiPartRequestParam struct {
  202. Header any
  203. Query any
  204. Form any
  205. Files MultiPartFileIterator
  206. }
  207. type MultiPartFileIterator = iterator.Iterator[*IterMultiPartFile]
  208. type IterMultiPartFile struct {
  209. FieldName string // 这个文件所属的form字段
  210. FileName string // 文件名
  211. File io.ReadCloser
  212. }
  213. func PostMultiPart(url string, param MultiPartRequestParam) (*http.Response, error) {
  214. req, err := http.NewRequest(http.MethodPost, url, nil)
  215. if err != nil {
  216. return nil, err
  217. }
  218. if err = prepareQuery(req, param.Query); err != nil {
  219. return nil, err
  220. }
  221. if err = prepareHeader(req, param.Header); err != nil {
  222. return nil, err
  223. }
  224. pr, pw := io.Pipe()
  225. muWriter := multipart.NewWriter(pw)
  226. setHeader(req.Header, "Content-Type", fmt.Sprintf("%s;boundary=%s", ContentTypeMultiPart, muWriter.Boundary()))
  227. writeResult := make(chan error, 1)
  228. go func() {
  229. writeResult <- func() error {
  230. defer pw.Close()
  231. defer muWriter.Close()
  232. if param.Form != nil {
  233. mp, err := objectToStringMap(param.Form)
  234. if err != nil {
  235. return fmt.Errorf("formValues object to map failed, err: %w", err)
  236. }
  237. for k, v := range mp {
  238. err := muWriter.WriteField(k, v)
  239. if err != nil {
  240. return fmt.Errorf("write form field failed, err: %w", err)
  241. }
  242. }
  243. }
  244. for {
  245. file, err := param.Files.MoveNext()
  246. if err == iterator.ErrNoMoreItem {
  247. break
  248. }
  249. if err != nil {
  250. return fmt.Errorf("opening file: %w", err)
  251. }
  252. err = func() error {
  253. defer file.File.Close()
  254. w, err := muWriter.CreateFormFile(file.FieldName, ul.PathEscape(file.FileName))
  255. if err != nil {
  256. return fmt.Errorf("create form file failed, err: %w", err)
  257. }
  258. _, err = io.Copy(w, file.File)
  259. if err != nil {
  260. return err
  261. }
  262. return nil
  263. }()
  264. if err != nil {
  265. return err
  266. }
  267. }
  268. return nil
  269. }()
  270. }()
  271. req.Body = pr
  272. cli := http.Client{}
  273. resp, err := cli.Do(req)
  274. if err != nil {
  275. return nil, err
  276. }
  277. writeErr := <-writeResult
  278. if writeErr != nil {
  279. return nil, writeErr
  280. }
  281. return resp, nil
  282. }
  283. func prepareQuery(req *http.Request, query any) error {
  284. if query == nil {
  285. return nil
  286. }
  287. mp, ok := query.(map[string]string)
  288. if !ok {
  289. var err error
  290. if mp, err = objectToStringMap(query); err != nil {
  291. return fmt.Errorf("query object to map: %w", err)
  292. }
  293. }
  294. values := make(ul.Values)
  295. for k, v := range mp {
  296. values.Add(k, v)
  297. }
  298. req.URL.RawQuery = values.Encode()
  299. return nil
  300. }
  301. func prepareHeader(req *http.Request, header any) error {
  302. if header == nil {
  303. return nil
  304. }
  305. mp, ok := header.(map[string]string)
  306. if !ok {
  307. var err error
  308. if mp, err = objectToStringMap(header); err != nil {
  309. return fmt.Errorf("header object to map: %w", err)
  310. }
  311. }
  312. req.Header = make(http.Header)
  313. for k, v := range mp {
  314. req.Header.Set(k, v)
  315. }
  316. return nil
  317. }
  318. func prepareJSONBody(req *http.Request, body any) error {
  319. setHeader(req.Header, "Content-Type", ContentTypeJSON)
  320. if body == nil {
  321. return nil
  322. }
  323. data, err := serder.ObjectToJSON(body)
  324. if err != nil {
  325. return err
  326. }
  327. req.ContentLength = int64(len(data))
  328. req.Body = io.NopCloser(bytes.NewReader(data))
  329. return nil
  330. }
  331. func prepareFormBody(req *http.Request, body any) error {
  332. setHeader(req.Header, "Content-Type", ContentTypeForm)
  333. if body == nil {
  334. return nil
  335. }
  336. mp, ok := body.(map[string]string)
  337. if !ok {
  338. var err error
  339. if mp, err = objectToStringMap(body); err != nil {
  340. return fmt.Errorf("body object to map: %w", err)
  341. }
  342. }
  343. values := make(ul.Values)
  344. for k, v := range mp {
  345. values.Add(k, v)
  346. }
  347. data := values.Encode()
  348. req.Body = io.NopCloser(strings.NewReader(data))
  349. req.ContentLength = int64(len(data))
  350. return nil
  351. }
  352. func setHeader(mp http.Header, key, value string) http.Header {
  353. if mp == nil {
  354. mp = make(http.Header)
  355. }
  356. mp.Set(key, value)
  357. return mp
  358. }
  359. func setValue(values ul.Values, key, value string) ul.Values {
  360. if values == nil {
  361. values = make(ul.Values)
  362. }
  363. values.Add(key, value)
  364. return values
  365. }
  366. func objectToStringMap(obj any) (map[string]string, error) {
  367. anyMap := make(map[string]any)
  368. dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
  369. TagName: "json",
  370. Result: &anyMap,
  371. WeaklyTypedInput: true,
  372. })
  373. if err != nil {
  374. return nil, err
  375. }
  376. err = dec.Decode(obj)
  377. if err != nil {
  378. return nil, err
  379. }
  380. ret := make(map[string]string)
  381. for k, v := range anyMap {
  382. val := reflect.ValueOf(v)
  383. for val.Kind() == reflect.Ptr {
  384. if val.IsNil() {
  385. break
  386. } else {
  387. val = val.Elem()
  388. }
  389. }
  390. if val.Kind() == reflect.Pointer {
  391. ret[k] = ""
  392. } else {
  393. ret[k] = fmt.Sprintf("%v", val)
  394. }
  395. }
  396. return ret, nil
  397. }