// 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(`

{{with .Username}} Hello {{.}}{{end}} {{with .Logout}}Sign out{{end}}

You have visited {{.Visits}}

`)) // 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) } }