jsonhandler.go 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package tsweb
  5. import (
  6. "encoding/json"
  7. "fmt"
  8. "net/http"
  9. )
  10. type response struct {
  11. Status string `json:"status"`
  12. Error string `json:"error,omitempty"`
  13. Data interface{} `json:"data,omitempty"`
  14. }
  15. // JSONHandlerFunc is an HTTP ReturnHandler that writes JSON responses to the client.
  16. //
  17. // Return a HTTPError to show an error message, otherwise JSONHandlerFunc will
  18. // only report "internal server error" to the user with status code 500.
  19. type JSONHandlerFunc func(r *http.Request) (status int, data interface{}, err error)
  20. // ServeHTTPReturn implements the ReturnHandler interface.
  21. //
  22. // Use the following code to unmarshal the request body
  23. //
  24. // body := new(DataType)
  25. // if err := json.NewDecoder(r.Body).Decode(body); err != nil {
  26. // return http.StatusBadRequest, nil, err
  27. // }
  28. //
  29. // See jsonhandler_test.go for examples.
  30. func (fn JSONHandlerFunc) ServeHTTPReturn(w http.ResponseWriter, r *http.Request) error {
  31. w.Header().Set("Content-Type", "application/json")
  32. var resp *response
  33. status, data, err := fn(r)
  34. if err != nil {
  35. if werr, ok := err.(HTTPError); ok {
  36. resp = &response{
  37. Status: "error",
  38. Error: werr.Msg,
  39. Data: data,
  40. }
  41. // Unwrap the HTTPError here because we are communicating with
  42. // the client in this handler. We don't want the wrapping
  43. // ReturnHandler to do it too.
  44. err = werr.Err
  45. if werr.Msg != "" {
  46. err = fmt.Errorf("%s: %w", werr.Msg, err)
  47. }
  48. // take status from the HTTPError to encourage error handling in one location
  49. if status != 0 && status != werr.Code {
  50. err = fmt.Errorf("[unexpected] non-zero status that does not match HTTPError status, status: %d, HTTPError.code: %d: %w", status, werr.Code, err)
  51. }
  52. status = werr.Code
  53. } else {
  54. status = http.StatusInternalServerError
  55. resp = &response{
  56. Status: "error",
  57. Error: "internal server error",
  58. }
  59. }
  60. } else if status == 0 {
  61. status = http.StatusInternalServerError
  62. resp = &response{
  63. Status: "error",
  64. Error: "internal server error",
  65. }
  66. } else if err == nil {
  67. resp = &response{
  68. Status: "success",
  69. Data: data,
  70. }
  71. }
  72. b, jerr := json.Marshal(resp)
  73. if jerr != nil {
  74. w.WriteHeader(http.StatusInternalServerError)
  75. w.Write([]byte(`{"status":"error","error":"json marshal error"}`))
  76. if err != nil {
  77. return fmt.Errorf("%w, and then we could not respond: %v", err, jerr)
  78. }
  79. return jerr
  80. }
  81. w.WriteHeader(status)
  82. w.Write(b)
  83. return err
  84. }