123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- // Copyright 2015 Google Inc. All rights reserved.
- // Use of this source code is governed by the Apache 2.0
- // license that can be found in the LICENSE file.
- /*
- helloworld tracks how often a user has visited the index page.
- This program demonstrates usage of the Cloud Bigtable API for Managed VMs and Go.
- Instructions for running this program are in the README.md.
- */
- package main
- import (
- "bytes"
- "encoding/binary"
- "html/template"
- "log"
- "net/http"
- "golang.org/x/net/context"
- "google.golang.org/appengine"
- aelog "google.golang.org/appengine/log"
- "google.golang.org/appengine/user"
- "google.golang.org/cloud/bigtable"
- )
- // User-provided constants.
- const (
- project = "PROJECT_ID"
- zone = "CLUSTER_ZONE"
- cluster = "CLUSTER_NAME"
- )
- var (
- tableName = "bigtable-hello"
- familyName = "emails"
- // Client is initialized by main.
- client *bigtable.Client
- )
- func main() {
- ctx := context.Background()
- // Set up admin client, tables, and column families.
- // NewAdminClient uses Application Default Credentials to authenticate.
- adminClient, err := bigtable.NewAdminClient(ctx, project, zone, cluster)
- if err != nil {
- log.Fatalf("Unable to create a table admin client. %v", err)
- }
- tables, err := adminClient.Tables(ctx)
- if err != nil {
- log.Fatalf("Unable to fetch table list. %v", err)
- }
- if !sliceContains(tables, tableName) {
- if err := adminClient.CreateTable(ctx, tableName); err != nil {
- log.Fatalf("Unable to create table: %v. %v", tableName, err)
- }
- }
- tblInfo, err := adminClient.TableInfo(ctx, tableName)
- if err != nil {
- log.Fatalf("Unable to read info for table: %v. %v", tableName, err)
- }
- if !sliceContains(tblInfo.Families, familyName) {
- if err := adminClient.CreateColumnFamily(ctx, tableName, familyName); err != nil {
- log.Fatalf("Unable to create column family: %v. %v", familyName, err)
- }
- }
- adminClient.Close()
- // Set up Bigtable data operations client.
- // NewClient uses Application Default Credentials to authenticate.
- client, err = bigtable.NewClient(ctx, project, zone, cluster)
- if err != nil {
- log.Fatalf("Unable to create data operations client. %v", err)
- }
- http.Handle("/", appHandler(mainHandler))
- appengine.Main() // Never returns.
- }
- // mainHandler tracks how many times each user has visited this page.
- func mainHandler(w http.ResponseWriter, r *http.Request) *appError {
- if r.URL.Path != "/" {
- http.NotFound(w, r)
- return nil
- }
- ctx := appengine.NewContext(r)
- u := user.Current(ctx)
- if u == nil {
- login, err := user.LoginURL(ctx, r.URL.String())
- if err != nil {
- return &appError{err, "Error finding login URL", http.StatusInternalServerError}
- }
- http.Redirect(w, r, login, http.StatusFound)
- return nil
- }
- logoutURL, err := user.LogoutURL(ctx, "/")
- if err != nil {
- return &appError{err, "Error finding logout URL", http.StatusInternalServerError}
- }
- // Display hello page.
- tbl := client.Open(tableName)
- rmw := bigtable.NewReadModifyWrite()
- rmw.Increment(familyName, u.Email, 1)
- row, err := tbl.ApplyReadModifyWrite(ctx, u.Email, rmw)
- if err != nil {
- return &appError{err, "Error applying ReadModifyWrite to row: " + u.Email, http.StatusInternalServerError}
- }
- data := struct {
- Username, Logout string
- Visits uint64
- }{
- Username: u.Email,
- // Retrieve the most recently edited column.
- Visits: binary.BigEndian.Uint64(row[familyName][0].Value),
- Logout: logoutURL,
- }
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return &appError{err, "Error writing template", http.StatusInternalServerError}
- }
- buf.WriteTo(w)
- return nil
- }
- var tmpl = template.Must(template.New("").Parse(`
- <html><body>
- <p>
- {{with .Username}} Hello {{.}}{{end}}
- {{with .Logout}}<a href="{{.}}">Sign out</a>{{end}}
- </p>
- <p>
- You have visited {{.Visits}}
- </p>
- </body></html>`))
- // sliceContains reports whether the provided string is present in the given slice of strings.
- func sliceContains(list []string, target string) bool {
- for _, s := range list {
- if s == target {
- return true
- }
- }
- return false
- }
- // More info about this method of error handling can be found at: http://blog.golang.org/error-handling-and-go
- type appHandler func(http.ResponseWriter, *http.Request) *appError
- type appError struct {
- Error error
- Message string
- Code int
- }
- func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if e := fn(w, r); e != nil {
- ctx := appengine.NewContext(r)
- aelog.Errorf(ctx, "%v", e.Error)
- http.Error(w, e.Message, e.Code)
- }
- }
|