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.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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. //
  41. // * If the "res" is a struct pointer, the response will be parsed as JSON
  42. // * If the "res" is responseText pointer, the response will be stored as text in it
  43. // * If the "res" is responseCallback pointer, the callback function should set the ResponseExtra fields accordingly
  44. func requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra ResponseExtra) {
  45. resp, err := req.Response()
  46. if err != nil {
  47. extra.UserMsg = "Internal Server Connection Error"
  48. extra.Error = fmt.Errorf("unable to contact gitea %q: %w", req.GoString(), err)
  49. return nil, extra
  50. }
  51. defer resp.Body.Close()
  52. extra.StatusCode = resp.StatusCode
  53. // if the status code is not 2xx, try to parse the error response
  54. if resp.StatusCode/100 != 2 {
  55. var respErr Response
  56. if err := json.NewDecoder(resp.Body).Decode(&respErr); err != nil {
  57. extra.UserMsg = "Internal Server Error Decoding Failed"
  58. extra.Error = fmt.Errorf("unable to decode error response %q: %w", req.GoString(), err)
  59. return nil, extra
  60. }
  61. extra.UserMsg = respErr.UserMsg
  62. if extra.UserMsg == "" {
  63. extra.UserMsg = "Internal Server Error (no message for end users)"
  64. }
  65. extra.Error = responseError{statusCode: resp.StatusCode, errorString: respErr.Err}
  66. return res, extra
  67. }
  68. // now, the StatusCode must be 2xx
  69. var v any = res
  70. if respText, ok := v.(*responseText); ok {
  71. // get the whole response as a text string
  72. bs, err := io.ReadAll(resp.Body)
  73. if err != nil {
  74. extra.UserMsg = "Internal Server Response Reading Failed"
  75. extra.Error = fmt.Errorf("unable to read response %q: %w", req.GoString(), err)
  76. return nil, extra
  77. }
  78. respText.Text = string(bs)
  79. return res, extra
  80. } else if cb, ok := v.(*responseCallback); ok {
  81. // pass the response to callback, and let the callback update the ResponseExtra
  82. extra.StatusCode = resp.StatusCode
  83. cb.Callback(resp, &extra)
  84. return nil, extra
  85. } else if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
  86. // decode the response into the given struct
  87. extra.UserMsg = "Internal Server Response Decoding Failed"
  88. extra.Error = fmt.Errorf("unable to decode response %q: %w", req.GoString(), err)
  89. return nil, extra
  90. }
  91. if respMsg, ok := v.(*Response); ok {
  92. // if the "res" is Response structure, try to get the UserMsg from it and update the ResponseExtra
  93. extra.UserMsg = respMsg.UserMsg
  94. if respMsg.Err != "" {
  95. // usually this shouldn't happen, because the StatusCode is 2xx, there should be no error.
  96. // but we still handle the "err" response, in case some people return error messages by status code 200.
  97. extra.Error = responseError{statusCode: resp.StatusCode, errorString: respMsg.Err}
  98. }
  99. }
  100. return res, extra
  101. }
  102. // requestJSONClientMsg sends a request to the gitea server, server only responds text message status=200 with "success" body
  103. // If the request succeeds (200), the argument clientSuccessMsg will be used as ResponseExtra.UserMsg.
  104. func requestJSONClientMsg(req *httplib.Request, clientSuccessMsg string) ResponseExtra {
  105. _, extra := requestJSONResp(req, &responseText{})
  106. if extra.HasError() {
  107. return extra
  108. }
  109. extra.UserMsg = clientSuccessMsg
  110. return extra
  111. }