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

request.go 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package private
  4. import (
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "code.gitea.io/gitea/modules/httplib"
  9. "code.gitea.io/gitea/modules/json"
  10. )
  11. // responseText is used to get the response as text, instead of parsing it as JSON.
  12. type responseText struct {
  13. Text string
  14. }
  15. // ResponseExtra contains extra information about the response, especially for error responses.
  16. type ResponseExtra struct {
  17. StatusCode int
  18. UserMsg string
  19. Error error
  20. }
  21. type responseCallback struct {
  22. Callback func(resp *http.Response, extra *ResponseExtra)
  23. }
  24. func (re *ResponseExtra) HasError() bool {
  25. return re.Error != nil
  26. }
  27. type responseError struct {
  28. statusCode int
  29. errorString string
  30. }
  31. func (re responseError) Error() string {
  32. if re.errorString == "" {
  33. return fmt.Sprintf("internal API error response, status=%d", re.statusCode)
  34. }
  35. return fmt.Sprintf("internal API error response, status=%d, err=%s", re.statusCode, re.errorString)
  36. }
  37. // requestJSONResp sends a request to the gitea server and then parses the response.
  38. // If the status code is not 2xx, or any error occurs, the ResponseExtra.Error field is guaranteed to be non-nil,
  39. // and the ResponseExtra.UserMsg field will be set to a message for the end user.
  40. // Caller should check the ResponseExtra.HasError() first to see whether the request fails.
  41. //
  42. // * If the "res" is a struct pointer, the response will be parsed as JSON
  43. // * If the "res" is responseText pointer, the response will be stored as text in it
  44. // * If the "res" is responseCallback pointer, the callback function should set the ResponseExtra fields accordingly
  45. func requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra ResponseExtra) {
  46. resp, err := req.Response()
  47. if err != nil {
  48. extra.UserMsg = "Internal Server Connection Error"
  49. extra.Error = fmt.Errorf("unable to contact gitea %q: %w", req.GoString(), err)
  50. return nil, extra
  51. }
  52. defer resp.Body.Close()
  53. extra.StatusCode = resp.StatusCode
  54. // if the status code is not 2xx, try to parse the error response
  55. if resp.StatusCode/100 != 2 {
  56. var respErr Response
  57. if err := json.NewDecoder(resp.Body).Decode(&respErr); err != nil {
  58. extra.UserMsg = "Internal Server Error Decoding Failed"
  59. extra.Error = fmt.Errorf("unable to decode error response %q: %w", req.GoString(), err)
  60. return nil, extra
  61. }
  62. extra.UserMsg = respErr.UserMsg
  63. if extra.UserMsg == "" {
  64. extra.UserMsg = "Internal Server Error (no message for end users)"
  65. }
  66. extra.Error = responseError{statusCode: resp.StatusCode, errorString: respErr.Err}
  67. return res, extra
  68. }
  69. // now, the StatusCode must be 2xx
  70. var v any = res
  71. if respText, ok := v.(*responseText); ok {
  72. // get the whole response as a text string
  73. bs, err := io.ReadAll(resp.Body)
  74. if err != nil {
  75. extra.UserMsg = "Internal Server Response Reading Failed"
  76. extra.Error = fmt.Errorf("unable to read response %q: %w", req.GoString(), err)
  77. return nil, extra
  78. }
  79. respText.Text = string(bs)
  80. return res, extra
  81. } else if cb, ok := v.(*responseCallback); ok {
  82. // pass the response to callback, and let the callback update the ResponseExtra
  83. extra.StatusCode = resp.StatusCode
  84. cb.Callback(resp, &extra)
  85. return nil, extra
  86. } else if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
  87. // decode the response into the given struct
  88. extra.UserMsg = "Internal Server Response Decoding Failed"
  89. extra.Error = fmt.Errorf("unable to decode response %q: %w", req.GoString(), err)
  90. return nil, extra
  91. }
  92. if respMsg, ok := v.(*Response); ok {
  93. // if the "res" is Response structure, try to get the UserMsg from it and update the ResponseExtra
  94. extra.UserMsg = respMsg.UserMsg
  95. if respMsg.Err != "" {
  96. // usually this shouldn't happen, because the StatusCode is 2xx, there should be no error.
  97. // but we still handle the "err" response, in case some people return error messages by status code 200.
  98. extra.Error = responseError{statusCode: resp.StatusCode, errorString: respMsg.Err}
  99. }
  100. }
  101. return res, extra
  102. }
  103. // requestJSONClientMsg sends a request to the gitea server, server only responds text message status=200 with "success" body
  104. // If the request succeeds (200), the argument clientSuccessMsg will be used as ResponseExtra.UserMsg.
  105. func requestJSONClientMsg(req *httplib.Request, clientSuccessMsg string) ResponseExtra {
  106. _, extra := requestJSONResp(req, &responseText{})
  107. if extra.HasError() {
  108. return extra
  109. }
  110. extra.UserMsg = clientSuccessMsg
  111. return extra
  112. }