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  }