golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/playground/socket/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 //go:build !appengine 6 // +build !appengine 7 8 // Package socket implements an WebSocket-based playground backend. 9 // Clients connect to a websocket handler and send run/kill commands, and 10 // the server sends the output and exit status of the running processes. 11 // Multiple clients running multiple processes may be served concurrently. 12 // The wire format is JSON and is described by the Message type. 13 // 14 // This will not run on App Engine as WebSockets are not supported there. 15 package socket // import "golang.org/x/tools/playground/socket" 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "errors" 21 "go/parser" 22 "go/token" 23 "io" 24 "log" 25 "net" 26 "net/http" 27 "net/url" 28 "os" 29 "os/exec" 30 "path/filepath" 31 "runtime" 32 "strings" 33 "time" 34 "unicode/utf8" 35 36 "golang.org/x/net/websocket" 37 "golang.org/x/tools/txtar" 38 ) 39 40 // RunScripts specifies whether the socket handler should execute shell scripts 41 // (snippets that start with a shebang). 42 var RunScripts = true 43 44 // Environ provides an environment when a binary, such as the go tool, is 45 // invoked. 46 var Environ func() []string = os.Environ 47 48 const ( 49 // The maximum number of messages to send per session (avoid flooding). 50 msgLimit = 1000 51 52 // Batch messages sent in this interval and send as a single message. 53 msgDelay = 10 * time.Millisecond 54 ) 55 56 // Message is the wire format for the websocket connection to the browser. 57 // It is used for both sending output messages and receiving commands, as 58 // distinguished by the Kind field. 59 type Message struct { 60 Id string // client-provided unique id for the process 61 Kind string // in: "run", "kill" out: "stdout", "stderr", "end" 62 Body string 63 Options *Options `json:",omitempty"` 64 } 65 66 // Options specify additional message options. 67 type Options struct { 68 Race bool // use -race flag when building code (for "run" only) 69 } 70 71 // NewHandler returns a websocket server which checks the origin of requests. 72 func NewHandler(origin *url.URL) websocket.Server { 73 return websocket.Server{ 74 Config: websocket.Config{Origin: origin}, 75 Handshake: handshake, 76 Handler: websocket.Handler(socketHandler), 77 } 78 } 79 80 // handshake checks the origin of a request during the websocket handshake. 81 func handshake(c *websocket.Config, req *http.Request) error { 82 o, err := websocket.Origin(c, req) 83 if err != nil { 84 log.Println("bad websocket origin:", err) 85 return websocket.ErrBadWebSocketOrigin 86 } 87 _, port, err := net.SplitHostPort(c.Origin.Host) 88 if err != nil { 89 log.Println("bad websocket origin:", err) 90 return websocket.ErrBadWebSocketOrigin 91 } 92 ok := c.Origin.Scheme == o.Scheme && (c.Origin.Host == o.Host || c.Origin.Host == net.JoinHostPort(o.Host, port)) 93 if !ok { 94 log.Println("bad websocket origin:", o) 95 return websocket.ErrBadWebSocketOrigin 96 } 97 log.Println("accepting connection from:", req.RemoteAddr) 98 return nil 99 } 100 101 // socketHandler handles the websocket connection for a given present session. 102 // It handles transcoding Messages to and from JSON format, and starting 103 // and killing processes. 104 func socketHandler(c *websocket.Conn) { 105 in, out := make(chan *Message), make(chan *Message) 106 errc := make(chan error, 1) 107 108 // Decode messages from client and send to the in channel. 109 go func() { 110 dec := json.NewDecoder(c) 111 for { 112 var m Message 113 if err := dec.Decode(&m); err != nil { 114 errc <- err 115 return 116 } 117 in <- &m 118 } 119 }() 120 121 // Receive messages from the out channel and encode to the client. 122 go func() { 123 enc := json.NewEncoder(c) 124 for m := range out { 125 if err := enc.Encode(m); err != nil { 126 errc <- err 127 return 128 } 129 } 130 }() 131 defer close(out) 132 133 // Start and kill processes and handle errors. 134 proc := make(map[string]*process) 135 for { 136 select { 137 case m := <-in: 138 switch m.Kind { 139 case "run": 140 log.Println("running snippet from:", c.Request().RemoteAddr) 141 proc[m.Id].Kill() 142 proc[m.Id] = startProcess(m.Id, m.Body, out, m.Options) 143 case "kill": 144 proc[m.Id].Kill() 145 } 146 case err := <-errc: 147 if err != io.EOF { 148 // A encode or decode has failed; bail. 149 log.Println(err) 150 } 151 // Shut down any running processes. 152 for _, p := range proc { 153 p.Kill() 154 } 155 return 156 } 157 } 158 } 159 160 // process represents a running process. 161 type process struct { 162 out chan<- *Message 163 done chan struct{} // closed when wait completes 164 run *exec.Cmd 165 path string 166 } 167 168 // startProcess builds and runs the given program, sending its output 169 // and end event as Messages on the provided channel. 170 func startProcess(id, body string, dest chan<- *Message, opt *Options) *process { 171 var ( 172 done = make(chan struct{}) 173 out = make(chan *Message) 174 p = &process{out: out, done: done} 175 ) 176 go func() { 177 defer close(done) 178 for m := range buffer(limiter(out, p), time.After) { 179 m.Id = id 180 dest <- m 181 } 182 }() 183 var err error 184 if path, args := shebang(body); path != "" { 185 if RunScripts { 186 err = p.startProcess(path, args, body) 187 } else { 188 err = errors.New("script execution is not allowed") 189 } 190 } else { 191 err = p.start(body, opt) 192 } 193 if err != nil { 194 p.end(err) 195 return nil 196 } 197 go func() { 198 p.end(p.run.Wait()) 199 }() 200 return p 201 } 202 203 // end sends an "end" message to the client, containing the process id and the 204 // given error value. It also removes the binary, if present. 205 func (p *process) end(err error) { 206 if p.path != "" { 207 defer os.RemoveAll(p.path) 208 } 209 m := &Message{Kind: "end"} 210 if err != nil { 211 m.Body = err.Error() 212 } 213 p.out <- m 214 close(p.out) 215 } 216 217 // A killer provides a mechanism to terminate a process. 218 // The Kill method returns only once the process has exited. 219 type killer interface { 220 Kill() 221 } 222 223 // limiter returns a channel that wraps the given channel. 224 // It receives Messages from the given channel and sends them to the returned 225 // channel until it passes msgLimit messages, at which point it will kill the 226 // process and pass only the "end" message. 227 // When the given channel is closed, or when the "end" message is received, 228 // it closes the returned channel. 229 func limiter(in <-chan *Message, p killer) <-chan *Message { 230 out := make(chan *Message) 231 go func() { 232 defer close(out) 233 n := 0 234 for m := range in { 235 switch { 236 case n < msgLimit || m.Kind == "end": 237 out <- m 238 if m.Kind == "end" { 239 return 240 } 241 case n == msgLimit: 242 // Kill in a goroutine as Kill will not return 243 // until the process' output has been 244 // processed, and we're doing that in this loop. 245 go p.Kill() 246 default: 247 continue // don't increment 248 } 249 n++ 250 } 251 }() 252 return out 253 } 254 255 // buffer returns a channel that wraps the given channel. It receives messages 256 // from the given channel and sends them to the returned channel. 257 // Message bodies are gathered over the period msgDelay and coalesced into a 258 // single Message before they are passed on. Messages of the same kind are 259 // coalesced; when a message of a different kind is received, any buffered 260 // messages are flushed. When the given channel is closed, buffer flushes the 261 // remaining buffered messages and closes the returned channel. 262 // The timeAfter func should be time.After. It exists for testing. 263 func buffer(in <-chan *Message, timeAfter func(time.Duration) <-chan time.Time) <-chan *Message { 264 out := make(chan *Message) 265 go func() { 266 defer close(out) 267 var ( 268 tc <-chan time.Time 269 buf []byte 270 kind string 271 flush = func() { 272 if len(buf) == 0 { 273 return 274 } 275 out <- &Message{Kind: kind, Body: safeString(buf)} 276 buf = buf[:0] // recycle buffer 277 kind = "" 278 } 279 ) 280 for { 281 select { 282 case m, ok := <-in: 283 if !ok { 284 flush() 285 return 286 } 287 if m.Kind == "end" { 288 flush() 289 out <- m 290 return 291 } 292 if kind != m.Kind { 293 flush() 294 kind = m.Kind 295 if tc == nil { 296 tc = timeAfter(msgDelay) 297 } 298 } 299 buf = append(buf, m.Body...) 300 case <-tc: 301 flush() 302 tc = nil 303 } 304 } 305 }() 306 return out 307 } 308 309 // Kill stops the process if it is running and waits for it to exit. 310 func (p *process) Kill() { 311 if p == nil || p.run == nil { 312 return 313 } 314 p.run.Process.Kill() 315 <-p.done // block until process exits 316 } 317 318 // shebang looks for a shebang ('#!') at the beginning of the passed string. 319 // If found, it returns the path and args after the shebang. 320 // args includes the command as args[0]. 321 func shebang(body string) (path string, args []string) { 322 body = strings.TrimSpace(body) 323 if !strings.HasPrefix(body, "#!") { 324 return "", nil 325 } 326 if i := strings.Index(body, "\n"); i >= 0 { 327 body = body[:i] 328 } 329 fs := strings.Fields(body[2:]) 330 return fs[0], fs 331 } 332 333 // startProcess starts a given program given its path and passing the given body 334 // to the command standard input. 335 func (p *process) startProcess(path string, args []string, body string) error { 336 cmd := &exec.Cmd{ 337 Path: path, 338 Args: args, 339 Stdin: strings.NewReader(body), 340 Stdout: &messageWriter{kind: "stdout", out: p.out}, 341 Stderr: &messageWriter{kind: "stderr", out: p.out}, 342 } 343 if err := cmd.Start(); err != nil { 344 return err 345 } 346 p.run = cmd 347 return nil 348 } 349 350 // start builds and starts the given program, sending its output to p.out, 351 // and stores the running *exec.Cmd in the run field. 352 func (p *process) start(body string, opt *Options) error { 353 // We "go build" and then exec the binary so that the 354 // resultant *exec.Cmd is a handle to the user's program 355 // (rather than the go tool process). 356 // This makes Kill work. 357 358 path, err := os.MkdirTemp("", "present-") 359 if err != nil { 360 return err 361 } 362 p.path = path // to be removed by p.end 363 364 out := "prog" 365 if runtime.GOOS == "windows" { 366 out = "prog.exe" 367 } 368 bin := filepath.Join(path, out) 369 370 // write body to x.go files 371 a := txtar.Parse([]byte(body)) 372 if len(a.Comment) != 0 { 373 a.Files = append(a.Files, txtar.File{Name: "prog.go", Data: a.Comment}) 374 a.Comment = nil 375 } 376 hasModfile := false 377 for _, f := range a.Files { 378 err = os.WriteFile(filepath.Join(path, f.Name), f.Data, 0666) 379 if err != nil { 380 return err 381 } 382 if f.Name == "go.mod" { 383 hasModfile = true 384 } 385 } 386 387 // build x.go, creating x 388 args := []string{"go", "build", "-tags", "OMIT"} 389 if opt != nil && opt.Race { 390 p.out <- &Message{ 391 Kind: "stderr", 392 Body: "Running with race detector.\n", 393 } 394 args = append(args, "-race") 395 } 396 args = append(args, "-o", bin) 397 cmd := p.cmd(path, args...) 398 if !hasModfile { 399 cmd.Env = append(cmd.Env, "GO111MODULE=off") 400 } 401 cmd.Stdout = cmd.Stderr // send compiler output to stderr 402 if err := cmd.Run(); err != nil { 403 return err 404 } 405 406 // run x 407 if isNacl() { 408 cmd, err = p.naclCmd(bin) 409 if err != nil { 410 return err 411 } 412 } else { 413 cmd = p.cmd("", bin) 414 } 415 if opt != nil && opt.Race { 416 cmd.Env = append(cmd.Env, "GOMAXPROCS=2") 417 } 418 if err := cmd.Start(); err != nil { 419 // If we failed to exec, that might be because they built 420 // a non-main package instead of an executable. 421 // Check and report that. 422 if name, err := packageName(body); err == nil && name != "main" { 423 return errors.New(`executable programs must use "package main"`) 424 } 425 return err 426 } 427 p.run = cmd 428 return nil 429 } 430 431 // cmd builds an *exec.Cmd that writes its standard output and error to the 432 // process' output channel. 433 func (p *process) cmd(dir string, args ...string) *exec.Cmd { 434 cmd := exec.Command(args[0], args[1:]...) 435 cmd.Dir = dir 436 cmd.Env = Environ() 437 cmd.Stdout = &messageWriter{kind: "stdout", out: p.out} 438 cmd.Stderr = &messageWriter{kind: "stderr", out: p.out} 439 return cmd 440 } 441 442 func isNacl() bool { 443 for _, v := range append(Environ(), os.Environ()...) { 444 if v == "GOOS=nacl" { 445 return true 446 } 447 } 448 return false 449 } 450 451 // naclCmd returns an *exec.Cmd that executes bin under native client. 452 func (p *process) naclCmd(bin string) (*exec.Cmd, error) { 453 pwd, err := os.Getwd() 454 if err != nil { 455 return nil, err 456 } 457 var args []string 458 env := []string{ 459 "NACLENV_GOOS=" + runtime.GOOS, 460 "NACLENV_GOROOT=/go", 461 "NACLENV_NACLPWD=" + strings.Replace(pwd, runtime.GOROOT(), "/go", 1), 462 } 463 switch runtime.GOARCH { 464 case "amd64": 465 env = append(env, "NACLENV_GOARCH=amd64p32") 466 args = []string{"sel_ldr_x86_64"} 467 case "386": 468 env = append(env, "NACLENV_GOARCH=386") 469 args = []string{"sel_ldr_x86_32"} 470 case "arm": 471 env = append(env, "NACLENV_GOARCH=arm") 472 selLdr, err := exec.LookPath("sel_ldr_arm") 473 if err != nil { 474 return nil, err 475 } 476 args = []string{"nacl_helper_bootstrap_arm", selLdr, "--reserved_at_zero=0xXXXXXXXXXXXXXXXX"} 477 default: 478 return nil, errors.New("native client does not support GOARCH=" + runtime.GOARCH) 479 } 480 481 cmd := p.cmd("", append(args, "-l", "/dev/null", "-S", "-e", bin)...) 482 cmd.Env = append(cmd.Env, env...) 483 484 return cmd, nil 485 } 486 487 func packageName(body string) (string, error) { 488 f, err := parser.ParseFile(token.NewFileSet(), "prog.go", 489 strings.NewReader(body), parser.PackageClauseOnly) 490 if err != nil { 491 return "", err 492 } 493 return f.Name.String(), nil 494 } 495 496 // messageWriter is an io.Writer that converts all writes to Message sends on 497 // the out channel with the specified id and kind. 498 type messageWriter struct { 499 kind string 500 out chan<- *Message 501 } 502 503 func (w *messageWriter) Write(b []byte) (n int, err error) { 504 w.out <- &Message{Kind: w.kind, Body: safeString(b)} 505 return len(b), nil 506 } 507 508 // safeString returns b as a valid UTF-8 string. 509 func safeString(b []byte) string { 510 if utf8.Valid(b) { 511 return string(b) 512 } 513 var buf bytes.Buffer 514 for len(b) > 0 { 515 r, size := utf8.DecodeRune(b) 516 b = b[size:] 517 buf.WriteRune(r) 518 } 519 return buf.String() 520 }