| // Copyright 2012 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. |
| |
| // +build ignore,OMIT |
| |
| package main |
| |
| import ( |
| "encoding/json" |
| "io/ioutil" |
| "log" |
| "net/http" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| "strconv" |
| |
| "golang.org/x/net/websocket" |
| ) |
| |
| const socketPresent = true |
| |
| func HandleSocket(path string) { |
| http.Handle(path, websocket.Handler(socketHandler)) |
| } |
| |
| const msgLimit = 1000 // max number of messages to send per session |
| |
| var uniq = make(chan int) // a source of numbers for naming temporary files |
| |
| func init() { |
| go func() { |
| for i := 0; ; i++ { |
| uniq <- i |
| } |
| }() |
| } |
| |
| // Message is the wire format for the websocket connection to the browser. |
| // It is used for both sending output messages and receiving commands, as |
| // distinguished by the Kind field. |
| type Message struct { |
| Id string // client-provided unique id for the process |
| Kind string // in: "run", "kill" out: "stdout", "stderr", "end" |
| Body string |
| } |
| |
| // socketHandler handles the websocket connection for a given present session. |
| // It handles transcoding Messages to and from JSON format, and starting |
| // and killing Processes. |
| func socketHandler(c *websocket.Conn) { |
| in, out := make(chan *Message), make(chan *Message) |
| errc := make(chan error, 1) |
| |
| // Decode messages from client and send to the in channel. |
| go func() { |
| dec := json.NewDecoder(c) |
| for { |
| var m Message |
| if err := dec.Decode(&m); err != nil { |
| errc <- err |
| return |
| } |
| in <- &m |
| } |
| }() |
| |
| // Receive messages from the out channel and encode to the client. |
| go func() { |
| enc := json.NewEncoder(c) |
| for m := range out { |
| if err := enc.Encode(m); err != nil { |
| errc <- err |
| return |
| } |
| } |
| }() |
| // END OMIT |
| |
| // Start and kill Processes and handle errors. |
| proc := make(map[string]*Process) |
| for { |
| select { |
| case m := <-in: |
| switch m.Kind { |
| case "run": |
| proc[m.Id].Kill() |
| lOut := limiter(in, out) // HL |
| proc[m.Id] = StartProcess(m.Id, m.Body, lOut) // HL |
| case "kill": |
| proc[m.Id].Kill() |
| } |
| case err := <-errc: |
| // A encode or decode has failed; bail. |
| log.Println(err) |
| // Shut down any running processes. |
| for _, p := range proc { |
| p.Kill() |
| } |
| return |
| } |
| } |
| } |
| |
| // Process represents a running process. |
| type Process struct { |
| id string |
| out chan<- *Message |
| done chan struct{} // closed when wait completes |
| run *exec.Cmd |
| } |
| |
| // StartProcess builds and runs the given program, sending its output |
| // and end event as Messages on the provided channel. |
| func StartProcess(id, body string, out chan<- *Message) *Process { |
| p := &Process{ |
| id: id, |
| out: out, |
| done: make(chan struct{}), |
| } |
| if err := p.start(body); err != nil { |
| p.end(err) |
| return nil |
| } |
| go p.wait() |
| return p |
| } |
| |
| // Kill stops the process if it is running and waits for it to exit. |
| func (p *Process) Kill() { |
| if p == nil { |
| return |
| } |
| p.run.Process.Kill() |
| <-p.done |
| } |
| |
| // start builds and starts the given program, sends its output to p.out, |
| // and stores the running *exec.Cmd in the run field. |
| func (p *Process) start(body string) error { |
| // x is the base name for .go and executable files |
| x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq)) |
| src := x + ".go" |
| bin := x |
| if runtime.GOOS == "windows" { |
| bin += ".exe" |
| } |
| |
| // write body to x.go |
| defer os.Remove(src) |
| if err := ioutil.WriteFile(src, []byte(body), 0666); err != nil { |
| return err |
| } |
| // END OMIT |
| |
| // build x.go, creating x |
| dir, file := filepath.Split(src) |
| err := p.cmd(dir, "go", "build", "-o", bin, file).Run() |
| defer os.Remove(bin) |
| if err != nil { |
| return err |
| } |
| |
| // run x |
| cmd := p.cmd("", bin) |
| if err = cmd.Start(); err != nil { |
| return err |
| } |
| |
| p.run = cmd |
| return nil |
| } |
| |
| // wait waits for the running process to complete |
| // and sends its error state to the client. |
| func (p *Process) wait() { |
| defer close(p.done) |
| p.end(p.run.Wait()) |
| } |
| |
| // end sends an "end" message to the client, containing the process id and the |
| // given error value. |
| func (p *Process) end(err error) { |
| m := &Message{Id: p.id, Kind: "end"} |
| if err != nil { |
| m.Body = err.Error() |
| } |
| p.out <- m |
| } |
| |
| // cmd builds an *exec.Cmd that writes its standard output and error to the |
| // Process' output channel. |
| func (p *Process) cmd(dir string, args ...string) *exec.Cmd { |
| cmd := exec.Command(args[0], args[1:]...) |
| cmd.Dir = dir |
| cmd.Stdout = &messageWriter{p.id, "stdout", p.out} |
| cmd.Stderr = &messageWriter{p.id, "stderr", p.out} |
| return cmd |
| } |
| |
| // messageWriter is an io.Writer that converts all writes to Message sends on |
| // the out channel with the specified id and kind. |
| type messageWriter struct { |
| id, kind string |
| out chan<- *Message |
| } |
| |
| func (w *messageWriter) Write(b []byte) (n int, err error) { |
| w.out <- &Message{Id: w.id, Kind: w.kind, Body: string(b)} |
| return len(b), nil |
| } |
| |
| // END OMIT |
| |
| // limiter returns a channel that wraps dest. Messages sent to the channel are |
| // sent to dest. After msgLimit Messages have been passed on, a "kill" Message |
| // is sent to the kill channel, and only "end" messages are passed. |
| func limiter(kill chan<- *Message, dest chan<- *Message) chan<- *Message { |
| ch := make(chan *Message) |
| go func() { |
| n := 0 |
| for m := range ch { |
| switch { |
| case n < msgLimit || m.Kind == "end": |
| dest <- m |
| if m.Kind == "end" { |
| return |
| } |
| case n == msgLimit: |
| // Process produced too much output. Kill it. |
| kill <- &Message{Id: m.Id, Kind: "kill"} |
| } |
| n++ |
| } |
| }() |
| return ch |
| } |
| |
| var tmpdir string |
| |
| func init() { |
| // find real path to temporary directory |
| var err error |
| tmpdir, err = filepath.EvalSymlinks(os.TempDir()) |
| if err != nil { |
| log.Fatal(err) |
| } |
| } |