tour: deploy with App Engine Standard on Go 1.11

This change upgrades the deployment of tour to use the newer
Go 1.11 runtime. As part of that, the appengine build tag is
removed (it's no longer set by App Engine), and the GAE_ENV
environment variable is used to detect when tour is being run
in App Engine mode.

Set an environment variable in app.yaml to configure the
x/tools/godoc/golangorgenv package appropriately.

Factor out the static file handlers in local.go, but keep
static file handlers in app.yaml for improved latency across
global regions.

Updates golang/go#30486

Change-Id: Ia5bc88aab34fd07bf6ff0785da831180f509156f
Reviewed-on: https://go-review.googlesource.com/c/tour/+/165537
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/.gcloudignore b/.gcloudignore
new file mode 100644
index 0000000..0030d4f
--- /dev/null
+++ b/.gcloudignore
@@ -0,0 +1,2 @@
+.gcloudignore
+.git
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index d8c2c11..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-last-change
-*.orig
-*.rej
diff --git a/app.yaml b/app.yaml
index e6552eb..fcd8f17 100644
--- a/app.yaml
+++ b/app.yaml
@@ -1,12 +1,14 @@
 service: tour
-runtime: go
-api_version: go1
+runtime: go111
+
+env_variables:
+  GOLANGORG_CHECK_COUNTRY: true
 
 default_expiration: "7d"
 
-handlers:
-
 # Keep these static file handlers in sync with local.go.
+# They're here for improved latency across global regions.
+handlers:
 - url: /favicon.ico
   static_files: static/img/favicon.ico
   upload: static/img/favicon.ico
@@ -16,11 +18,4 @@
   secure: always
 - url: /static
   static_dir: static
-  application_readable: true
   secure: always
-
-- url: /(.*|list|lesson/.*|compile|fmt|script\.js)
-  script: _go_app
-  secure: always
-
-nobuild_files: (solutions|content)/.*
diff --git a/appengine.go b/appengine.go
index 4767a2c..5db885b 100644
--- a/appengine.go
+++ b/appengine.go
@@ -2,56 +2,43 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build appengine
-
 package main
 
 import (
 	"bufio"
 	"bytes"
 	"io"
+	"log"
 	"net/http"
-	"strings"
-
-	"appengine"
+	"os"
 
 	_ "golang.org/x/tools/playground"
 )
 
-const runUrl = "https://golang.org/compile"
-
-func init() {
-	http.Handle("/lesson/", hstsHandler(lessonHandler))
-	http.Handle("/", hstsHandler(rootHandler))
+func gaeMain() {
+	prepContent = gaePrepContent
+	socketAddr = gaeSocketAddr
 
 	if err := initTour(".", "HTTPTransport"); err != nil {
-		panic(err)
+		log.Fatal(err)
 	}
+
+	http.Handle("/", hstsHandler(rootHandler))
+	http.Handle("/lesson/", hstsHandler(lessonHandler))
+
+	registerStatic(".")
+
+	port := os.Getenv("PORT")
+	if port == "" {
+		port = "8080"
+	}
+	log.Fatal(http.ListenAndServe(":"+port, nil))
 }
 
-func rootHandler(w http.ResponseWriter, r *http.Request) {
-	c := appengine.NewContext(r)
-	if err := renderUI(w); err != nil {
-		c.Criticalf("UI render: %v", err)
-	}
-}
-
-func lessonHandler(w http.ResponseWriter, r *http.Request) {
-	c := appengine.NewContext(r)
-	lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
-	if err := writeLesson(lesson, w); err != nil {
-		if err == lessonNotFound {
-			http.NotFound(w, r)
-		} else {
-			c.Criticalf("tour render: %v", err)
-		}
-	}
-}
-
-// prepContent returns a Reader that produces the content from the given
+// gaePrepContent returns a Reader that produces the content from the given
 // Reader, but strips the prefix "#appengine: " from each line. It also drops
 // any non-blank like that follows a series of 1 or more lines with the prefix.
-func prepContent(in io.Reader) io.Reader {
+func gaePrepContent(in io.Reader) io.Reader {
 	var prefix = []byte("#appengine: ")
 	out, w := io.Pipe()
 	go func() {
@@ -84,9 +71,9 @@
 	return out
 }
 
-// socketAddr returns the WebSocket handler address.
+// gaeSocketAddr returns the WebSocket handler address.
 // The App Engine version does not provide a WebSocket handler.
-func socketAddr() string { return "" }
+func gaeSocketAddr() string { return "" }
 
 // hstsHandler wraps an http.HandlerFunc such that it sets the HSTS header.
 func hstsHandler(fn http.HandlerFunc) http.Handler {
diff --git a/local.go b/local.go
index 170dcf0..21421af 100644
--- a/local.go
+++ b/local.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build !appengine
-
 package main
 
 import (
@@ -31,7 +29,7 @@
 )
 
 const (
-	basePkg    = "golang.org/x/tour/"
+	basePkg    = "golang.org/x/tour"
 	socketPath = "/socket"
 )
 
@@ -76,6 +74,12 @@
 func main() {
 	flag.Parse()
 
+	if os.Getenv("GAE_ENV") == "standard" {
+		log.Println("running in App Engine Standard mode")
+		gaeMain()
+		return
+	}
+
 	// find and serve the go tour files
 	root, err := findRoot()
 	if err != nil {
@@ -106,12 +110,7 @@
 	origin := &url.URL{Scheme: "http", Host: host + ":" + port}
 	http.Handle(socketPath, socket.NewHandler(origin))
 
-	// Keep these static file handlers in sync with app.yaml.
-	static := http.FileServer(http.Dir(root))
-	http.Handle("/content/img/", static)
-	http.Handle("/static/", static)
-	imgDir := filepath.Join(root, "static", "img")
-	http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))
+	registerStatic(root)
 
 	go func() {
 		url := "http://" + httpAddr
@@ -124,6 +123,16 @@
 	log.Fatal(http.ListenAndServe(httpAddr, nil))
 }
 
+// registerStatic registers handlers to serve static content
+// from the directory root.
+func registerStatic(root string) {
+	// Keep these static file handlers in sync with app.yaml.
+	http.Handle("/favicon.ico", http.FileServer(http.Dir(filepath.Join(root, "static", "img"))))
+	static := http.FileServer(http.Dir(root))
+	http.Handle("/content/img/", static)
+	http.Handle("/static/", static)
+}
+
 // rootHandler returns a handler for all the requests except the ones for lessons.
 func rootHandler(w http.ResponseWriter, r *http.Request) {
 	if err := renderUI(w); err != nil {
@@ -146,7 +155,8 @@
 const localhostWarning = `
 WARNING!  WARNING!  WARNING!
 
-I appear to be listening on an address that is not localhost.
+The tour server appears to be listening on an address that is
+not localhost and is configured to run code snippets locally.
 Anyone with access to this address and port will have access
 to this machine as the user running gotour.
 
@@ -210,7 +220,7 @@
 }
 
 // prepContent for the local tour simply returns the content as-is.
-func prepContent(r io.Reader) io.Reader { return r }
+var prepContent = func(r io.Reader) io.Reader { return r }
 
 // socketAddr returns the WebSocket handler address.
-func socketAddr() string { return "ws://" + httpAddr + socketPath }
+var socketAddr = func() string { return "ws://" + httpAddr + socketPath }