Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
Storing data in a scalable web application can be tricky. A user could be interacting with any of dozens of web servers at a given time, and the user's next request could go to a different web server than the one that handled the previous request. A web server may depend on data that is spread out across dozens of machines, possibly in different locations around the world.
Thanks to Google App Engine, you don't have to worry about any of that. App Engine's infrastructure takes care of all of the distribution, replication and load balancing of data behind a simple API—and you get a powerful query engine as well.
Here is a new version of myapp/hello/hello.go
that stores
greetings in the datastore. The rest of this page discusses the new pieces.
package hello import ( "appengine" "appengine/datastore" "appengine/user" "http" "template" "time" ) type Greeting struct { Author string Content string Date datastore.Time } func init() { http.HandleFunc("/", root) http.HandleFunc("/sign", sign) } func root(w http.ResponseWriter, r *http.Request) { c := appengine.NewContext(r) q := datastore.NewQuery("Greeting").Order("-Date").Limit(10) greetings := make([]Greeting, 0, 10) if _, err := q.GetAll(c, &greetings); err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } if err := guestbookTemplate.Execute(w, greetings); err != nil { http.Error(w, err.String(), http.StatusInternalServerError) } } var guestbookTemplate = template.Must(template.New("book").Parse(guestbookTemplateHTML)) const guestbookTemplateHTML = ` <html> <body> {{range .}} {{with .Author}} <p><b>{{html .}}</b> wrote:</p> {{else}} <p>An anonymous person wrote:</p> {{end}} <pre>{{html .Content}}</pre> {{end}} <form action="/sign" method="post"> <div><textarea name="content" rows="3" cols="60"></textarea></div> <div><input type="submit" value="Sign Guestbook"></div> </form> </body> </html> ` func sign(w http.ResponseWriter, r *http.Request) { c := appengine.NewContext(r) g := Greeting{ Content: r.FormValue("content"), Date: datastore.SecondsToTime(time.Seconds()), } if u := user.Current(c); u != nil { g.Author = u.String() } _, err := datastore.Put(c, datastore.NewIncompleteKey(c, "Greeting", nil), &g) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } http.Redirect(w, r, "/", http.StatusFound) }
Replace myapp/hello/hello.go
with this, then reload
http://localhost:8080/ in your browser.
Post a few messages to verify that messages get stored and displayed correctly.
For the guestbook application, we want to store greetings posted by users. Each greeting includes the author's name, the message content, and the date and time the message was posted so we can display messages in chronological order.
To represent this data we create a Go struct named Greeting
:
type Greeting struct { Author string Content string Date datastore.Time }
Now that we have a data type for greetings, the application can create new
Greeting
values and put them into the datastore.
The new version of the sign
handler does just that:
func sign(w http.ResponseWriter, r *http.Request) { c := appengine.NewContext(r) g := Greeting{ Content: r.FormValue("content"), Date: datastore.SecondsToTime(time.Seconds()), } if u := user.Current(c); u != nil { g.Author = u.String() } _, err := datastore.Put(c, datastore.NewIncompleteKey(c, "Greeting", nil), &g) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } http.Redirect(w, r, "/", http.StatusFound) }
This creates a new Greeting
value, settings its
Author
field to the current user, its Content
field
with the data posted by the user, and its Date
field to the
current time.
Finally, datastore.Put
saves our new value to the datastore.
We pass it a new, incomplete key so that the datastore will create a new
key for this record automatically.
datastore.Query
The datastore
package provides a Query
type for
querying the datastore and iterating over the results.
The new version of the root
handler queries the datastore for
greetings:
func root(w http.ResponseWriter, r *http.Request) { c := appengine.NewContext(r) q := datastore.NewQuery("Greeting").Order("-Date").Limit(10) greetings := make([]Greeting, 0, 10) if _, err := q.GetAll(c, &greetings); err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } if err := guestbookTemplate.Execute(w, greetings); err != nil { http.Error(w, err.String(), http.StatusInternalServerError) } }
First the function constructs a Query
value that requests
Greeting
objects in Date
-descending order,
with a limit of 10 objects.
q := datastore.NewQuery("Greeting").Order("-Date").Limit(10)
Then it calls q.GetAll(c, &greetings)
, which runs the query
and appends the query results to the greetings
slice.
greetings := make([]Greeting, 0, 10) if _, err := q.GetAll(c, &greetings); err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return }
Finally, the guestbookTemplate.Execute
function renders an HTML
page containing these greetings and writes it out to the
http.ResponseWriter
. For more details on the templating language,
see the template package
documentation.
For a complete description of the Datastore API, see the Datastore reference.
The development web server uses a local version of the datastore for testing your application, using temporary files. The data persists as long as the temporary files exist, and the web server does not reset these files unless you ask it to do so.
If you want the development server to erase its datastore prior to starting up,
use the --clear_datastore
option when starting the server:
dev_appserver.py --clear_datastore myapp/
We now have a working guest book application that authenticates users using Google accounts, lets them submit messages, and displays messages other users have left. Because App Engine handles scaling automatically, we will not need to restructure our application as it gets popular.
Continue to Uploading Your Application.