helloworld.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright 2015 Google Inc. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. /*
  5. helloworld tracks how often a user has visited the index page.
  6. This program demonstrates usage of the Cloud Bigtable API for Managed VMs and Go.
  7. Instructions for running this program are in the README.md.
  8. */
  9. package main
  10. import (
  11. "bytes"
  12. "encoding/binary"
  13. "html/template"
  14. "log"
  15. "net/http"
  16. "golang.org/x/net/context"
  17. "google.golang.org/appengine"
  18. aelog "google.golang.org/appengine/log"
  19. "google.golang.org/appengine/user"
  20. "google.golang.org/cloud/bigtable"
  21. )
  22. // User-provided constants.
  23. const (
  24. project = "PROJECT_ID"
  25. zone = "CLUSTER_ZONE"
  26. cluster = "CLUSTER_NAME"
  27. )
  28. var (
  29. tableName = "bigtable-hello"
  30. familyName = "emails"
  31. // Client is initialized by main.
  32. client *bigtable.Client
  33. )
  34. func main() {
  35. ctx := context.Background()
  36. // Set up admin client, tables, and column families.
  37. // NewAdminClient uses Application Default Credentials to authenticate.
  38. adminClient, err := bigtable.NewAdminClient(ctx, project, zone, cluster)
  39. if err != nil {
  40. log.Fatalf("Unable to create a table admin client. %v", err)
  41. }
  42. tables, err := adminClient.Tables(ctx)
  43. if err != nil {
  44. log.Fatalf("Unable to fetch table list. %v", err)
  45. }
  46. if !sliceContains(tables, tableName) {
  47. if err := adminClient.CreateTable(ctx, tableName); err != nil {
  48. log.Fatalf("Unable to create table: %v. %v", tableName, err)
  49. }
  50. }
  51. tblInfo, err := adminClient.TableInfo(ctx, tableName)
  52. if err != nil {
  53. log.Fatalf("Unable to read info for table: %v. %v", tableName, err)
  54. }
  55. if !sliceContains(tblInfo.Families, familyName) {
  56. if err := adminClient.CreateColumnFamily(ctx, tableName, familyName); err != nil {
  57. log.Fatalf("Unable to create column family: %v. %v", familyName, err)
  58. }
  59. }
  60. adminClient.Close()
  61. // Set up Bigtable data operations client.
  62. // NewClient uses Application Default Credentials to authenticate.
  63. client, err = bigtable.NewClient(ctx, project, zone, cluster)
  64. if err != nil {
  65. log.Fatalf("Unable to create data operations client. %v", err)
  66. }
  67. http.Handle("/", appHandler(mainHandler))
  68. appengine.Main() // Never returns.
  69. }
  70. // mainHandler tracks how many times each user has visited this page.
  71. func mainHandler(w http.ResponseWriter, r *http.Request) *appError {
  72. if r.URL.Path != "/" {
  73. http.NotFound(w, r)
  74. return nil
  75. }
  76. ctx := appengine.NewContext(r)
  77. u := user.Current(ctx)
  78. if u == nil {
  79. login, err := user.LoginURL(ctx, r.URL.String())
  80. if err != nil {
  81. return &appError{err, "Error finding login URL", http.StatusInternalServerError}
  82. }
  83. http.Redirect(w, r, login, http.StatusFound)
  84. return nil
  85. }
  86. logoutURL, err := user.LogoutURL(ctx, "/")
  87. if err != nil {
  88. return &appError{err, "Error finding logout URL", http.StatusInternalServerError}
  89. }
  90. // Display hello page.
  91. tbl := client.Open(tableName)
  92. rmw := bigtable.NewReadModifyWrite()
  93. rmw.Increment(familyName, u.Email, 1)
  94. row, err := tbl.ApplyReadModifyWrite(ctx, u.Email, rmw)
  95. if err != nil {
  96. return &appError{err, "Error applying ReadModifyWrite to row: " + u.Email, http.StatusInternalServerError}
  97. }
  98. data := struct {
  99. Username, Logout string
  100. Visits uint64
  101. }{
  102. Username: u.Email,
  103. // Retrieve the most recently edited column.
  104. Visits: binary.BigEndian.Uint64(row[familyName][0].Value),
  105. Logout: logoutURL,
  106. }
  107. var buf bytes.Buffer
  108. if err := tmpl.Execute(&buf, data); err != nil {
  109. return &appError{err, "Error writing template", http.StatusInternalServerError}
  110. }
  111. buf.WriteTo(w)
  112. return nil
  113. }
  114. var tmpl = template.Must(template.New("").Parse(`
  115. <html><body>
  116. <p>
  117. {{with .Username}} Hello {{.}}{{end}}
  118. {{with .Logout}}<a href="{{.}}">Sign out</a>{{end}}
  119. </p>
  120. <p>
  121. You have visited {{.Visits}}
  122. </p>
  123. </body></html>`))
  124. // sliceContains reports whether the provided string is present in the given slice of strings.
  125. func sliceContains(list []string, target string) bool {
  126. for _, s := range list {
  127. if s == target {
  128. return true
  129. }
  130. }
  131. return false
  132. }
  133. // More info about this method of error handling can be found at: http://blog.golang.org/error-handling-and-go
  134. type appHandler func(http.ResponseWriter, *http.Request) *appError
  135. type appError struct {
  136. Error error
  137. Message string
  138. Code int
  139. }
  140. func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  141. if e := fn(w, r); e != nil {
  142. ctx := appengine.NewContext(r)
  143. aelog.Errorf(ctx, "%v", e.Error)
  144. http.Error(w, e.Message, e.Code)
  145. }
  146. }