/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

  1. // Copyright 2011 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package app
  5. import (
  6. "appengine"
  7. "appengine/channel"
  8. "appengine/taskqueue"
  9. "bytes"
  10. "crypto/sha1"
  11. "fmt"
  12. "goray/job"
  13. "goray/std/yamlscene"
  14. "http"
  15. "io"
  16. "os"
  17. "url"
  18. _ "goray/std/all"
  19. )
  20. const bucket = "http://appengine-go-ray.commondatastorage.googleapis.com/"
  21. func init() {
  22. http.HandleFunc("/task/render", renderTask)
  23. http.HandleFunc("/task/send", sendTask)
  24. }
  25. func renderTask(w http.ResponseWriter, r *http.Request) {
  26. var (
  27. c = appengine.NewContext(r)
  28. scene = r.FormValue("scene")
  29. clientid = r.FormValue("clientid")
  30. )
  31. // send is a helper closure to make sending messages more concise.
  32. send := func(msg string) {
  33. c.Infof("renderTask: %s", msg) // log the message to dashboard
  34. sendMessage(c, clientid, msg)
  35. }
  36. // Recover from panics.
  37. // If programmer error causes this task to crash,
  38. // we should notify the end user.
  39. defer func() {
  40. if e := recover(); e != nil {
  41. c.Criticalf("panic: %v", e)
  42. send("Error: render failed.")
  43. }
  44. }()
  45. send("Rendering...")
  46. // Set up the goray job.
  47. j := job.New("job", bytes.NewBufferString(scene), yamlscene.Params{
  48. "OutputFormat": job.FormatMap["png"],
  49. })
  50. // Render the image.
  51. var image bytes.Buffer
  52. if err := j.Render(&image); err != nil {
  53. send(fmt.Sprintf("Error: %v", err))
  54. c.Errorf("renderTask: %v", err)
  55. return
  56. }
  57. send("Render complete.")
  58. send("Uploading...")
  59. // Use a hash of the scene data as the image filename.
  60. h := sha1.New()
  61. io.WriteString(h, scene)
  62. imageURL := fmt.Sprintf("%s%x.png", bucket, h.Sum())
  63. if err := putImage(c, imageURL, &image); err != nil {
  64. send(fmt.Sprintf("Error: %v", err))
  65. c.Errorf("renderTask: putImage: %v", err)
  66. return
  67. }
  68. send("Upload complete.")
  69. // The front-end looks for a message beginning with "Image: " and
  70. // interprets it as the final image URL.
  71. send("Image: " + imageURL)
  72. }
  73. // putImage reads image data from r and uploads it to url using authentication
  74. // data stored in the datastore.
  75. func putImage(c appengine.Context, url string, r io.Reader) os.Error {
  76. rt := oauth2Transport(c)
  77. if rt == nil {
  78. return os.NewError("couldn't load OAuth2 credentials.")
  79. }
  80. req, err := http.NewRequest("PUT", url, r)
  81. if err != nil {
  82. return err
  83. }
  84. req.Header.Set("x-goog-acl", "public-read")
  85. resp, err := rt.RoundTrip(req)
  86. if err != nil {
  87. return err
  88. }
  89. if resp.StatusCode != 200 {
  90. return os.NewError("uploading image: " + resp.Status)
  91. }
  92. return nil
  93. }
  94. // sendMessage sends msg to the channel identified by clientid.
  95. func sendMessage(c appengine.Context, clientid, msg string) {
  96. // FIXME: This is a work-around for broken Channel API on back-ends.
  97. // Really we should just be doing the channel.Send here,
  98. // but instead we must create a task to send the message
  99. // from the front-end.
  100. // http://code.google.com/p/googleappengine/issues/detail?id=5123
  101. // Create send task.
  102. t := taskqueue.NewPOSTTask("/task/send", url.Values{
  103. "id": {clientid}, "msg": {msg},
  104. })
  105. // Send the task to a normal app instance, as the renderTask
  106. // should be executed on the backend. By default, new tasks go
  107. // run on the instance that creates them.
  108. if !appengine.IsDevAppServer() {
  109. t.Header.Set("Host", appengine.DefaultVersionHostname(c))
  110. }
  111. // Add task to the queue.
  112. if _, err := taskqueue.Add(c, t, ""); err != nil {
  113. c.Errorf("renderTask: %v", err)
  114. }
  115. }
  116. // sendTask is a task that sends msg to a channel identified by id.
  117. func sendTask(w http.ResponseWriter, r *http.Request) {
  118. var (
  119. c = appengine.NewContext(r)
  120. id = r.FormValue("id")
  121. msg = r.FormValue("msg")
  122. )
  123. if err := channel.Send(c, id, msg); err != nil {
  124. c.Errorf("sending to %q: %v", id, err)
  125. }
  126. }