mirror of
https://github.com/golang/go
synced 2024-11-05 17:46:16 -07:00
ee6b03148c
See bug for more details on exactly what was migrated. Notably: * No more Google-internal deployment scripts; see README.godoc-app and the Makefile for details. * Build tag "golangorg" is used for the godoc configuration used for golang.org. * Use of App Engine libraries replaced with GCP client libraries. * Redis is used to replace App Engine memcache. * Google analytics is controlled by an environment variable. * Regression tests have been migrated from Google-internal. * hg -> git hash map is moved from Google-internal. Updates golang/go#27205. Change-Id: Ia0a983f239c50eda8be2363494c8b784f60c2c6d Reviewed-on: https://go-review.googlesource.com/133355 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
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
|
|
}
|