158 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2015 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 proxy proxies requests to the playground's compile and share handlers.
 | |
| // It is designed to run only on the instance of godoc that serves golang.org.
 | |
| package proxy
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"net/http"
 | |
| 	"net/http/httputil"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"golang.org/x/net/context"
 | |
| 	"golang.org/x/tools/godoc/env"
 | |
| )
 | |
| 
 | |
| const playgroundURL = "https://play.golang.org"
 | |
| 
 | |
| var proxy *httputil.ReverseProxy
 | |
| 
 | |
| func init() {
 | |
| 	target, _ := url.Parse(playgroundURL)
 | |
| 	proxy = httputil.NewSingleHostReverseProxy(target)
 | |
| }
 | |
| 
 | |
| type Request struct {
 | |
| 	Body string
 | |
| }
 | |
| 
 | |
| type Response struct {
 | |
| 	Errors string
 | |
| 	Events []Event
 | |
| }
 | |
| 
 | |
| type Event struct {
 | |
| 	Message string
 | |
| 	Kind    string        // "stdout" or "stderr"
 | |
| 	Delay   time.Duration // time to wait before printing Message
 | |
| }
 | |
| 
 | |
| const expires = 7 * 24 * time.Hour // 1 week
 | |
| var cacheControlHeader = fmt.Sprintf("public, max-age=%d", int(expires.Seconds()))
 | |
| 
 | |
| func RegisterHandlers(mux *http.ServeMux) {
 | |
| 	mux.HandleFunc("/compile", compile)
 | |
| 	mux.HandleFunc("/share", share)
 | |
| }
 | |
| 
 | |
| func compile(w http.ResponseWriter, r *http.Request) {
 | |
| 	if r.Method != "POST" {
 | |
| 		http.Error(w, "I only answer to POST requests.", http.StatusMethodNotAllowed)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ctx := r.Context()
 | |
| 
 | |
| 	body := r.FormValue("body")
 | |
| 	res := &Response{}
 | |
| 	req := &Request{Body: body}
 | |
| 	if err := makeCompileRequest(ctx, req, res); err != nil {
 | |
| 		log.Printf("ERROR compile error: %v", err)
 | |
| 		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var out interface{}
 | |
| 	switch r.FormValue("version") {
 | |
| 	case "2":
 | |
| 		out = res
 | |
| 	default: // "1"
 | |
| 		out = struct {
 | |
| 			CompileErrors string `json:"compile_errors"`
 | |
| 			Output        string `json:"output"`
 | |
| 		}{res.Errors, flatten(res.Events)}
 | |
| 	}
 | |
| 	b, err := json.Marshal(out)
 | |
| 	if err != nil {
 | |
| 		log.Printf("ERROR encoding response: %v", err)
 | |
| 		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	expiresTime := time.Now().Add(expires).UTC()
 | |
| 	w.Header().Set("Expires", expiresTime.Format(time.RFC1123))
 | |
| 	w.Header().Set("Cache-Control", cacheControlHeader)
 | |
| 	w.Write(b)
 | |
| }
 | |
| 
 | |
| // makePlaygroundRequest sends the given Request to the playground compile
 | |
| // endpoint and stores the response in the given Response.
 | |
| func makeCompileRequest(ctx context.Context, req *Request, res *Response) error {
 | |
| 	reqJ, err := json.Marshal(req)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("marshalling request: %v", err)
 | |
| 	}
 | |
| 	hReq, _ := http.NewRequest("POST", playgroundURL+"/compile", bytes.NewReader(reqJ))
 | |
| 	hReq.Header.Set("Content-Type", "application/json")
 | |
| 	hReq = hReq.WithContext(ctx)
 | |
| 
 | |
| 	r, err := http.DefaultClient.Do(hReq)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("making request: %v", err)
 | |
| 	}
 | |
| 	defer r.Body.Close()
 | |
| 
 | |
| 	if r.StatusCode != http.StatusOK {
 | |
| 		b, _ := ioutil.ReadAll(r.Body)
 | |
| 		return fmt.Errorf("bad status: %v body:\n%s", r.Status, b)
 | |
| 	}
 | |
| 
 | |
| 	if err := json.NewDecoder(r.Body).Decode(res); err != nil {
 | |
| 		return fmt.Errorf("unmarshalling response: %v", err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // flatten takes a sequence of Events and returns their contents, concatenated.
 | |
| func flatten(seq []Event) string {
 | |
| 	var buf bytes.Buffer
 | |
| 	for _, e := range seq {
 | |
| 		buf.WriteString(e.Message)
 | |
| 	}
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| func share(w http.ResponseWriter, r *http.Request) {
 | |
| 	if googleCN(r) {
 | |
| 		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
 | |
| 		return
 | |
| 	}
 | |
| 	proxy.ServeHTTP(w, r)
 | |
| }
 | |
| 
 | |
| func googleCN(r *http.Request) bool {
 | |
| 	if r.FormValue("googlecn") != "" {
 | |
| 		return true
 | |
| 	}
 | |
| 	if !env.IsProd() {
 | |
| 		return false
 | |
| 	}
 | |
| 	if strings.HasSuffix(r.Host, ".cn") {
 | |
| 		return true
 | |
| 	}
 | |
| 	switch r.Header.Get("X-AppEngine-Country") {
 | |
| 	case "", "ZZ", "CN":
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 |