transaction.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // Package p contains an HTTP Cloud Function.
  2. package p
  3. import (
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "html"
  8. "io"
  9. "log"
  10. "net/http"
  11. "os"
  12. "cloud.google.com/go/bigtable"
  13. )
  14. // fetch a single row by transaction identifier
  15. func Transaction(w http.ResponseWriter, r *http.Request) {
  16. // Set CORS headers for the preflight request
  17. if r.Method == http.MethodOptions {
  18. w.Header().Set("Access-Control-Allow-Origin", "*")
  19. w.Header().Set("Access-Control-Allow-Methods", "POST")
  20. w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
  21. w.Header().Set("Access-Control-Max-Age", "3600")
  22. w.WriteHeader(http.StatusNoContent)
  23. return
  24. }
  25. // Set CORS headers for the main request.
  26. w.Header().Set("Access-Control-Allow-Origin", "*")
  27. var transactionID string
  28. // allow GET requests with querystring params, or POST requests with json body.
  29. switch r.Method {
  30. case http.MethodGet:
  31. queryParams := r.URL.Query()
  32. transactionID = queryParams.Get("id")
  33. readyCheck := queryParams.Get("readyCheck")
  34. if readyCheck != "" {
  35. // for running in devnet
  36. w.WriteHeader(http.StatusOK)
  37. fmt.Fprint(w, html.EscapeString("ready"))
  38. return
  39. }
  40. case http.MethodPost:
  41. // declare request body properties
  42. var d struct {
  43. ID string `json:"id"`
  44. }
  45. // deserialize request body
  46. if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
  47. switch err {
  48. case io.EOF:
  49. // do nothing, empty body is ok
  50. default:
  51. log.Printf("json.NewDecoder: %v", err)
  52. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  53. return
  54. }
  55. }
  56. transactionID = d.ID
  57. default:
  58. http.Error(w, "405 - Method Not Allowed", http.StatusMethodNotAllowed)
  59. log.Println("Method Not Allowed")
  60. return
  61. }
  62. if transactionID == "" {
  63. fmt.Fprint(w, "id cannot be blank")
  64. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  65. }
  66. // create bibtable client and open table
  67. clientOnce.Do(func() {
  68. // Declare a separate err variable to avoid shadowing client.
  69. var err error
  70. project := os.Getenv("GCP_PROJECT")
  71. instance := os.Getenv("BIGTABLE_INSTANCE")
  72. client, err = bigtable.NewClient(context.Background(), project, instance)
  73. if err != nil {
  74. http.Error(w, "Error initializing client", http.StatusInternalServerError)
  75. log.Printf("bigtable.NewClient: %v", err)
  76. return
  77. }
  78. })
  79. tbl := client.Open("v2Events")
  80. var result bigtable.Row
  81. readErr := tbl.ReadRows(r.Context(), bigtable.PrefixRange(""), func(row bigtable.Row) bool {
  82. result = row
  83. return true
  84. }, bigtable.RowFilter(bigtable.ValueFilter(transactionID)))
  85. if readErr != nil {
  86. log.Fatalf("failed to read rows: %v", readErr)
  87. }
  88. if result == nil {
  89. http.NotFound(w, r)
  90. log.Printf("did not find row with transaction ID %v", transactionID)
  91. return
  92. }
  93. key := result.Key()
  94. row, err := tbl.ReadRow(r.Context(), key)
  95. if err != nil {
  96. w.WriteHeader(http.StatusInternalServerError)
  97. w.Write([]byte(err.Error()))
  98. log.Fatalf("Could not read row with key %s: %v", key, err)
  99. }
  100. summary := makeSummary(row)
  101. jsonBytes, err := json.Marshal(summary)
  102. if err != nil {
  103. w.WriteHeader(http.StatusInternalServerError)
  104. w.Write([]byte(err.Error()))
  105. log.Println(err.Error())
  106. return
  107. }
  108. w.WriteHeader(http.StatusOK)
  109. w.Write(jsonBytes)
  110. }