github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/talks/2012/insidepresent/socket.go (about) 1 // Copyright 2012 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 OMIT 6 7 package main 8 9 import ( 10 "encoding/json" 11 "io/ioutil" 12 "log" 13 "net/http" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "runtime" 18 "strconv" 19 20 "golang.org/x/net/websocket" 21 ) 22 23 const socketPresent = true 24 25 func HandleSocket(path string) { 26 http.Handle(path, websocket.Handler(socketHandler)) 27 } 28 29 const msgLimit = 1000 // max number of messages to send per session 30 31 var uniq = make(chan int) // a source of numbers for naming temporary files 32 33 func init() { 34 go func() { 35 for i := 0; ; i++ { 36 uniq <- i 37 } 38 }() 39 } 40 41 // Message is the wire format for the websocket connection to the browser. 42 // It is used for both sending output messages and receiving commands, as 43 // distinguished by the Kind field. 44 type Message struct { 45 Id string // client-provided unique id for the process 46 Kind string // in: "run", "kill" out: "stdout", "stderr", "end" 47 Body string 48 } 49 50 // socketHandler handles the websocket connection for a given present session. 51 // It handles transcoding Messages to and from JSON format, and starting 52 // and killing Processes. 53 func socketHandler(c *websocket.Conn) { 54 in, out := make(chan *Message), make(chan *Message) 55 errc := make(chan error, 1) 56 57 // Decode messages from client and send to the in channel. 58 go func() { 59 dec := json.NewDecoder(c) 60 for { 61 var m Message 62 if err := dec.Decode(&m); err != nil { 63 errc <- err 64 return 65 } 66 in <- &m 67 } 68 }() 69 70 // Receive messages from the out channel and encode to the client. 71 go func() { 72 enc := json.NewEncoder(c) 73 for m := range out { 74 if err := enc.Encode(m); err != nil { 75 errc <- err 76 return 77 } 78 } 79 }() 80 // END OMIT 81 82 // Start and kill Processes and handle errors. 83 proc := make(map[string]*Process) 84 for { 85 select { 86 case m := <-in: 87 switch m.Kind { 88 case "run": 89 proc[m.Id].Kill() 90 lOut := limiter(in, out) // HL 91 proc[m.Id] = StartProcess(m.Id, m.Body, lOut) // HL 92 case "kill": 93 proc[m.Id].Kill() 94 } 95 case err := <-errc: 96 // A encode or decode has failed; bail. 97 log.Println(err) 98 // Shut down any running processes. 99 for _, p := range proc { 100 p.Kill() 101 } 102 return 103 } 104 } 105 } 106 107 // Process represents a running process. 108 type Process struct { 109 id string 110 out chan<- *Message 111 done chan struct{} // closed when wait completes 112 run *exec.Cmd 113 } 114 115 // StartProcess builds and runs the given program, sending its output 116 // and end event as Messages on the provided channel. 117 func StartProcess(id, body string, out chan<- *Message) *Process { 118 p := &Process{ 119 id: id, 120 out: out, 121 done: make(chan struct{}), 122 } 123 if err := p.start(body); err != nil { 124 p.end(err) 125 return nil 126 } 127 go p.wait() 128 return p 129 } 130 131 // Kill stops the process if it is running and waits for it to exit. 132 func (p *Process) Kill() { 133 if p == nil { 134 return 135 } 136 p.run.Process.Kill() 137 <-p.done 138 } 139 140 // start builds and starts the given program, sends its output to p.out, 141 // and stores the running *exec.Cmd in the run field. 142 func (p *Process) start(body string) error { 143 // x is the base name for .go and executable files 144 x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq)) 145 src := x + ".go" 146 bin := x 147 if runtime.GOOS == "windows" { 148 bin += ".exe" 149 } 150 151 // write body to x.go 152 defer os.Remove(src) 153 if err := ioutil.WriteFile(src, []byte(body), 0666); err != nil { 154 return err 155 } 156 // END OMIT 157 158 // build x.go, creating x 159 dir, file := filepath.Split(src) 160 err := p.cmd(dir, "go", "build", "-o", bin, file).Run() 161 defer os.Remove(bin) 162 if err != nil { 163 return err 164 } 165 166 // run x 167 cmd := p.cmd("", bin) 168 if err = cmd.Start(); err != nil { 169 return err 170 } 171 172 p.run = cmd 173 return nil 174 } 175 176 // wait waits for the running process to complete 177 // and sends its error state to the client. 178 func (p *Process) wait() { 179 defer close(p.done) 180 p.end(p.run.Wait()) 181 } 182 183 // end sends an "end" message to the client, containing the process id and the 184 // given error value. 185 func (p *Process) end(err error) { 186 m := &Message{Id: p.id, Kind: "end"} 187 if err != nil { 188 m.Body = err.Error() 189 } 190 p.out <- m 191 } 192 193 // cmd builds an *exec.Cmd that writes its standard output and error to the 194 // Process' output channel. 195 func (p *Process) cmd(dir string, args ...string) *exec.Cmd { 196 cmd := exec.Command(args[0], args[1:]...) 197 cmd.Dir = dir 198 cmd.Stdout = &messageWriter{p.id, "stdout", p.out} 199 cmd.Stderr = &messageWriter{p.id, "stderr", p.out} 200 return cmd 201 } 202 203 // messageWriter is an io.Writer that converts all writes to Message sends on 204 // the out channel with the specified id and kind. 205 type messageWriter struct { 206 id, kind string 207 out chan<- *Message 208 } 209 210 func (w *messageWriter) Write(b []byte) (n int, err error) { 211 w.out <- &Message{Id: w.id, Kind: w.kind, Body: string(b)} 212 return len(b), nil 213 } 214 215 // END OMIT 216 217 // limiter returns a channel that wraps dest. Messages sent to the channel are 218 // sent to dest. After msgLimit Messages have been passed on, a "kill" Message 219 // is sent to the kill channel, and only "end" messages are passed. 220 func limiter(kill chan<- *Message, dest chan<- *Message) chan<- *Message { 221 ch := make(chan *Message) 222 go func() { 223 n := 0 224 for m := range ch { 225 switch { 226 case n < msgLimit || m.Kind == "end": 227 dest <- m 228 if m.Kind == "end" { 229 return 230 } 231 case n == msgLimit: 232 // Process produced too much output. Kill it. 233 kill <- &Message{Id: m.Id, Kind: "kill"} 234 } 235 n++ 236 } 237 }() 238 return ch 239 } 240 241 var tmpdir string 242 243 func init() { 244 // find real path to temporary directory 245 var err error 246 tmpdir, err = filepath.EvalSymlinks(os.TempDir()) 247 if err != nil { 248 log.Fatal(err) 249 } 250 }