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.
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.
14 // This will not run on App Engine as WebSockets are not supported there.
15 package socket // import "golang.org/x/tools/playground/socket"
23 exec "golang.org/x/sys/execabs"
37 "golang.org/x/net/websocket"
38 "golang.org/x/tools/txtar"
41 // RunScripts specifies whether the socket handler should execute shell scripts
42 // (snippets that start with a shebang).
45 // Environ provides an environment when a binary, such as the go tool, is
47 var Environ func() []string = os.Environ
50 // The maximum number of messages to send per session (avoid flooding).
53 // Batch messages sent in this interval and send as a single message.
54 msgDelay = 10 * time.Millisecond
57 // Message is the wire format for the websocket connection to the browser.
58 // It is used for both sending output messages and receiving commands, as
59 // distinguished by the Kind field.
61 Id string // client-provided unique id for the process
62 Kind string // in: "run", "kill" out: "stdout", "stderr", "end"
64 Options *Options `json:",omitempty"`
67 // Options specify additional message options.
69 Race bool // use -race flag when building code (for "run" only)
72 // NewHandler returns a websocket server which checks the origin of requests.
73 func NewHandler(origin *url.URL) websocket.Server {
74 return websocket.Server{
75 Config: websocket.Config{Origin: origin},
77 Handler: websocket.Handler(socketHandler),
81 // handshake checks the origin of a request during the websocket handshake.
82 func handshake(c *websocket.Config, req *http.Request) error {
83 o, err := websocket.Origin(c, req)
85 log.Println("bad websocket origin:", err)
86 return websocket.ErrBadWebSocketOrigin
88 _, port, err := net.SplitHostPort(c.Origin.Host)
90 log.Println("bad websocket origin:", err)
91 return websocket.ErrBadWebSocketOrigin
93 ok := c.Origin.Scheme == o.Scheme && (c.Origin.Host == o.Host || c.Origin.Host == net.JoinHostPort(o.Host, port))
95 log.Println("bad websocket origin:", o)
96 return websocket.ErrBadWebSocketOrigin
98 log.Println("accepting connection from:", req.RemoteAddr)
102 // socketHandler handles the websocket connection for a given present session.
103 // It handles transcoding Messages to and from JSON format, and starting
104 // and killing processes.
105 func socketHandler(c *websocket.Conn) {
106 in, out := make(chan *Message), make(chan *Message)
107 errc := make(chan error, 1)
109 // Decode messages from client and send to the in channel.
111 dec := json.NewDecoder(c)
114 if err := dec.Decode(&m); err != nil {
122 // Receive messages from the out channel and encode to the client.
124 enc := json.NewEncoder(c)
126 if err := enc.Encode(m); err != nil {
134 // Start and kill processes and handle errors.
135 proc := make(map[string]*process)
141 log.Println("running snippet from:", c.Request().RemoteAddr)
143 proc[m.Id] = startProcess(m.Id, m.Body, out, m.Options)
149 // A encode or decode has failed; bail.
152 // Shut down any running processes.
153 for _, p := range proc {
161 // process represents a running process.
162 type process struct {
164 done chan struct{} // closed when wait completes
169 // startProcess builds and runs the given program, sending its output
170 // and end event as Messages on the provided channel.
171 func startProcess(id, body string, dest chan<- *Message, opt *Options) *process {
173 done = make(chan struct{})
174 out = make(chan *Message)
175 p = &process{out: out, done: done}
179 for m := range buffer(limiter(out, p), time.After) {
185 if path, args := shebang(body); path != "" {
187 err = p.startProcess(path, args, body)
189 err = errors.New("script execution is not allowed")
192 err = p.start(body, opt)
204 // end sends an "end" message to the client, containing the process id and the
205 // given error value. It also removes the binary, if present.
206 func (p *process) end(err error) {
208 defer os.RemoveAll(p.path)
210 m := &Message{Kind: "end"}
218 // A killer provides a mechanism to terminate a process.
219 // The Kill method returns only once the process has exited.
220 type killer interface {
224 // limiter returns a channel that wraps the given channel.
225 // It receives Messages from the given channel and sends them to the returned
226 // channel until it passes msgLimit messages, at which point it will kill the
227 // process and pass only the "end" message.
228 // When the given channel is closed, or when the "end" message is received,
229 // it closes the returned channel.
230 func limiter(in <-chan *Message, p killer) <-chan *Message {
231 out := make(chan *Message)
237 case n < msgLimit || m.Kind == "end":
243 // Kill in a goroutine as Kill will not return
244 // until the process' output has been
245 // processed, and we're doing that in this loop.
248 continue // don't increment
256 // buffer returns a channel that wraps the given channel. It receives messages
257 // from the given channel and sends them to the returned channel.
258 // Message bodies are gathered over the period msgDelay and coalesced into a
259 // single Message before they are passed on. Messages of the same kind are
260 // coalesced; when a message of a different kind is received, any buffered
261 // messages are flushed. When the given channel is closed, buffer flushes the
262 // remaining buffered messages and closes the returned channel.
263 // The timeAfter func should be time.After. It exists for testing.
264 func buffer(in <-chan *Message, timeAfter func(time.Duration) <-chan time.Time) <-chan *Message {
265 out := make(chan *Message)
276 out <- &Message{Kind: kind, Body: safeString(buf)}
277 buf = buf[:0] // recycle buffer
297 tc = timeAfter(msgDelay)
300 buf = append(buf, m.Body...)
310 // Kill stops the process if it is running and waits for it to exit.
311 func (p *process) Kill() {
312 if p == nil || p.run == nil {
316 <-p.done // block until process exits
319 // shebang looks for a shebang ('#!') at the beginning of the passed string.
320 // If found, it returns the path and args after the shebang.
321 // args includes the command as args[0].
322 func shebang(body string) (path string, args []string) {
323 body = strings.TrimSpace(body)
324 if !strings.HasPrefix(body, "#!") {
327 if i := strings.Index(body, "\n"); i >= 0 {
330 fs := strings.Fields(body[2:])
334 // startProcess starts a given program given its path and passing the given body
335 // to the command standard input.
336 func (p *process) startProcess(path string, args []string, body string) error {
340 Stdin: strings.NewReader(body),
341 Stdout: &messageWriter{kind: "stdout", out: p.out},
342 Stderr: &messageWriter{kind: "stderr", out: p.out},
344 if err := cmd.Start(); err != nil {
351 // start builds and starts the given program, sending its output to p.out,
352 // and stores the running *exec.Cmd in the run field.
353 func (p *process) start(body string, opt *Options) error {
354 // We "go build" and then exec the binary so that the
355 // resultant *exec.Cmd is a handle to the user's program
356 // (rather than the go tool process).
357 // This makes Kill work.
359 path, err := ioutil.TempDir("", "present-")
363 p.path = path // to be removed by p.end
366 if runtime.GOOS == "windows" {
369 bin := filepath.Join(path, out)
371 // write body to x.go files
372 a := txtar.Parse([]byte(body))
373 if len(a.Comment) != 0 {
374 a.Files = append(a.Files, txtar.File{Name: "prog.go", Data: a.Comment})
378 for _, f := range a.Files {
379 err = ioutil.WriteFile(filepath.Join(path, f.Name), f.Data, 0666)
383 if f.Name == "go.mod" {
388 // build x.go, creating x
389 args := []string{"go", "build", "-tags", "OMIT"}
390 if opt != nil && opt.Race {
393 Body: "Running with race detector.\n",
395 args = append(args, "-race")
397 args = append(args, "-o", bin)
398 cmd := p.cmd(path, args...)
400 cmd.Env = append(cmd.Env, "GO111MODULE=off")
402 cmd.Stdout = cmd.Stderr // send compiler output to stderr
403 if err := cmd.Run(); err != nil {
409 cmd, err = p.naclCmd(bin)
416 if opt != nil && opt.Race {
417 cmd.Env = append(cmd.Env, "GOMAXPROCS=2")
419 if err := cmd.Start(); err != nil {
420 // If we failed to exec, that might be because they built
421 // a non-main package instead of an executable.
422 // Check and report that.
423 if name, err := packageName(body); err == nil && name != "main" {
424 return errors.New(`executable programs must use "package main"`)
432 // cmd builds an *exec.Cmd that writes its standard output and error to the
433 // process' output channel.
434 func (p *process) cmd(dir string, args ...string) *exec.Cmd {
435 cmd := exec.Command(args[0], args[1:]...)
438 cmd.Stdout = &messageWriter{kind: "stdout", out: p.out}
439 cmd.Stderr = &messageWriter{kind: "stderr", out: p.out}
444 for _, v := range append(Environ(), os.Environ()...) {
445 if v == "GOOS=nacl" {
452 // naclCmd returns an *exec.Cmd that executes bin under native client.
453 func (p *process) naclCmd(bin string) (*exec.Cmd, error) {
454 pwd, err := os.Getwd()
460 "NACLENV_GOOS=" + runtime.GOOS,
461 "NACLENV_GOROOT=/go",
462 "NACLENV_NACLPWD=" + strings.Replace(pwd, runtime.GOROOT(), "/go", 1),
464 switch runtime.GOARCH {
466 env = append(env, "NACLENV_GOARCH=amd64p32")
467 args = []string{"sel_ldr_x86_64"}
469 env = append(env, "NACLENV_GOARCH=386")
470 args = []string{"sel_ldr_x86_32"}
472 env = append(env, "NACLENV_GOARCH=arm")
473 selLdr, err := exec.LookPath("sel_ldr_arm")
477 args = []string{"nacl_helper_bootstrap_arm", selLdr, "--reserved_at_zero=0xXXXXXXXXXXXXXXXX"}
479 return nil, errors.New("native client does not support GOARCH=" + runtime.GOARCH)
482 cmd := p.cmd("", append(args, "-l", "/dev/null", "-S", "-e", bin)...)
483 cmd.Env = append(cmd.Env, env...)
488 func packageName(body string) (string, error) {
489 f, err := parser.ParseFile(token.NewFileSet(), "prog.go",
490 strings.NewReader(body), parser.PackageClauseOnly)
494 return f.Name.String(), nil
497 // messageWriter is an io.Writer that converts all writes to Message sends on
498 // the out channel with the specified id and kind.
499 type messageWriter struct {
504 func (w *messageWriter) Write(b []byte) (n int, err error) {
505 w.out <- &Message{Kind: w.kind, Body: safeString(b)}
509 // safeString returns b as a valid UTF-8 string.
510 func safeString(b []byte) string {
516 r, size := utf8.DecodeRune(b)