You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
4.4 KiB

  1. // Copyright 2015 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. // +build appengine
  5. // Package proxy proxies requests to the sandbox compiler service and the
  6. // playground share handler.
  7. // It is designed to run only on the instance of godoc that serves golang.org.
  8. package proxy
  9. import (
  10. "bytes"
  11. "crypto/sha1"
  12. "encoding/json"
  13. "fmt"
  14. "io"
  15. "io/ioutil"
  16. "net/http"
  17. "net/http/httputil"
  18. "net/url"
  19. "strings"
  20. "time"
  21. "golang.org/x/net/context"
  22. "google.golang.org/appengine"
  23. "google.golang.org/appengine/log"
  24. "google.golang.org/appengine/memcache"
  25. "google.golang.org/appengine/urlfetch"
  26. )
  27. type Request struct {
  28. Body string
  29. }
  30. type Response struct {
  31. Errors string
  32. Events []Event
  33. }
  34. type Event struct {
  35. Message string
  36. Kind string // "stdout" or "stderr"
  37. Delay time.Duration // time to wait before printing Message
  38. }
  39. const (
  40. // We need to use HTTP here for "reasons", but the traffic isn't
  41. // sensitive and it only travels across Google's internal network
  42. // so we should be OK.
  43. sandboxURL = "http://sandbox.golang.org/compile"
  44. playgroundURL = "https://play.golang.org"
  45. )
  46. const expires = 7 * 24 * time.Hour // 1 week
  47. var cacheControlHeader = fmt.Sprintf("public, max-age=%d", int(expires.Seconds()))
  48. func RegisterHandlers(mux *http.ServeMux) {
  49. mux.HandleFunc("/compile", compile)
  50. mux.HandleFunc("/share", share)
  51. }
  52. func compile(w http.ResponseWriter, r *http.Request) {
  53. if r.Method != "POST" {
  54. http.Error(w, "I only answer to POST requests.", http.StatusMethodNotAllowed)
  55. return
  56. }
  57. c := appengine.NewContext(r)
  58. body := r.FormValue("body")
  59. res := &Response{}
  60. key := cacheKey(body)
  61. if _, err := memcache.Gob.Get(c, key, res); err != nil {
  62. if err != memcache.ErrCacheMiss {
  63. log.Errorf(c, "getting response cache: %v", err)
  64. }
  65. req := &Request{Body: body}
  66. if err := makeSandboxRequest(c, req, res); err != nil {
  67. log.Errorf(c, "compile error: %v", err)
  68. http.Error(w, "Internal Server Error", http.StatusInternalServerError)
  69. return
  70. }
  71. item := &memcache.Item{Key: key, Object: res}
  72. if err := memcache.Gob.Set(c, item); err != nil {
  73. log.Errorf(c, "setting response cache: %v", err)
  74. }
  75. }
  76. expiresTime := time.Now().Add(expires).UTC()
  77. w.Header().Set("Expires", expiresTime.Format(time.RFC1123))
  78. w.Header().Set("Cache-Control", cacheControlHeader)
  79. var out interface{}
  80. switch r.FormValue("version") {
  81. case "2":
  82. out = res
  83. default: // "1"
  84. out = struct {
  85. CompileErrors string `json:"compile_errors"`
  86. Output string `json:"output"`
  87. }{res.Errors, flatten(res.Events)}
  88. }
  89. if err := json.NewEncoder(w).Encode(out); err != nil {
  90. log.Errorf(c, "encoding response: %v", err)
  91. }
  92. }
  93. // makeSandboxRequest sends the given Request to the sandbox
  94. // and stores the response in the given Response.
  95. func makeSandboxRequest(c context.Context, req *Request, res *Response) error {
  96. reqJ, err := json.Marshal(req)
  97. if err != nil {
  98. return fmt.Errorf("marshalling request: %v", err)
  99. }
  100. r, err := urlfetch.Client(c).Post(sandboxURL, "application/json", bytes.NewReader(reqJ))
  101. if err != nil {
  102. return fmt.Errorf("making request: %v", err)
  103. }
  104. defer r.Body.Close()
  105. if r.StatusCode != http.StatusOK {
  106. b, _ := ioutil.ReadAll(r.Body)
  107. return fmt.Errorf("bad status: %v body:\n%s", r.Status, b)
  108. }
  109. err = json.NewDecoder(r.Body).Decode(res)
  110. if err != nil {
  111. return fmt.Errorf("unmarshalling response: %v", err)
  112. }
  113. return nil
  114. }
  115. // flatten takes a sequence of Events and returns their contents, concatenated.
  116. func flatten(seq []Event) string {
  117. var buf bytes.Buffer
  118. for _, e := range seq {
  119. buf.WriteString(e.Message)
  120. }
  121. return buf.String()
  122. }
  123. func cacheKey(body string) string {
  124. h := sha1.New()
  125. io.WriteString(h, body)
  126. return fmt.Sprintf("prog-%x", h.Sum(nil))
  127. }
  128. func share(w http.ResponseWriter, r *http.Request) {
  129. if googleCN(r) {
  130. http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  131. return
  132. }
  133. target, _ := url.Parse(playgroundURL)
  134. p := httputil.NewSingleHostReverseProxy(target)
  135. p.Transport = &urlfetch.Transport{Context: appengine.NewContext(r)}
  136. p.ServeHTTP(w, r)
  137. }
  138. func googleCN(r *http.Request) bool {
  139. if r.FormValue("googlecn") != "" {
  140. return true
  141. }
  142. if appengine.IsDevAppServer() {
  143. return false
  144. }
  145. if strings.HasSuffix(r.Host, ".cn") {
  146. return true
  147. }
  148. switch r.Header.Get("X-AppEngine-Country") {
  149. case "", "ZZ", "CN":
  150. return true
  151. }
  152. return false
  153. }