github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/talks/2012/insidepresent/socket-simple.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 proc[m.Id] = StartProcess(m.Id, m.Body, out) 91 case "kill": 92 proc[m.Id].Kill() 93 } 94 case err := <-errc: 95 // A encode or decode has failed; bail. 96 log.Println(err) 97 // Shut down any running processes. 98 for _, p := range proc { 99 p.Kill() 100 } 101 return 102 } 103 } 104 } 105 106 // Process represents a running process. 107 type Process struct { 108 id string 109 out chan<- *Message 110 done chan struct{} // closed when wait completes 111 run *exec.Cmd 112 } 113 114 // StartProcess builds and runs the given program, sending its output 115 // and end event as Messages on the provided channel. 116 func StartProcess(id, body string, out chan<- *Message) *Process { 117 p := &Process{ 118 id: id, 119 out: out, 120 done: make(chan struct{}), 121 } 122 cmd, err := p.start(body) 123 if err != nil { 124 p.end(err) 125 return nil 126 } 127 p.run = cmd 128 go p.wait(cmd) 129 return p 130 } 131 132 // Kill stops the process if it is running and waits for it to exit. 133 func (p *Process) Kill() { 134 if p == nil { 135 return 136 } 137 if p.run != nil { 138 p.run.Process.Kill() 139 } 140 <-p.done 141 } 142 143 // start builds and starts the given program, sending its output to p.out, 144 // and returns the associated *exec.Cmd. 145 func (p *Process) start(body string) (*exec.Cmd, error) { 146 // x is the base name for .go and executable files 147 x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq)) 148 src := x + ".go" 149 bin := x 150 if runtime.GOOS == "windows" { 151 bin += ".exe" 152 } 153 154 // write body to x.go 155 defer os.Remove(src) 156 if err := ioutil.WriteFile(src, []byte(body), 0666); err != nil { 157 return nil, err 158 } 159 // END OMIT 160 161 // build x.go, creating x 162 dir, file := filepath.Split(src) 163 err := p.cmd(dir, "go", "build", "-o", bin, file).Run() 164 defer os.Remove(bin) 165 if err != nil { 166 return nil, err 167 } 168 169 // run x 170 cmd := p.cmd("", bin) 171 if err = cmd.Start(); err != nil { 172 return nil, err 173 } 174 return cmd, nil 175 } 176 177 // wait waits for the running process to complete and returns its error state. 178 func (p *Process) wait(cmd *exec.Cmd) { 179 defer close(p.done) 180 p.end(cmd.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 var tmpdir string 218 219 func init() { 220 // find real path to temporary directory 221 var err error 222 tmpdir, err = filepath.EvalSymlinks(os.TempDir()) 223 if err != nil { 224 log.Fatal(err) 225 } 226 }