cmd/redirector/main.go (154 lines of code) (raw):
package main
import (
"fmt"
"log"
"net/http"
"net/url"
"os"
"github.com/pkg/errors"
)
const (
hdrReferer = "Referer"
)
func main() {
log.SetFlags(log.Lmicroseconds)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
listenAddr := ":" + port
// TODO(ahmetb): extract server logic to NewServer() to unit test it
mux := http.NewServeMux()
mux.HandleFunc("/", withLogging(redirect))
mux.HandleFunc("/button.svg", staticRedirect("https://storage.googleapis.com/cloudrun/button.svg", http.StatusMovedPermanently))
mux.HandleFunc("/button.png", staticRedirect("https://storage.googleapis.com/cloudrun/button.png", http.StatusMovedPermanently))
err := http.ListenAndServe(listenAddr, mux)
if err == http.ErrServerClosed {
log.Printf("server successfully closed")
} else if err != nil {
log.Fatal(err)
}
}
func withLogging(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
log.Printf("request: method=%s ip=%s referer=%s params=%s", req.Method, req.Header.Get("x-forwarded-for"), req.Header.Get(hdrReferer), req.URL.RawQuery)
ww := &respRecorder{w: w}
next(ww, req)
log.Printf("response: status=%d location=%s", ww.status, w.Header().Get("location"))
}
}
func staticRedirect(url string, code int) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("location", url)
w.WriteHeader(code)
}
}
func redirect(w http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodPost {
manualRedirect(w, req)
return
}
if req.Method != http.MethodGet && req.Method != http.MethodHead {
w.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintf(w, "method %s not allowed", req.Method)
return
}
repoParam := req.URL.Query().Get(paramRepo)
referer := req.Header.Get(hdrReferer)
// TODO(ahmetb): remove once https://github.community/t/chrome-85-breaks-referer/130039 is fixed
if referer == "https://github.com/" && repoParam == "" {
showRedirectForm(w, req)
return
}
var repo repoRef
if repoParam != "" {
repo = customRepoRef{req.URL.Query()}
} else {
if referer == "" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Cannot infer which repository to deploy (%s header was not present).\n", hdrReferer)
fmt.Fprintln(w, "Go back, and click the 'Run on Google Cloud' button directly from the repository page.")
return
}
r, err := parseReferer(referer, availableExtractors)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, errors.Wrapf(err, "failed to parse %s header", hdrReferer).Error())
return
}
repo = r
}
doRedirect(w, repo, req.URL.Query())
}
func doRedirect(w http.ResponseWriter, r repoRef, overrides url.Values) {
target := prepURL(r, overrides)
w.Header().Set("location", target)
w.WriteHeader(http.StatusTemporaryRedirect)
}
// TODO(ahmetb): remove once https://github.community/t/chrome-85-breaks-referer/130039 is fixed
func showRedirectForm(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head>
<title>Cloud Run Button</title>
<style>
html {
font-family: '-apple-system','BlinkMacSystemFont','segoe ui',Roboto,'helvetica neue',Arial, sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol';
}
body {
margin: 0;
}
html, input {
font-size: 120%%;
}
.container {
margin: 5em auto;
max-width: 768px;
}
</style>
</head>
<body>
<div class="container">
<h1>You’re almost there!</h1>
<p>
Unfortunately with the new Chrome 85, GitHub temporarily breaks our
ability to determine which GitHub repository you came from. (You can
<a href="https://github.community/t/chrome-85-breaks-referer/130039"
rel="nofolow">help us ask GitHub</a> to fix it!)
</p>
<p>
Please provide the URL of the previous page you came from:
</p>
<form action="/" method="POST">
<input type="text" name="url" placeholder="https://github.com/..."
size="32"/>
<input type="hidden" name="orig_query" value="%s"/>
<input type="submit" value="Deploy!"/>
</form>
</div>
</body>
</html>`, r.URL.Query().Encode())
}
// TODO(ahmetb): remove once https://github.community/t/chrome-85-breaks-referer/130039 is fixed
func manualRedirect(w http.ResponseWriter, req *http.Request) {
refURL := req.FormValue("url")
origQuery, err := url.ParseQuery(req.FormValue("orig_query"))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, errors.Wrapf(err, "failed to parse orig_query=%q: %v", origQuery, err).Error())
return
}
repo, err := parseReferer(refURL, availableExtractors)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, errors.Wrapf(err, "failed to parse url into a github repository: %s", refURL).Error())
return
}
doRedirect(w, repo, origQuery)
}
type respRecorder struct {
w http.ResponseWriter
status int
}
func (rr *respRecorder) Header() http.Header { return rr.w.Header() }
func (rr *respRecorder) Write(p []byte) (int, error) { return rr.w.Write(p) }
func (rr *respRecorder) WriteHeader(statusCode int) {
rr.status = statusCode
rr.w.WriteHeader(statusCode)
}