/app/backend.go
https://code.google.com/p/go-ray/ · Go · 152 lines · 100 code · 26 blank · 26 comment · 20 complexity · c46dd773d8749ade4e329e5088d761c2 MD5 · raw file
- // Copyright 2011 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package app
- import (
- "appengine"
- "appengine/channel"
- "appengine/taskqueue"
- "bytes"
- "crypto/sha1"
- "fmt"
- "goray/job"
- "goray/std/yamlscene"
- "http"
- "io"
- "os"
- "url"
- _ "goray/std/all"
- )
- const bucket = "http://appengine-go-ray.commondatastorage.googleapis.com/"
- func init() {
- http.HandleFunc("/task/render", renderTask)
- http.HandleFunc("/task/send", sendTask)
- }
- func renderTask(w http.ResponseWriter, r *http.Request) {
- var (
- c = appengine.NewContext(r)
- scene = r.FormValue("scene")
- clientid = r.FormValue("clientid")
- )
- // send is a helper closure to make sending messages more concise.
- send := func(msg string) {
- c.Infof("renderTask: %s", msg) // log the message to dashboard
- sendMessage(c, clientid, msg)
- }
- // Recover from panics.
- // If programmer error causes this task to crash,
- // we should notify the end user.
- defer func() {
- if e := recover(); e != nil {
- c.Criticalf("panic: %v", e)
- send("Error: render failed.")
- }
- }()
- send("Rendering...")
- // Set up the goray job.
- j := job.New("job", bytes.NewBufferString(scene), yamlscene.Params{
- "OutputFormat": job.FormatMap["png"],
- })
- // Render the image.
- var image bytes.Buffer
- if err := j.Render(&image); err != nil {
- send(fmt.Sprintf("Error: %v", err))
- c.Errorf("renderTask: %v", err)
- return
- }
- send("Render complete.")
- send("Uploading...")
- // Use a hash of the scene data as the image filename.
- h := sha1.New()
- io.WriteString(h, scene)
- imageURL := fmt.Sprintf("%s%x.png", bucket, h.Sum())
- if err := putImage(c, imageURL, &image); err != nil {
- send(fmt.Sprintf("Error: %v", err))
- c.Errorf("renderTask: putImage: %v", err)
- return
- }
- send("Upload complete.")
- // The front-end looks for a message beginning with "Image: " and
- // interprets it as the final image URL.
- send("Image: " + imageURL)
- }
- // putImage reads image data from r and uploads it to url using authentication
- // data stored in the datastore.
- func putImage(c appengine.Context, url string, r io.Reader) os.Error {
- rt := oauth2Transport(c)
- if rt == nil {
- return os.NewError("couldn't load OAuth2 credentials.")
- }
- req, err := http.NewRequest("PUT", url, r)
- if err != nil {
- return err
- }
- req.Header.Set("x-goog-acl", "public-read")
- resp, err := rt.RoundTrip(req)
- if err != nil {
- return err
- }
- if resp.StatusCode != 200 {
- return os.NewError("uploading image: " + resp.Status)
- }
- return nil
- }
- // sendMessage sends msg to the channel identified by clientid.
- func sendMessage(c appengine.Context, clientid, msg string) {
- // FIXME: This is a work-around for broken Channel API on back-ends.
- // Really we should just be doing the channel.Send here,
- // but instead we must create a task to send the message
- // from the front-end.
- // http://code.google.com/p/googleappengine/issues/detail?id=5123
- // Create send task.
- t := taskqueue.NewPOSTTask("/task/send", url.Values{
- "id": {clientid}, "msg": {msg},
- })
- // Send the task to a normal app instance, as the renderTask
- // should be executed on the backend. By default, new tasks go
- // run on the instance that creates them.
- if !appengine.IsDevAppServer() {
- t.Header.Set("Host", appengine.DefaultVersionHostname(c))
- }
- // Add task to the queue.
- if _, err := taskqueue.Add(c, t, ""); err != nil {
- c.Errorf("renderTask: %v", err)
- }
- }
- // sendTask is a task that sends msg to a channel identified by id.
- func sendTask(w http.ResponseWriter, r *http.Request) {
- var (
- c = appengine.NewContext(r)
- id = r.FormValue("id")
- msg = r.FormValue("msg")
- )
- if err := channel.Send(c, id, msg); err != nil {
- c.Errorf("sending to %q: %v", id, err)
- }
- }