|
| 1 | +// Copyright 2011 The Go Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +// +build !appengine |
| 6 | + |
| 7 | +package main |
| 8 | + |
| 9 | +import ( |
| 10 | + "flag" |
| 11 | + "fmt" |
| 12 | + "go/build" |
| 13 | + "io" |
| 14 | + "log" |
| 15 | + "net" |
| 16 | + "net/http" |
| 17 | + "net/url" |
| 18 | + "os" |
| 19 | + "os/exec" |
| 20 | + "path/filepath" |
| 21 | + "runtime" |
| 22 | + "strings" |
| 23 | + "time" |
| 24 | + |
| 25 | + "golang.org/x/tools/playground/socket" |
| 26 | + |
| 27 | + // Imports so that go build/install automatically installs them. |
| 28 | + _ "golang.org/x/tour/pic" |
| 29 | + _ "golang.org/x/tour/tree" |
| 30 | + _ "golang.org/x/tour/wc" |
| 31 | +) |
| 32 | + |
| 33 | +const ( |
| 34 | + basePkg = "golang.org/x/tour/" |
| 35 | + socketPath = "/socket" |
| 36 | +) |
| 37 | + |
| 38 | +var ( |
| 39 | + httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on") |
| 40 | + openBrowser = flag.Bool("openbrowser", true, "open browser automatically") |
| 41 | +) |
| 42 | + |
| 43 | +var ( |
| 44 | + // GOPATH containing the tour packages |
| 45 | + gopath = os.Getenv("GOPATH") |
| 46 | + |
| 47 | + httpAddr string |
| 48 | +) |
| 49 | + |
| 50 | +// isRoot reports whether path is the root directory of the tour tree. |
| 51 | +// To be the root, it must have content and template subdirectories. |
| 52 | +func isRoot(path string) bool { |
| 53 | + _, err := os.Stat(filepath.Join(path, "content", "welcome.article")) |
| 54 | + if err == nil { |
| 55 | + _, err = os.Stat(filepath.Join(path, "template", "index.tmpl")) |
| 56 | + } |
| 57 | + return err == nil |
| 58 | +} |
| 59 | + |
| 60 | +func findRoot() (string, error) { |
| 61 | + ctx := build.Default |
| 62 | + p, err := ctx.Import(basePkg, "", build.FindOnly) |
| 63 | + if err == nil && isRoot(p.Dir) { |
| 64 | + return p.Dir, nil |
| 65 | + } |
| 66 | + tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour") |
| 67 | + ctx.GOPATH = tourRoot |
| 68 | + p, err = ctx.Import(basePkg, "", build.FindOnly) |
| 69 | + if err == nil && isRoot(tourRoot) { |
| 70 | + gopath = tourRoot |
| 71 | + return tourRoot, nil |
| 72 | + } |
| 73 | + return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH") |
| 74 | +} |
| 75 | + |
| 76 | +func main() { |
| 77 | + flag.Parse() |
| 78 | + |
| 79 | + // find and serve the go tour files |
| 80 | + root, err := findRoot() |
| 81 | + if err != nil { |
| 82 | + log.Fatalf("Couldn't find tour files: %v", err) |
| 83 | + } |
| 84 | + |
| 85 | + log.Println("Serving content from", root) |
| 86 | + |
| 87 | + host, port, err := net.SplitHostPort(*httpListen) |
| 88 | + if err != nil { |
| 89 | + log.Fatal(err) |
| 90 | + } |
| 91 | + if host == "" { |
| 92 | + host = "localhost" |
| 93 | + } |
| 94 | + if host != "127.0.0.1" && host != "localhost" { |
| 95 | + log.Print(localhostWarning) |
| 96 | + } |
| 97 | + httpAddr = host + ":" + port |
| 98 | + |
| 99 | + if err := initTour(root, "SocketTransport"); err != nil { |
| 100 | + log.Fatal(err) |
| 101 | + } |
| 102 | + |
| 103 | + http.HandleFunc("/", rootHandler) |
| 104 | + http.HandleFunc("/lesson/", lessonHandler) |
| 105 | + |
| 106 | + origin := &url.URL{Scheme: "http", Host: host + ":" + port} |
| 107 | + http.Handle(socketPath, socket.NewHandler(origin)) |
| 108 | + |
| 109 | + // Keep these static file handlers in sync with ../app.yaml. |
| 110 | + static := http.FileServer(http.Dir(root)) |
| 111 | + http.Handle("/content/img/", static) |
| 112 | + http.Handle("/static/", static) |
| 113 | + imgDir := filepath.Join(root, "static", "img") |
| 114 | + http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir))) |
| 115 | + |
| 116 | + go func() { |
| 117 | + url := "http://" + httpAddr |
| 118 | + if waitServer(url) && *openBrowser && startBrowser(url) { |
| 119 | + log.Printf("A browser window should open. If not, please visit %s", url) |
| 120 | + } else { |
| 121 | + log.Printf("Please open your web browser and visit %s", url) |
| 122 | + } |
| 123 | + }() |
| 124 | + log.Fatal(http.ListenAndServe(httpAddr, nil)) |
| 125 | +} |
| 126 | + |
| 127 | +// rootHandler returns a handler for all the requests except the ones for lessons. |
| 128 | +func rootHandler(w http.ResponseWriter, r *http.Request) { |
| 129 | + if err := renderUI(w); err != nil { |
| 130 | + log.Println(err) |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +// lessonHandler handler the HTTP requests for lessons. |
| 135 | +func lessonHandler(w http.ResponseWriter, r *http.Request) { |
| 136 | + lesson := strings.TrimPrefix(r.URL.Path, "/lesson/") |
| 137 | + if err := writeLesson(lesson, w); err != nil { |
| 138 | + if err == lessonNotFound { |
| 139 | + http.NotFound(w, r) |
| 140 | + } else { |
| 141 | + log.Println(err) |
| 142 | + } |
| 143 | + } |
| 144 | +} |
| 145 | + |
| 146 | +const localhostWarning = ` |
| 147 | +WARNING! WARNING! WARNING! |
| 148 | +
|
| 149 | +I appear to be listening on an address that is not localhost. |
| 150 | +Anyone with access to this address and port will have access |
| 151 | +to this machine as the user running gotour. |
| 152 | +
|
| 153 | +If you don't understand this message, hit Control-C to terminate this process. |
| 154 | +
|
| 155 | +WARNING! WARNING! WARNING! |
| 156 | +` |
| 157 | + |
| 158 | +type response struct { |
| 159 | + Output string `json:"output"` |
| 160 | + Errors string `json:"compile_errors"` |
| 161 | +} |
| 162 | + |
| 163 | +func init() { |
| 164 | + socket.Environ = environ |
| 165 | +} |
| 166 | + |
| 167 | +// environ returns the original execution environment with GOPATH |
| 168 | +// replaced (or added) with the value of the global var gopath. |
| 169 | +func environ() (env []string) { |
| 170 | + for _, v := range os.Environ() { |
| 171 | + if !strings.HasPrefix(v, "GOPATH=") { |
| 172 | + env = append(env, v) |
| 173 | + } |
| 174 | + } |
| 175 | + env = append(env, "GOPATH="+gopath) |
| 176 | + return |
| 177 | +} |
| 178 | + |
| 179 | +// waitServer waits some time for the http Server to start |
| 180 | +// serving url. The return value reports whether it starts. |
| 181 | +func waitServer(url string) bool { |
| 182 | + tries := 20 |
| 183 | + for tries > 0 { |
| 184 | + resp, err := http.Get(url) |
| 185 | + if err == nil { |
| 186 | + resp.Body.Close() |
| 187 | + return true |
| 188 | + } |
| 189 | + time.Sleep(100 * time.Millisecond) |
| 190 | + tries-- |
| 191 | + } |
| 192 | + return false |
| 193 | +} |
| 194 | + |
| 195 | +// startBrowser tries to open the URL in a browser, and returns |
| 196 | +// whether it succeed. |
| 197 | +func startBrowser(url string) bool { |
| 198 | + // try to start the browser |
| 199 | + var args []string |
| 200 | + switch runtime.GOOS { |
| 201 | + case "darwin": |
| 202 | + args = []string{"open"} |
| 203 | + case "windows": |
| 204 | + args = []string{"cmd", "/c", "start"} |
| 205 | + default: |
| 206 | + args = []string{"xdg-open"} |
| 207 | + } |
| 208 | + cmd := exec.Command(args[0], append(args[1:], url)...) |
| 209 | + return cmd.Start() == nil |
| 210 | +} |
| 211 | + |
| 212 | +// prepContent for the local tour simply returns the content as-is. |
| 213 | +func prepContent(r io.Reader) io.Reader { return r } |
| 214 | + |
| 215 | +// socketAddr returns the WebSocket handler address. |
| 216 | +func socketAddr() string { return "ws://" + httpAddr + socketPath } |
0 commit comments