all: promote new app deployments only after they become ready
There are differences in the App Engine environment that cannot be adequately simulated elsewhere. Although we do the best we can with testing locally, there will always be differences. The old makefiles deployed the site, then ran a regression test against it, and then promoted the tested version. This change does the same. The testing has moved into the web server proper so that it can test the handler directly and thereby check things like the responses on different domains. The go-app-deploy.sh now always deploys --no-promote, only promoting after a self-test passes on the deployed site. Unlike the old check which only applied to golang.org, the new pre-promotion testing happens for all the sites. Also factor out GoogleCN into internal/web, because we needed to modify it (to avoid internal/webtest's requests being diagnosed as coming from China) and there were too many copies. Change-Id: I0cde0e2167df2332939908e716ddb6bf429f2565 Reviewed-on: https://go-review.googlesource.com/c/website/+/329250 Trust: Russ Cox <rsc@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
This commit is contained in:
Родитель
d38c22d04f
Коммит
5f5f230b60
|
@ -20,6 +20,7 @@ import (
|
|||
_ "golang.org/x/tools/playground"
|
||||
"golang.org/x/website"
|
||||
"golang.org/x/website/internal/backport/httpfs"
|
||||
"golang.org/x/website/internal/webtest"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -66,6 +67,10 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
h = webtest.HandlerWithCheck(h, "/_readycheck",
|
||||
filepath.Join(blogRoot, "testdata/*.txt"))
|
||||
|
||||
http.Handle("/", h)
|
||||
|
||||
ln, err := net.Listen("tcp", *httpAddr)
|
||||
|
|
|
@ -6,7 +6,6 @@ runtime: go115
|
|||
main: ./cmd/golangorg
|
||||
|
||||
env_variables:
|
||||
GOLANGORG_CHECK_COUNTRY: true
|
||||
GOLANGORG_REQUIRE_DL_SECRET_KEY: true
|
||||
GOLANGORG_ENFORCE_HOSTS: true
|
||||
GOLANGORG_REDIS_ADDR: 10.0.0.4:6379 # instance "gophercache"
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"golang.org/x/website/internal/redirect"
|
||||
"golang.org/x/website/internal/short"
|
||||
"golang.org/x/website/internal/web"
|
||||
"golang.org/x/website/internal/webtest"
|
||||
|
||||
// Registers "/compile" handler that redirects to play.golang.org/compile.
|
||||
// If we are in prod we will register "golang.org/compile" separately,
|
||||
|
@ -93,6 +94,9 @@ func main() {
|
|||
|
||||
handler := NewHandler(*contentDir, *goroot)
|
||||
|
||||
handler = webtest.HandlerWithCheck(handler, "/_readycheck",
|
||||
filepath.Join(*contentDir, "../cmd/golangorg/testdata/*.txt"))
|
||||
|
||||
if *verbose {
|
||||
log.Printf("golang.org server:")
|
||||
log.Printf("\tversion = %s", runtime.Version())
|
||||
|
@ -137,7 +141,6 @@ func NewHandler(contentDir, goroot string) http.Handler {
|
|||
if err != nil {
|
||||
log.Fatalf("NewSite: %v", err)
|
||||
}
|
||||
site.GoogleCN = googleCN
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", site)
|
||||
|
@ -195,26 +198,6 @@ func appEngineSetup(site *web.Site, mux *http.ServeMux) {
|
|||
log.Println("AppEngine initialization complete")
|
||||
}
|
||||
|
||||
// googleCN reports whether request r is considered
|
||||
// to be served from golang.google.cn.
|
||||
// TODO: This is duplicated within internal/proxy. Move to a common location.
|
||||
func googleCN(r *http.Request) bool {
|
||||
if r.FormValue("googlecn") != "" {
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(r.Host, ".cn") {
|
||||
return true
|
||||
}
|
||||
if !env.CheckCountry() {
|
||||
return false
|
||||
}
|
||||
switch r.Header.Get("X-Appengine-Country") {
|
||||
case "", "ZZ", "CN":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func blogHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "https://blog.golang.org"+strings.TrimPrefix(r.URL.Path, "/blog"), http.StatusFound)
|
||||
}
|
||||
|
|
|
@ -5,80 +5,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/website/internal/webtest"
|
||||
)
|
||||
|
||||
func serverAddress(t *testing.T) string {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
ln, err = net.Listen("tcp6", "[::1]:0")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
return ln.Addr().String()
|
||||
}
|
||||
|
||||
func waitForServerReady(t *testing.T, addr string) {
|
||||
waitForServer(t,
|
||||
fmt.Sprintf("http://%v/", addr),
|
||||
"The Go Programming Language",
|
||||
15*time.Second)
|
||||
}
|
||||
|
||||
const pollInterval = 200 * time.Millisecond
|
||||
|
||||
func waitForServer(t *testing.T, url, match string, timeout time.Duration) {
|
||||
// "health check" duplicated from x/tools/cmd/tipgodoc/tip.go
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
time.Sleep(pollInterval)
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
rbody, err := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err == nil && res.StatusCode == http.StatusOK {
|
||||
if bytes.Contains(rbody, []byte(match)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Fatalf("Server failed to respond in %v", timeout)
|
||||
}
|
||||
|
||||
func killAndWait(cmd *exec.Cmd) {
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
}
|
||||
|
||||
func init() {
|
||||
// TestWeb reinvokes the test binary (us) with -be-main
|
||||
// to simulate running the actual golangorg binary.
|
||||
if len(os.Args) >= 2 && os.Args[1] == "-be-main" {
|
||||
os.Args = os.Args[1:]
|
||||
os.Args[0] = "(golangorg)"
|
||||
main()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeb(t *testing.T) {
|
||||
h := NewHandler("../../_content", runtime.GOROOT())
|
||||
files, err := filepath.Glob("testdata/*.txt")
|
||||
|
@ -91,16 +24,3 @@ func TestWeb(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Regression tests to run against a production instance of golangorg.
|
||||
|
||||
var host = flag.String("regtest.host", "", "host to run regression test against")
|
||||
|
||||
func TestLiveServer(t *testing.T) {
|
||||
*host = strings.TrimSuffix(*host, "/")
|
||||
if *host == "" {
|
||||
t.Skip("regtest.host flag missing.")
|
||||
}
|
||||
|
||||
webtest.TestServer(t, "testdata/*.txt", *host)
|
||||
}
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
# Tests that can only run against the live server,
|
||||
# because they depend on production resources.
|
||||
|
||||
GET /dl/
|
||||
GET https://golang.org/dl/
|
||||
body contains href="/dl/go1.11.windows-amd64.msi"
|
||||
|
||||
GET /dl/?mode=json
|
||||
GET https://golang.org/dl/?mode=json
|
||||
body contains .windows-amd64.msi
|
||||
body !contains UA-
|
||||
|
||||
GET /s/go2design
|
||||
GET https://golang.org/s/go2design
|
||||
code == 302
|
||||
body ~ proposal.*Found
|
||||
body !contains UA-
|
||||
|
||||
POST /compile
|
||||
POST https://golang.org/compile
|
||||
postquery
|
||||
body=package main; func main() { print(6*7); }
|
||||
body == {"compile_errors":"","output":"42"}
|
||||
|
||||
POST /compile
|
||||
POST https://golang.org/compile
|
||||
postquery
|
||||
body=//empty
|
||||
body contains expected 'package', found 'EOF'
|
||||
body !contains UA-
|
||||
|
||||
POST /compile
|
||||
POST https://golang.org/compile
|
||||
postquery
|
||||
version=2
|
||||
body=package main; import ("fmt"; "time"); func main() {fmt.Print("A"); time.Sleep(time.Second); fmt.Print("B")}
|
||||
body == {"Errors":"","Events":[{"Message":"A","Kind":"stdout","Delay":0},{"Message":"B","Kind":"stdout","Delay":1000000000}]}
|
||||
|
||||
POST /share
|
||||
POST https://golang.org/share
|
||||
postbody
|
||||
package main
|
||||
body !contains UA-
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
GET /doc/devel/release
|
||||
GET https://golang.org/doc/devel/release
|
||||
header content-type == text/html; charset=utf-8
|
||||
trimbody contains
|
||||
<h2 id="go1.14">go1.14 (released 2020-02-25)</h2>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
GET /
|
||||
GET https://golang.org/
|
||||
body contains Go is an open source programming language
|
||||
body contains Binary distributions available for
|
||||
|
||||
|
@ -11,143 +11,143 @@ body contains Binary distributions available for
|
|||
body contains href="/golang.org/doc
|
||||
body !contains href="/doc
|
||||
|
||||
GET /change/75944e2e3a63
|
||||
GET https://golang.org/change/75944e2e3a63
|
||||
code == 302
|
||||
redirect contains bdb10cf
|
||||
body contains bdb10cf
|
||||
body !contains UA-
|
||||
|
||||
GET /cmd/compile/internal/amd64/
|
||||
GET https://golang.org/cmd/compile/internal/amd64/
|
||||
body contains href="/src/cmd/compile/internal/amd64/ssa.go"
|
||||
|
||||
GET /conduct
|
||||
GET https://golang.org/conduct
|
||||
body contains Project Stewards
|
||||
|
||||
GET /doc/
|
||||
GET https://golang.org/doc/
|
||||
body contains an introduction to using modules in a simple project
|
||||
|
||||
GET /doc/asm
|
||||
GET https://golang.org/doc/asm
|
||||
body ~ Quick Guide.*Assembler
|
||||
|
||||
GET /doc/debugging_with_gdb.html
|
||||
GET https://golang.org/doc/debugging_with_gdb.html
|
||||
redirect == /doc/gdb
|
||||
|
||||
GET /doc/devel/release
|
||||
GET https://golang.org/doc/devel/release
|
||||
body ~ go1\.14\.2\s+\(released 2020-04-08\)\s+includes\s+fixes to cgo, the go command, the runtime,
|
||||
|
||||
GET /doc/devel/release.html
|
||||
GET https://golang.org/doc/devel/release.html
|
||||
redirect == /doc/devel/release
|
||||
|
||||
GET /doc/faq
|
||||
GET https://golang.org/doc/faq
|
||||
body contains What is the purpose of the project
|
||||
|
||||
GET /doc/gdb
|
||||
GET https://golang.org/doc/gdb
|
||||
body contains Debugging Go Code
|
||||
|
||||
GET /doc/go1.16.html
|
||||
GET https://golang.org/doc/go1.16.html
|
||||
redirect == /doc/go1.16
|
||||
|
||||
GET /doc/go1.16
|
||||
GET https://golang.org/doc/go1.16
|
||||
body contains Go 1.16
|
||||
|
||||
GET /doc/go_spec
|
||||
GET https://golang.org/doc/go_spec
|
||||
redirect == /ref/spec
|
||||
|
||||
GET /doc/go_spec.html
|
||||
GET https://golang.org/doc/go_spec.html
|
||||
redirect == /ref/spec
|
||||
|
||||
GET /doc/go_spec.md
|
||||
GET https://golang.org/doc/go_spec.md
|
||||
redirect == /ref/spec
|
||||
|
||||
GET /doc/go_mem.html
|
||||
GET https://golang.org/doc/go_mem.html
|
||||
redirect == /ref/mem
|
||||
|
||||
GET /doc/go_mem.md
|
||||
GET https://golang.org/doc/go_mem.md
|
||||
redirect == /ref/mem
|
||||
|
||||
GET /doc/help.html
|
||||
GET https://golang.org/doc/help.html
|
||||
redirect == /help
|
||||
|
||||
GET /help/
|
||||
GET https://golang.org/help/
|
||||
redirect == /help
|
||||
|
||||
GET /help
|
||||
GET https://golang.org/help
|
||||
body contains Get help
|
||||
|
||||
GET /pkg/fmt/
|
||||
GET https://golang.org/pkg/fmt/
|
||||
body contains Package fmt implements formatted I/O
|
||||
|
||||
GET /src/fmt/
|
||||
GET https://golang.org/src/fmt/
|
||||
body contains scan_test.go
|
||||
|
||||
GET /src/fmt/print.go
|
||||
GET https://golang.org/src/fmt/print.go
|
||||
body contains // Println formats using
|
||||
|
||||
GET /pkg
|
||||
GET https://golang.org/pkg
|
||||
redirect == /pkg/
|
||||
|
||||
GET /pkg/
|
||||
GET https://golang.org/pkg/
|
||||
body contains Standard library
|
||||
body contains Package fmt implements formatted I/O
|
||||
body !contains internal/syscall
|
||||
body !contains cmd/gc
|
||||
|
||||
GET /pkg/?m=all
|
||||
GET https://golang.org/pkg/?m=all
|
||||
body contains Standard library
|
||||
body contains Package fmt implements formatted I/O
|
||||
body contains internal/syscall/?m=all
|
||||
body !contains cmd/gc
|
||||
|
||||
GET /pkg/bufio/
|
||||
GET https://golang.org/pkg/bufio/
|
||||
body contains href="/pkg/io/#Writer
|
||||
|
||||
GET /pkg/database/sql/
|
||||
GET https://golang.org/pkg/database/sql/
|
||||
body contains The number of connections currently in use; added in Go 1.11
|
||||
body contains The number of idle connections; added in Go 1.11
|
||||
|
||||
GET /cmd/compile/internal/amd64/
|
||||
GET https://golang.org/cmd/compile/internal/amd64/
|
||||
body contains href="/src/cmd/compile/internal/amd64/ssa.go"
|
||||
|
||||
GET /pkg/math/bits/
|
||||
GET https://golang.org/pkg/math/bits/
|
||||
body contains Added in Go 1.9
|
||||
|
||||
GET /pkg/net/
|
||||
GET https://golang.org/pkg/net/
|
||||
body contains // IPv6 scoped addressing zone; added in Go 1.1
|
||||
|
||||
GET /pkg/net/http/
|
||||
GET https://golang.org/pkg/net/http/
|
||||
body contains title="Added in Go 1.11"
|
||||
|
||||
GET /pkg/net/http/httptrace/
|
||||
GET https://golang.org/pkg/net/http/httptrace/
|
||||
body ~ Got1xxResponse.*// Go 1\.11
|
||||
body ~ GotFirstResponseByte func\(\)\s*$
|
||||
|
||||
GET /pkg/os/
|
||||
GET https://golang.org/pkg/os/
|
||||
body contains func Open
|
||||
|
||||
GET /pkg/strings/
|
||||
GET https://golang.org/pkg/strings/
|
||||
body contains href="/src/strings/strings.go"
|
||||
|
||||
GET /project
|
||||
GET https://golang.org/project
|
||||
body contains <li><a href="/doc/go1.14">Go 1.14</a> <small>(February 2020)</small></li>
|
||||
body contains <li><a href="/doc/go1.1">Go 1.1</a> <small>(May 2013)</small></li>
|
||||
|
||||
GET /project/
|
||||
GET https://golang.org/project/
|
||||
redirect == /project
|
||||
|
||||
GET /project/notexist
|
||||
GET https://golang.org/project/notexist
|
||||
code == 404
|
||||
|
||||
GET /ref/mem
|
||||
GET https://golang.org/ref/mem
|
||||
body contains Memory Model
|
||||
|
||||
GET /ref/spec
|
||||
GET https://golang.org/ref/spec
|
||||
body contains Go Programming Language Specification
|
||||
|
||||
GET /robots.txt
|
||||
GET https://golang.org/robots.txt
|
||||
body contains Disallow: /search
|
||||
body !contains UA-
|
||||
|
||||
GET /x/net
|
||||
GET https://golang.org/x/net
|
||||
code == 200
|
||||
body contains <meta name="go-import" content="golang.org/x/net git https://go.googlesource.com/net">
|
||||
body !contains UA-
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
GET /x/net
|
||||
GET https://golang.org/x/net
|
||||
code == 200
|
||||
body contains <meta name="go-import" content="golang.org/x/net git https://go.googlesource.com/net">
|
||||
body contains http-equiv="refresh" content="0; url=https://pkg.go.dev/golang.org/x/net">
|
||||
|
||||
GET /x/net/suffix
|
||||
GET https://golang.org/x/net/suffix
|
||||
code == 200
|
||||
body contains <meta name="go-import" content="golang.org/x/net git https://go.googlesource.com/net">
|
||||
body contains http-equiv="refresh" content="0; url=https://pkg.go.dev/golang.org/x/net/suffix">
|
||||
|
||||
GET /x/pkgsite
|
||||
GET https://golang.org/x/pkgsite
|
||||
code == 200
|
||||
body contains <meta name="go-import" content="golang.org/x/pkgsite git https://go.googlesource.com/pkgsite">
|
||||
body contains <a href="https://pkg.go.dev/golang.org/x/pkgsite">Redirecting to documentation...</a>
|
||||
body contains http-equiv="refresh" content="0; url=https://pkg.go.dev/golang.org/x/pkgsite">
|
||||
|
||||
GET /x/notexist
|
||||
GET https://golang.org/x/notexist
|
||||
code == 404
|
||||
|
||||
GET /x/
|
||||
GET https://golang.org/x/
|
||||
code == 307
|
||||
header location == https://pkg.go.dev/search?q=golang.org/x
|
||||
|
||||
GET /x/In%20Valid,X
|
||||
GET https://golang.org/x/In%20Valid,X
|
||||
code == 404
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# This script is meant to be run from Cloud Build as a substitute
|
||||
# for "gcloud app deploy", as in:
|
||||
#
|
||||
# go-app-deploy.sh app.yaml
|
||||
# go-app-deploy.sh [--project=name] app.yaml
|
||||
#
|
||||
# It should not be run by hand and is therefore not marked executable.
|
||||
#
|
||||
|
@ -25,18 +25,68 @@
|
|||
|
||||
set -e
|
||||
|
||||
project=golang-org
|
||||
case "$1" in
|
||||
--project=*)
|
||||
project=$(echo $1 | sed 's/--project=//')
|
||||
shift
|
||||
esac
|
||||
|
||||
yaml=app.yaml
|
||||
case "$1" in
|
||||
*.yaml)
|
||||
yaml=$1
|
||||
shift
|
||||
esac
|
||||
|
||||
if [ $# != 0 ]; then
|
||||
echo 'usage: go-app-deploy.sh [--project=name] path/to/app.yaml' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
promote=$(
|
||||
git cat-file -p 'HEAD' |
|
||||
awk '
|
||||
BEGIN { flag = "--no-promote" }
|
||||
/^Reviewed-on:/ { flag = "--no-promote" }
|
||||
/^Website-Publish:/ { flag = "--promote" }
|
||||
BEGIN { flag = "false" }
|
||||
/^Reviewed-on:/ { flag = "false" }
|
||||
/^Website-Publish:/ { flag = "true" }
|
||||
END {print flag}
|
||||
'
|
||||
)
|
||||
|
||||
version=$(
|
||||
git log -n1 --date='format:%Y-%m-%d-%H%M%S' --pretty='format:%cd-%h'
|
||||
)
|
||||
version=$(git log -n1 --date='format:%Y-%m-%d-%H%M%S' --pretty='format:%cd-%h')
|
||||
|
||||
service=$(awk '$1=="service:" {print $2}' $yaml)
|
||||
|
||||
servicedot="-$service-dot"
|
||||
if [ "$service" = default ]; then
|
||||
servicedot=""
|
||||
fi
|
||||
host="$version-dot$servicedot-$project.appspot.com"
|
||||
|
||||
echo "### deploying to https://$host"
|
||||
gcloud -q --project=$project app deploy -v $version --no-promote $yaml
|
||||
|
||||
curl --version
|
||||
|
||||
for i in 1 2 3 4 5; do
|
||||
if curl -s --fail --show-error "https://$host/_readycheck"; then
|
||||
echo '### site is up!'
|
||||
if $promote; then
|
||||
serving=$(gcloud app services describe --project=$project $service | grep ': 1.0')
|
||||
if [ "$serving" '>' "$version" ]; then
|
||||
echo "### serving version $serving is newer than our $version; not promoting"
|
||||
exit 1
|
||||
fi
|
||||
echo '### promoting'
|
||||
gcloud -q --project=$project app services set-traffic $service --splits=$version=1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
echo '### not healthy'
|
||||
curl "https://$host/_readycheck" # show response body
|
||||
done
|
||||
|
||||
echo "### failed to become healthy; giving up"
|
||||
exit 1
|
||||
|
||||
gcloud app deploy $promote -v $version "$@"
|
||||
|
|
|
@ -10,9 +10,11 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/website/go.dev/cmd/internal/site"
|
||||
"golang.org/x/website/internal/webtest"
|
||||
)
|
||||
|
||||
var discoveryHosts = map[string]string{
|
||||
|
@ -27,13 +29,14 @@ func main() {
|
|||
// Running in repo root.
|
||||
dir = "go.dev"
|
||||
}
|
||||
godev, err := site.Load(dir)
|
||||
|
||||
h, err := NewHandler(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
http.Handle("/", addCSP(http.FileServer(godev)))
|
||||
http.Handle("/explore/", http.StripPrefix("/explore/", redirectHosts(discoveryHosts)))
|
||||
http.Handle("learn.go.dev/", http.HandlerFunc(redirectLearn))
|
||||
|
||||
h = webtest.HandlerWithCheck(h, "/_readycheck",
|
||||
filepath.Join(dir, "cmd/frontend/testdata/*.txt"))
|
||||
|
||||
addr := ":" + listenPort()
|
||||
if addr == ":0" {
|
||||
|
@ -45,7 +48,19 @@ func main() {
|
|||
}
|
||||
defer l.Close()
|
||||
log.Printf("Listening on http://%v/\n", l.Addr().String())
|
||||
log.Print(http.Serve(l, nil))
|
||||
log.Print(http.Serve(l, h))
|
||||
}
|
||||
|
||||
func NewHandler(dir string) (http.Handler, error) {
|
||||
godev, err := site.Load(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", addCSP(http.FileServer(godev)))
|
||||
mux.Handle("/explore/", http.StripPrefix("/explore/", redirectHosts(discoveryHosts)))
|
||||
mux.Handle("learn.go.dev/", http.HandlerFunc(redirectLearn))
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
func redirectLearn(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2021 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 main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/website/internal/webtest"
|
||||
)
|
||||
|
||||
func TestWeb(t *testing.T) {
|
||||
h, err := NewHandler("../..")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
webtest.TestHandler(t, "testdata/*.txt", h)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
GET https://go.dev/
|
||||
body contains <h2 class="WhoUses-headerH2">Companies using Go</h2>
|
||||
|
||||
GET https://go.dev/solutions/google/
|
||||
body ~ it\s+has\s+powered\s+many\s+projects\s+at\s+Google.
|
|
@ -115,26 +115,6 @@ func serveJSON(w http.ResponseWriter, r *http.Request, d listTemplateData) {
|
|||
}
|
||||
}
|
||||
|
||||
// googleCN reports whether request r is considered
|
||||
// to be served from golang.google.cn.
|
||||
// TODO: This is duplicated within internal/proxy. Move to a common location.
|
||||
func googleCN(r *http.Request) bool {
|
||||
if r.FormValue("googlecn") != "" {
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(r.Host, ".cn") {
|
||||
return true
|
||||
}
|
||||
if !env.CheckCountry() {
|
||||
return false
|
||||
}
|
||||
switch r.Header.Get("X-Appengine-Country") {
|
||||
case "", "ZZ", "CN":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h server) uploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
checkCountry = boolEnv("GOLANGORG_CHECK_COUNTRY")
|
||||
enforceHosts = boolEnv("GOLANGORG_ENFORCE_HOSTS")
|
||||
requireDLSecretKey = boolEnv("GOLANGORG_REQUIRE_DL_SECRET_KEY")
|
||||
)
|
||||
|
@ -25,11 +24,6 @@ func RequireDLSecretKey() bool {
|
|||
return requireDLSecretKey
|
||||
}
|
||||
|
||||
// CheckCountry reports whether country restrictions should be enforced.
|
||||
func CheckCountry() bool {
|
||||
return checkCountry
|
||||
}
|
||||
|
||||
// EnforceHosts reports whether host filtering should be enforced.
|
||||
func EnforceHosts() bool {
|
||||
return enforceHosts
|
||||
|
|
|
@ -15,10 +15,9 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/website/internal/env"
|
||||
"golang.org/x/website/internal/web"
|
||||
)
|
||||
|
||||
const playgroundURL = "https://play.golang.org"
|
||||
|
@ -124,7 +123,7 @@ func flatten(seq []Event) string {
|
|||
}
|
||||
|
||||
func share(w http.ResponseWriter, r *http.Request) {
|
||||
if googleCN(r) {
|
||||
if web.GoogleCN(r) {
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
@ -151,22 +150,3 @@ func share(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
// googleCN reports whether request r is considered
|
||||
// to be served from golang.google.cn.
|
||||
func googleCN(r *http.Request) bool {
|
||||
if r.FormValue("googlecn") != "" {
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(r.Host, ".cn") {
|
||||
return true
|
||||
}
|
||||
if !env.CheckCountry() {
|
||||
return false
|
||||
}
|
||||
switch r.Header.Get("X-Appengine-Country") {
|
||||
case "", "ZZ", "CN":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2021 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 web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GoogleCN reports whether request r is considered to be arriving from China.
|
||||
// Typically that means the request is for host golang.google.cn,
|
||||
// but we also report true for requests that set googlecn=1 as a query parameter
|
||||
// and requests that App Engine geolocates in China or in “unknown country.”
|
||||
func GoogleCN(r *http.Request) bool {
|
||||
if r.FormValue("googlecn") != "" {
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(r.Host, ".cn") {
|
||||
return true
|
||||
}
|
||||
switch r.Header.Get("X-Appengine-Country") {
|
||||
case "ZZ", "CN":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -36,10 +36,6 @@ type Site struct {
|
|||
|
||||
Templates *template.Template
|
||||
|
||||
// GoogleCN reports whether this request should be marked GoogleCN.
|
||||
// If the function is nil, no requests are marked GoogleCN.
|
||||
GoogleCN func(*http.Request) bool
|
||||
|
||||
// GoogleAnalytics optionally adds Google Analytics via the provided
|
||||
// tracking ID to each page.
|
||||
GoogleAnalytics string
|
||||
|
@ -122,7 +118,7 @@ type Page struct {
|
|||
Data interface{} // data to be rendered into page frame
|
||||
|
||||
// Filled in automatically by ServePage
|
||||
GoogleCN bool // page is being served from golang.google.cn
|
||||
GoogleCN bool // served on golang.google.cn
|
||||
GoogleAnalytics string // Google Analytics tag
|
||||
Version string // current Go version
|
||||
|
||||
|
@ -135,7 +131,7 @@ func (s *Site) fullPage(r *http.Request, page Page) Page {
|
|||
page.TabTitle = page.Title
|
||||
}
|
||||
page.Version = runtime.Version()
|
||||
page.GoogleCN = s.googleCN(r)
|
||||
page.GoogleCN = GoogleCN(r)
|
||||
page.GoogleAnalytics = s.GoogleAnalytics
|
||||
page.site = s
|
||||
return page
|
||||
|
@ -413,7 +409,3 @@ func (s *Site) serveRawText(w http.ResponseWriter, text []byte) {
|
|||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write(text)
|
||||
}
|
||||
|
||||
func (s *Site) googleCN(r *http.Request) bool {
|
||||
return s.GoogleCN != nil && s.GoogleCN(r)
|
||||
}
|
||||
|
|
|
@ -164,6 +164,26 @@ import (
|
|||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// HandlerWithCheck returns an http.Handler that responds to each request
|
||||
// by running the test script files mached by glob against the handler h.
|
||||
// If the tests pass, the returned http.Handler responds with status code 200.
|
||||
// If they fail, it prints the details and responds with status code 503
|
||||
// (service unavailable).
|
||||
func HandlerWithCheck(h http.Handler, path, glob string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == path {
|
||||
err := CheckHandler(glob, h)
|
||||
if err != nil {
|
||||
http.Error(w, "webtest.CheckHandler failed:\n"+err.Error()+"\n", http.StatusInternalServerError)
|
||||
} else {
|
||||
fmt.Fprintf(w, "ok\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// CheckHandler runs the test script files matched by glob
|
||||
// against the handler h. If any errors are encountered,
|
||||
// CheckHandler returns an error listing the problems.
|
||||
|
|
|
@ -12,8 +12,10 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "golang.org/x/tools/playground"
|
||||
"golang.org/x/website/internal/webtest"
|
||||
)
|
||||
|
||||
func gaeMain() {
|
||||
|
@ -36,7 +38,11 @@ func gaeMain() {
|
|||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||
|
||||
h := webtest.HandlerWithCheck(http.DefaultServeMux, "/_readycheck",
|
||||
filepath.Join(root, "testdata/*.txt"))
|
||||
|
||||
log.Fatal(http.ListenAndServe(":"+port, h))
|
||||
}
|
||||
|
||||
// gaePrepContent returns a Reader that produces the content from the given
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"time"
|
||||
|
||||
"golang.org/x/tools/playground/socket"
|
||||
"golang.org/x/website/internal/webtest"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -126,6 +127,9 @@ func main() {
|
|||
|
||||
registerStatic(root)
|
||||
|
||||
h := webtest.HandlerWithCheck(http.DefaultServeMux, "/_readycheck",
|
||||
filepath.Join(root, "testdata/*.txt"))
|
||||
|
||||
go func() {
|
||||
url := "http://" + httpAddr
|
||||
if waitServer(url) && *openBrowser && startBrowser(url) {
|
||||
|
@ -134,7 +138,7 @@ func main() {
|
|||
log.Printf("Please open your web browser and visit %s", url)
|
||||
}
|
||||
}()
|
||||
log.Fatal(http.ListenAndServe(httpAddr, nil))
|
||||
log.Fatal(http.ListenAndServe(httpAddr, h))
|
||||
}
|
||||
|
||||
// registerStatic registers handlers to serve static content
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2021 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 main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/website/internal/webtest"
|
||||
)
|
||||
|
||||
func TestWeb(t *testing.T) {
|
||||
if err := initTour(".", "SocketTransport"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
http.HandleFunc("/", rootHandler)
|
||||
http.HandleFunc("/lesson/", lessonHandler)
|
||||
registerStatic(".")
|
||||
|
||||
webtest.TestHandler(t, "testdata/*.txt", http.DefaultServeMux)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
GET https://tour.golang.org/
|
||||
body contains >A Tour of Go<
|
||||
|
Загрузка…
Ссылка в новой задаче