2
0

http.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. package protocol
  2. import (
  3. "net/http"
  4. "github.com/opencost/opencost/core/pkg/log"
  5. "github.com/opencost/opencost/core/pkg/util/json"
  6. "google.golang.org/protobuf/encoding/protojson"
  7. "google.golang.org/protobuf/proto"
  8. )
  9. // HTTPProtocol is a struct used as a selector for request/response protocol utility methods
  10. type HTTPProtocol struct{}
  11. const internalServerErrorJSON = `{"code":500,"message":"Internal Server Error"}`
  12. // HTTPError represents an http error response
  13. type HTTPError struct {
  14. StatusCode int
  15. Body string
  16. }
  17. // Error returns the error string
  18. func (he HTTPError) Error() string {
  19. return string(he.Body)
  20. }
  21. // BadRequest creates a BadRequest HTTPError
  22. func (hp HTTPProtocol) BadRequest(message string) HTTPError {
  23. return HTTPError{
  24. StatusCode: http.StatusBadRequest,
  25. Body: message,
  26. }
  27. }
  28. // UnprocessableEntity creates an UnprocessableEntity HTTPError
  29. func (hp HTTPProtocol) UnprocessableEntity(message string) HTTPError {
  30. if message == "" {
  31. message = "Unprocessable Entity"
  32. }
  33. return HTTPError{
  34. StatusCode: http.StatusUnprocessableEntity,
  35. Body: message,
  36. }
  37. }
  38. // InternalServerError creates an InternalServerError HTTPError
  39. func (hp HTTPProtocol) InternalServerError(message string) HTTPError {
  40. if message == "" {
  41. message = "Internal Server Error"
  42. }
  43. return HTTPError{
  44. StatusCode: http.StatusInternalServerError,
  45. Body: message,
  46. }
  47. }
  48. func (hp HTTPProtocol) NotImplemented(message string) HTTPError {
  49. if message == "" {
  50. message = "Not Implemented"
  51. }
  52. return HTTPError{
  53. StatusCode: http.StatusNotImplemented,
  54. Body: message,
  55. }
  56. }
  57. func (hp HTTPProtocol) Forbidden(message string) HTTPError {
  58. if message == "" {
  59. message = "Forbidden"
  60. }
  61. return HTTPError{
  62. StatusCode: http.StatusForbidden,
  63. Body: message,
  64. }
  65. }
  66. // NotFound creates a NotFound HTTPError
  67. func (hp HTTPProtocol) NotFound() HTTPError {
  68. return HTTPError{
  69. StatusCode: http.StatusNotFound,
  70. Body: "Not Found",
  71. }
  72. }
  73. // HTTPResponse represents a data envelope for our HTTP messaging
  74. type HTTPResponse struct {
  75. Code int `json:"code"`
  76. Data interface{} `json:"data"`
  77. Meta map[string]interface{} `json:"meta,omitempty"`
  78. Message string `json:"message,omitempty"`
  79. Warning string `json:"warning,omitempty"`
  80. }
  81. // ToResponse accepts a data payload and/or error to encode into a new HTTPResponse instance. Responses
  82. // which should not contain an error should pass nil for err.
  83. func (hp HTTPProtocol) ToResponse(data interface{}, err error) *HTTPResponse {
  84. if err != nil {
  85. return &HTTPResponse{
  86. Code: http.StatusInternalServerError,
  87. Data: data,
  88. Message: err.Error(),
  89. }
  90. }
  91. return &HTTPResponse{
  92. Code: http.StatusOK,
  93. Data: data,
  94. }
  95. }
  96. func (hp HTTPProtocol) WriteRawOK(w http.ResponseWriter) {
  97. w.Header().Set("Content-Type", "application/json")
  98. w.Header().Set("Content-Length", "0")
  99. w.WriteHeader(http.StatusOK)
  100. }
  101. func (hp HTTPProtocol) WriteRawNoContent(w http.ResponseWriter) {
  102. w.Header().Set("Content-Type", "application/json")
  103. w.WriteHeader(http.StatusNoContent)
  104. }
  105. // WriteJSONData uses json content-type and json encoder with no data envelope allowing to remove
  106. // xss CWE as well as backwards compatibility to exisitng FE expectations
  107. func (hp HTTPProtocol) WriteJSONData(w http.ResponseWriter, data interface{}) {
  108. w.Header().Set("Content-Type", "application/json")
  109. if err := json.NewEncoder(w).Encode(data); err != nil {
  110. log.Error("Failed to encode JSON response: " + err.Error())
  111. w.WriteHeader(http.StatusInternalServerError)
  112. w.Write([]byte(internalServerErrorJSON))
  113. }
  114. }
  115. // WriteRawError uses json content-type and outputs raw error message for backwards compatibility to existing
  116. // frontend expectations.
  117. func (hp HTTPProtocol) WriteRawError(w http.ResponseWriter, httpStatusCode int, err string) {
  118. http.Error(w, err, httpStatusCode)
  119. }
  120. // WriteEncodedError writes an error response in the format of HTTPResponse
  121. func (hp HTTPProtocol) WriteEncodedError(w http.ResponseWriter, httpStatusCode int, errorResponse interface{}) {
  122. w.Header().Set("Content-Type", "application/json")
  123. w.WriteHeader(httpStatusCode)
  124. if err := json.NewEncoder(w).Encode(errorResponse); err != nil {
  125. log.Error("Failed to encode error response: " + err.Error())
  126. w.WriteHeader(http.StatusInternalServerError)
  127. w.Write([]byte(internalServerErrorJSON))
  128. }
  129. }
  130. // WriteData wraps the data payload in an HTTPResponse and writes the resulting response using the
  131. // http.ResponseWriter
  132. func (hp HTTPProtocol) WriteData(w http.ResponseWriter, data interface{}) {
  133. w.Header().Set("Content-Type", "application/json")
  134. status := http.StatusOK
  135. w.WriteHeader(status)
  136. resp := &HTTPResponse{
  137. Code: status,
  138. Data: data,
  139. }
  140. if err := json.NewEncoder(w).Encode(resp); err != nil {
  141. log.Error("Failed to encode response: " + err.Error())
  142. w.WriteHeader(http.StatusInternalServerError)
  143. w.Write([]byte(internalServerErrorJSON))
  144. }
  145. }
  146. // WriteDataWithWarning writes the data payload similiar to WriteData except it provides an additional warning message.
  147. func (hp HTTPProtocol) WriteDataWithWarning(w http.ResponseWriter, data interface{}, warning string) {
  148. w.Header().Set("Content-Type", "application/json")
  149. status := http.StatusOK
  150. resp := &HTTPResponse{
  151. Code: status,
  152. Data: data,
  153. Warning: warning,
  154. }
  155. w.WriteHeader(status)
  156. if err := json.NewEncoder(w).Encode(resp); err != nil {
  157. log.Error("Failed to encode response with warning: " + err.Error())
  158. w.WriteHeader(http.StatusInternalServerError)
  159. w.Write([]byte(internalServerErrorJSON))
  160. }
  161. }
  162. // WriteDataWithMessage writes the data payload similiar to WriteData except it provides an additional string message.
  163. func (hp HTTPProtocol) WriteDataWithMessage(w http.ResponseWriter, data interface{}, message string) {
  164. w.Header().Set("Content-Type", "application/json")
  165. status := http.StatusOK
  166. resp := &HTTPResponse{
  167. Code: status,
  168. Data: data,
  169. Message: message,
  170. }
  171. w.WriteHeader(status)
  172. if err := json.NewEncoder(w).Encode(resp); err != nil {
  173. log.Error("Failed to encode response with message: " + err.Error())
  174. w.WriteHeader(http.StatusInternalServerError)
  175. w.Write([]byte(internalServerErrorJSON))
  176. }
  177. }
  178. // WriteProtoWithMessage uses the protojson package to convert proto3 response to json response and
  179. // return it to the requester. Proto3 drops messages with default values but overriding the param
  180. // EmitUnpopulated to true it returns default values in the Json response payload. If error is
  181. // encountered it sent InternalServerError and the error why the json conversion failed.
  182. func (hp HTTPProtocol) WriteProtoWithMessage(w http.ResponseWriter, data proto.Message) {
  183. w.Header().Set("Content-Type", "application/json")
  184. m := protojson.MarshalOptions{
  185. EmitUnpopulated: true,
  186. }
  187. status := http.StatusOK
  188. w.WriteHeader(status)
  189. b, err := m.Marshal(data)
  190. if err != nil {
  191. hp.WriteError(w, hp.InternalServerError(err.Error()))
  192. log.Error("Failed to marshal proto to json: " + err.Error())
  193. return
  194. }
  195. w.Write(b)
  196. }
  197. // WriteDataWithMessageAndWarning writes the data payload similiar to WriteData except it provides a warning and additional message string.
  198. func (hp HTTPProtocol) WriteDataWithMessageAndWarning(w http.ResponseWriter, data interface{}, message string, warning string) {
  199. w.Header().Set("Content-Type", "application/json")
  200. status := http.StatusOK
  201. resp := &HTTPResponse{
  202. Code: status,
  203. Data: data,
  204. Message: message,
  205. Warning: warning,
  206. }
  207. w.WriteHeader(status)
  208. if err := json.NewEncoder(w).Encode(resp); err != nil {
  209. log.Error("Failed to encode response with message and warning: " + err.Error())
  210. w.WriteHeader(http.StatusInternalServerError)
  211. w.Write([]byte(internalServerErrorJSON))
  212. }
  213. }
  214. // WriteError wraps the HTTPError in a HTTPResponse and writes it via http.ResponseWriter
  215. func (hp HTTPProtocol) WriteError(w http.ResponseWriter, err HTTPError) {
  216. w.Header().Set("Content-Type", "application/json")
  217. status := err.StatusCode
  218. if status == 0 {
  219. status = http.StatusInternalServerError
  220. }
  221. w.WriteHeader(status)
  222. resp := &HTTPResponse{
  223. Code: status,
  224. Message: err.Body,
  225. }
  226. if err := json.NewEncoder(w).Encode(resp); err != nil {
  227. log.Error("Failed to encode error response: " + err.Error())
  228. w.WriteHeader(http.StatusInternalServerError)
  229. w.Write([]byte(internalServerErrorJSON))
  230. }
  231. }
  232. // WriteResponse writes the provided HTTPResponse instance via http.ResponseWriter
  233. func (hp HTTPProtocol) WriteResponse(w http.ResponseWriter, r *HTTPResponse) {
  234. w.Header().Set("Content-Type", "application/json")
  235. status := r.Code
  236. w.WriteHeader(status)
  237. if err := json.NewEncoder(w).Encode(r); err != nil {
  238. log.Error("Failed to encode response: " + err.Error())
  239. w.WriteHeader(http.StatusInternalServerError)
  240. w.Write([]byte(internalServerErrorJSON))
  241. }
  242. }
  243. func (hp HTTPProtocol) NewError(statusCode int, err error) *HTTPError {
  244. if statusCode == 0 {
  245. statusCode = http.StatusInternalServerError
  246. }
  247. var body string
  248. if err != nil {
  249. body = err.Error()
  250. } else {
  251. body = "Internal Server Error"
  252. }
  253. return &HTTPError{
  254. StatusCode: statusCode,
  255. Body: body,
  256. }
  257. }
  258. func (hp HTTPProtocol) NewResponse(code ...int) *HTTPResponse {
  259. hr := &HTTPResponse{Code: http.StatusOK}
  260. if len(code) == 1 {
  261. hr.Code = code[0]
  262. }
  263. return hr
  264. }
  265. func (hr *HTTPResponse) WithCode(code int) *HTTPResponse {
  266. hr.Code = code
  267. return hr
  268. }
  269. func (hr *HTTPResponse) WithData(data interface{}) *HTTPResponse {
  270. hr.Data = data
  271. return hr
  272. }
  273. func (hr *HTTPResponse) WithMeta(meta map[string]interface{}) *HTTPResponse {
  274. hr.Meta = meta
  275. return hr
  276. }
  277. func (hr *HTTPResponse) WithMessage(message string) *HTTPResponse {
  278. hr.Message = message
  279. return hr
  280. }
  281. func (hr *HTTPResponse) WithWarning(warning string) *HTTPResponse {
  282. hr.Message = warning
  283. return hr
  284. }