2
0

http.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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(err error, statusCode ...int) *HTTPError {
  244. code := http.StatusInternalServerError
  245. if len(statusCode) > 0 {
  246. code = statusCode[0]
  247. }
  248. var body string
  249. if err != nil {
  250. body = err.Error()
  251. } else {
  252. body = "Internal Server Error"
  253. }
  254. return &HTTPError{
  255. StatusCode: code,
  256. Body: body,
  257. }
  258. }
  259. func (hp HTTPProtocol) NewResponse(code ...int) *HTTPResponse {
  260. r := &HTTPResponse{Code: http.StatusOK}
  261. if len(code) == 1 {
  262. r.Code = code[0]
  263. }
  264. return r
  265. }
  266. func (r *HTTPResponse) WithCode(code int) *HTTPResponse {
  267. if r == nil {
  268. r = &HTTPResponse{}
  269. }
  270. r.Code = code
  271. return r
  272. }
  273. func (r *HTTPResponse) WithData(data interface{}) *HTTPResponse {
  274. if r == nil {
  275. r = &HTTPResponse{}
  276. }
  277. r.Data = data
  278. return r
  279. }
  280. func (r *HTTPResponse) WithMeta(meta map[string]interface{}) *HTTPResponse {
  281. if r == nil {
  282. r = &HTTPResponse{}
  283. }
  284. r.Meta = meta
  285. return r
  286. }
  287. func (r *HTTPResponse) WithMessage(message string) *HTTPResponse {
  288. if r == nil {
  289. r = &HTTPResponse{}
  290. }
  291. r.Message = message
  292. return r
  293. }
  294. func (r *HTTPResponse) WithWarning(warning string) *HTTPResponse {
  295. if r == nil {
  296. r = &HTTPResponse{}
  297. }
  298. r.Warning = warning
  299. return r
  300. }