blob: d06b7649e333b6bce9db039f91f3a087dd9dba88 [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 main
import (
"bytes"
"flag"
"fmt"
"go/build"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
// Imports so that goinstall automatically installs them.
_ "code.google.com/p/go-tour/pic"
_ "code.google.com/p/go-tour/tree"
_ "code.google.com/p/go-tour/wc"
)
const basePkg = "code.google.com/p/go-tour/"
var (
httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
htmlOutput = flag.Bool("html", false, "render program output as HTML")
)
var (
// a source of numbers, for naming temporary files
uniq = make(chan int)
)
func main() {
flag.Parse()
// source of unique numbers
go func() {
for i := 0; ; i++ {
uniq <- i
}
}()
// find and serve the go tour files
p, err := build.Default.Import(basePkg, "", build.FindOnly)
if err != nil {
log.Fatalf("Couldn't find tour files: %v", err)
}
root := p.Dir
log.Println("Serving content from", root)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/favicon.ico" || r.URL.Path == "/" {
fn := filepath.Join(root, "static", r.URL.Path[1:])
http.ServeFile(w, r, fn)
return
}
http.Error(w, "not found", 404)
})
http.Handle("/static/", http.FileServer(http.Dir(root)))
http.Handle("/talks/", http.FileServer(http.Dir(root)))
http.HandleFunc("/kill", kill)
if !strings.HasPrefix(*httpListen, "127.0.0.1") &&
!strings.HasPrefix(*httpListen, "localhost") {
log.Print(localhostWarning)
}
log.Printf("Open your web browser and visit http://%s/", *httpListen)
log.Fatal(http.ListenAndServe(*httpListen, nil))
}
const localhostWarning = `
WARNING! WARNING! WARNING!
I appear to be listening on an address that is not localhost.
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!
`
var running struct {
sync.Mutex
cmd *exec.Cmd
}
func stopRun() {
running.Lock()
if running.cmd != nil {
running.cmd.Process.Kill()
running.cmd = nil
}
running.Unlock()
}
func kill(w http.ResponseWriter, r *http.Request) {
stopRun()
}
var (
commentRe = regexp.MustCompile(`(?m)^#.*\n`)
tmpdir string
)
func init() {
// find real temporary directory (for rewriting filename in output)
var err error
tmpdir, err = filepath.EvalSymlinks(os.TempDir())
if err != nil {
log.Fatal(err)
}
}
func compile(req *http.Request) (out []byte, err error) {
stopRun()
// x is the base name for .go, .6, executable files
x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq))
src := x + ".go"
bin := x
if runtime.GOOS == "windows" {
bin += ".exe"
}
// rewrite filename in error output
defer func() {
if err != nil {
// drop messages from the go tool like '# _/compile0'
out = commentRe.ReplaceAll(out, nil)
}
out = bytes.Replace(out, []byte(src+":"), []byte("main.go:"), -1)
}()
// write body to x.go
body := []byte(req.FormValue("body"))
defer os.Remove(src)
if err = ioutil.WriteFile(src, body, 0666); err != nil {
return
}
// build x.go, creating x
dir, file := filepath.Split(src)
out, err = run(dir, "go", "build", "-o", bin, file)
defer os.Remove(bin)
if err != nil {
return
}
// run x
return run("", bin)
}
// run executes the specified command and returns its output and an error.
func run(dir string, args ...string) ([]byte, error) {
var buf bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = dir
cmd.Stdout = &buf
cmd.Stderr = cmd.Stdout
// Start command and leave in 'running'.
running.Lock()
if running.cmd != nil {
defer running.Unlock()
return nil, fmt.Errorf("already running %s", running.cmd.Path)
}
if err := cmd.Start(); err != nil {
running.Unlock()
return nil, err
}
running.cmd = cmd
running.Unlock()
// Wait for the command. Clean up,
err := cmd.Wait()
running.Lock()
if running.cmd == cmd {
running.cmd = nil
}
running.Unlock()
return buf.Bytes(), err
}