blob: c11c415419ba76e1f3fea8c434f1a9a370731833 [file] [log] [blame]
// Copyright 2011 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 tour
import (
"flag"
"html/template"
"io"
"io/fs"
"log"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"runtime"
"strings"
"time"
"golang.org/x/tools/playground/socket"
"golang.org/x/website/internal/webtest"
)
const (
socketPath = "/socket"
)
var (
httpListen *string
openBrowser *bool
httpAddr string
)
func Main() {
httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
flag.Parse()
host, port, err := net.SplitHostPort(*httpListen)
if err != nil {
log.Fatal(err)
}
if host == "" {
host = "localhost"
}
if host != "127.0.0.1" && host != "localhost" {
log.Print(localhostWarning)
}
httpAddr = host + ":" + port
if err := initTour(http.DefaultServeMux, "SocketTransport"); err != nil {
log.Fatal(err)
}
http.HandleFunc("/", rootHandler)
http.HandleFunc("/_/fmt", fmtHandler)
fs := http.FileServer(http.FS(contentTour))
http.Handle("/favicon.ico", fs)
http.Handle("/images/", fs)
origin := &url.URL{Scheme: "http", Host: host + ":" + port}
http.Handle(socketPath, socket.NewHandler(origin))
h := webtest.HandlerWithCheck(http.DefaultServeMux, "/_readycheck",
os.DirFS("."), "tour/testdata/*.txt")
go func() {
url := "http://" + httpAddr
if waitServer(url) && *openBrowser && startBrowser(url) {
log.Printf("A browser window should open. If not, please visit %s", url)
} else {
log.Printf("Please open your web browser and visit %s", url)
}
}()
log.Fatal(http.ListenAndServe(httpAddr, &logging{h}))
}
type logging struct {
h http.Handler
}
func (l *logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
println(r.URL.Path)
l.h.ServeHTTP(w, r)
}
func must(fsys fs.FS, err error) fs.FS {
if err != nil {
panic(err)
}
return fsys
}
// rootHandler returns a handler for all the requests except the ones for lessons.
func rootHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
http.Redirect(w, r, "/tour/", http.StatusFound)
return
}
if err := renderUI(w); err != nil {
log.Println(err)
}
}
// lessonHandler handler the HTTP requests for lessons.
func lessonHandler(w http.ResponseWriter, r *http.Request) {
lesson := strings.TrimPrefix(r.URL.Path, "/tour/lesson/")
if err := writeLesson(lesson, w); err != nil {
if err == lessonNotFound {
http.NotFound(w, r)
} else {
log.Println(err)
}
}
}
const localhostWarning = `
WARNING! WARNING! WARNING!
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.
If you don't understand this message, hit Control-C to terminate this process.
WARNING! WARNING! WARNING!
`
// waitServer waits some time for the http Server to start
// serving url. The return value reports whether it starts.
func waitServer(url string) bool {
tries := 20
for tries > 0 {
resp, err := http.Get(url)
if err == nil {
resp.Body.Close()
return true
}
time.Sleep(100 * time.Millisecond)
tries--
}
return false
}
// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
// try to start the browser
var args []string
switch runtime.GOOS {
case "darwin":
args = []string{"open"}
case "windows":
args = []string{"cmd", "/c", "start"}
default:
args = []string{"xdg-open"}
}
cmd := exec.Command(args[0], append(args[1:], url)...)
return cmd.Start() == nil
}
// prepContent for the local tour simply returns the content as-is.
var prepContent = func(r io.Reader) io.Reader { return r }
// socketAddr returns the WebSocket handler address.
var socketAddr = func() string { return "ws://" + httpAddr + socketPath }
// analyticsHTML is optional analytics HTML to insert at the beginning of <head>.
var analyticsHTML template.HTML