// Copyright 2010 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" "exec" "flag" "http" "io" "io/ioutil" "log" "os" "runtime" "strconv" "template" ) 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) // the architecture-identifying character of the tool chain, 5, 6, or 8 archChar string ) func main() { flag.Parse() // set archChar switch runtime.GOARCH { case "arm": archChar = "5" case "amd64": archChar = "6" case "386": archChar = "8" default: log.Exitln("unrecognized GOARCH:", runtime.GOARCH) } // source of unique numbers go func() { for i := 0; ; i++ { uniq <- i } }() http.HandleFunc("/", FrontPage) http.HandleFunc("/compile", Compile) log.Exit(http.ListenAndServe(*httpListen, nil)) } // FrontPage is an HTTP handler that renders the goplay interface. // If a filename is supplied in the path component of the URI, // its contents will be put in the interface's text area. // Otherwise, the default "hello, world" program is displayed. func FrontPage(w http.ResponseWriter, req *http.Request) { data, err := ioutil.ReadFile(req.URL.Path[1:]) if err != nil { data = helloWorld } frontPage.Execute(data, w) } // Compile is an HTTP handler that reads Go source code from the request, // compiles and links the code (returning any errors), runs the program, // and sends the program's output as the HTTP response. func Compile(w http.ResponseWriter, req *http.Request) { // x is the base name for .go, .6, executable files x := "/tmp/compile" + strconv.Itoa(<-uniq) // write request Body to x.go f, err := os.Open(x+".go", os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { error(w, nil, err) return } defer os.Remove(x + ".go") defer f.Close() _, err = io.Copy(f, req.Body) if err != nil { error(w, nil, err) return } f.Close() // build x.go, creating x.6 out, err := run(archChar+"g", "-o", x+"."+archChar, x+".go") defer os.Remove(x + "." + archChar) if err != nil { error(w, out, err) return } // link x.6, creating x (the program binary) out, err = run(archChar+"l", "-o", x, x+"."+archChar) defer os.Remove(x) if err != nil { error(w, out, err) return } // run x out, err = run(x) if err != nil { error(w, out, err) } // write the output of x as the http response if *htmlOutput { w.Write(out) } else { output.Execute(out, w) } } // error writes compile, link, or runtime errors to the HTTP connection. // The JavaScript interface uses the 404 status code to identify the error. func error(w http.ResponseWriter, out []byte, err os.Error) { w.WriteHeader(404) if out != nil { output.Execute(out, w) } else { output.Execute(err.String(), w) } } // run executes the specified command and returns its output and an error. func run(cmd ...string) ([]byte, os.Error) { // find the specified binary bin, err := exec.LookPath(cmd[0]) if err != nil { // report binary as well as the error return nil, os.NewError(cmd[0] + ": " + err.String()) } // run the binary and read its combined stdout and stderr into a buffer p, err := exec.Run(bin, cmd, os.Environ(), "", exec.DevNull, exec.Pipe, exec.MergeWithStdout) if err != nil { return nil, err } var buf bytes.Buffer io.Copy(&buf, p.Stdout) w, err := p.Wait(0) p.Close() // set the error return value if the program had a non-zero exit status if !w.Exited() || w.ExitStatus() != 0 { err = os.ErrorString("running " + cmd[0] + ": " + w.String()) } return buf.Bytes(), err } var frontPage, output *template.Template // HTML templates func init() { frontPage = template.New(nil) frontPage.SetDelims("«", "»") if err := frontPage.Parse(frontPageText); err != nil { panic(err) } output = template.MustParse(outputText, nil) } var outputText = `
{@|html}
` var frontPageText = `
(Shift-Enter to compile and run.)     Compile and run after each keystroke
` var helloWorld = []byte(`package main import "fmt" func main() { fmt.Println("hello, world") } `)