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