GoPLS Viewer

Home|gopls/playground/socket/socket.go
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.
15package socket // import "golang.org/x/tools/playground/socket"
16
17import (
18    "bytes"
19    "encoding/json"
20    "errors"
21    "go/parser"
22    "go/token"
23    exec "golang.org/x/sys/execabs"
24    "io"
25    "io/ioutil"
26    "log"
27    "net"
28    "net/http"
29    "net/url"
30    "os"
31    "path/filepath"
32    "runtime"
33    "strings"
34    "time"
35    "unicode/utf8"
36
37    "golang.org/x/net/websocket"
38    "golang.org/x/tools/txtar"
39)
40
41// RunScripts specifies whether the socket handler should execute shell scripts
42// (snippets that start with a shebang).
43var RunScripts = true
44
45// Environ provides an environment when a binary, such as the go tool, is
46// invoked.
47var Environ func() []string = os.Environ
48
49const (
50    // The maximum number of messages to send per session (avoid flooding).
51    msgLimit = 1000
52
53    // Batch messages sent in this interval and send as a single message.
54    msgDelay = 10 * time.Millisecond
55)
56
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.
60type Message struct {
61    Id      string // client-provided unique id for the process
62    Kind    string // in: "run", "kill" out: "stdout", "stderr", "end"
63    Body    string
64    Options *Options `json:",omitempty"`
65}
66
67// Options specify additional message options.
68type Options struct {
69    Race bool // use -race flag when building code (for "run" only)
70}
71
72// NewHandler returns a websocket server which checks the origin of requests.
73func NewHandler(origin *url.URLwebsocket.Server {
74    return websocket.Server{
75        Config:    websocket.Config{Originorigin},
76        Handshakehandshake,
77        Handler:   websocket.Handler(socketHandler),
78    }
79}
80
81// handshake checks the origin of a request during the websocket handshake.
82func handshake(c *websocket.Configreq *http.Requesterror {
83    oerr := websocket.Origin(creq)
84    if err != nil {
85        log.Println("bad websocket origin:"err)
86        return websocket.ErrBadWebSocketOrigin
87    }
88    _porterr := net.SplitHostPort(c.Origin.Host)
89    if err != nil {
90        log.Println("bad websocket origin:"err)
91        return websocket.ErrBadWebSocketOrigin
92    }
93    ok := c.Origin.Scheme == o.Scheme && (c.Origin.Host == o.Host || c.Origin.Host == net.JoinHostPort(o.Hostport))
94    if !ok {
95        log.Println("bad websocket origin:"o)
96        return websocket.ErrBadWebSocketOrigin
97    }
98    log.Println("accepting connection from:"req.RemoteAddr)
99    return nil
100}
101
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.
105func socketHandler(c *websocket.Conn) {
106    inout := make(chan *Message), make(chan *Message)
107    errc := make(chan error1)
108
109    // Decode messages from client and send to the in channel.
110    go func() {
111        dec := json.NewDecoder(c)
112        for {
113            var m Message
114            if err := dec.Decode(&m); err != nil {
115                errc <- err
116                return
117            }
118            in <- &m
119        }
120    }()
121
122    // Receive messages from the out channel and encode to the client.
123    go func() {
124        enc := json.NewEncoder(c)
125        for m := range out {
126            if err := enc.Encode(m); err != nil {
127                errc <- err
128                return
129            }
130        }
131    }()
132    defer close(out)
133
134    // Start and kill processes and handle errors.
135    proc := make(map[string]*process)
136    for {
137        select {
138        case m := <-in:
139            switch m.Kind {
140            case "run":
141                log.Println("running snippet from:"c.Request().RemoteAddr)
142                proc[m.Id].Kill()
143                proc[m.Id] = startProcess(m.Idm.Bodyoutm.Options)
144            case "kill":
145                proc[m.Id].Kill()
146            }
147        case err := <-errc:
148            if err != io.EOF {
149                // A encode or decode has failed; bail.
150                log.Println(err)
151            }
152            // Shut down any running processes.
153            for _p := range proc {
154                p.Kill()
155            }
156            return
157        }
158    }
159}
160
161// process represents a running process.
162type process struct {
163    out  chan<- *Message
164    done chan struct{} // closed when wait completes
165    run  *exec.Cmd
166    path string
167}
168
169// startProcess builds and runs the given program, sending its output
170// and end event as Messages on the provided channel.
171func startProcess(idbody stringdest chan<- *Messageopt *Options) *process {
172    var (
173        done = make(chan struct{})
174        out  = make(chan *Message)
175        p    = &process{outoutdonedone}
176    )
177    go func() {
178        defer close(done)
179        for m := range buffer(limiter(outp), time.After) {
180            m.Id = id
181            dest <- m
182        }
183    }()
184    var err error
185    if pathargs := shebang(body); path != "" {
186        if RunScripts {
187            err = p.startProcess(pathargsbody)
188        } else {
189            err = errors.New("script execution is not allowed")
190        }
191    } else {
192        err = p.start(bodyopt)
193    }
194    if err != nil {
195        p.end(err)
196        return nil
197    }
198    go func() {
199        p.end(p.run.Wait())
200    }()
201    return p
202}
203
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.
206func (p *processend(err error) {
207    if p.path != "" {
208        defer os.RemoveAll(p.path)
209    }
210    m := &Message{Kind"end"}
211    if err != nil {
212        m.Body = err.Error()
213    }
214    p.out <- m
215    close(p.out)
216}
217
218// A killer provides a mechanism to terminate a process.
219// The Kill method returns only once the process has exited.
220type killer interface {
221    Kill()
222}
223
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.
230func limiter(in <-chan *Messagep killer) <-chan *Message {
231    out := make(chan *Message)
232    go func() {
233        defer close(out)
234        n := 0
235        for m := range in {
236            switch {
237            case n < msgLimit || m.Kind == "end":
238                out <- m
239                if m.Kind == "end" {
240                    return
241                }
242            case n == msgLimit:
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.
246                go p.Kill()
247            default:
248                continue // don't increment
249            }
250            n++
251        }
252    }()
253    return out
254}
255
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.
264func buffer(in <-chan *MessagetimeAfter func(time.Duration) <-chan time.Time) <-chan *Message {
265    out := make(chan *Message)
266    go func() {
267        defer close(out)
268        var (
269            tc    <-chan time.Time
270            buf   []byte
271            kind  string
272            flush = func() {
273                if len(buf) == 0 {
274                    return
275                }
276                out <- &Message{KindkindBodysafeString(buf)}
277                buf = buf[:0// recycle buffer
278                kind = ""
279            }
280        )
281        for {
282            select {
283            case mok := <-in:
284                if !ok {
285                    flush()
286                    return
287                }
288                if m.Kind == "end" {
289                    flush()
290                    out <- m
291                    return
292                }
293                if kind != m.Kind {
294                    flush()
295                    kind = m.Kind
296                    if tc == nil {
297                        tc = timeAfter(msgDelay)
298                    }
299                }
300                buf = append(bufm.Body...)
301            case <-tc:
302                flush()
303                tc = nil
304            }
305        }
306    }()
307    return out
308}
309
310// Kill stops the process if it is running and waits for it to exit.
311func (p *processKill() {
312    if p == nil || p.run == nil {
313        return
314    }
315    p.run.Process.Kill()
316    <-p.done // block until process exits
317}
318
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].
322func shebang(body string) (path stringargs []string) {
323    body = strings.TrimSpace(body)
324    if !strings.HasPrefix(body"#!") {
325        return ""nil
326    }
327    if i := strings.Index(body"\n"); i >= 0 {
328        body = body[:i]
329    }
330    fs := strings.Fields(body[2:])
331    return fs[0], fs
332}
333
334// startProcess starts a given program given its path and passing the given body
335// to the command standard input.
336func (p *processstartProcess(path stringargs []stringbody stringerror {
337    cmd := &exec.Cmd{
338        Path:   path,
339        Args:   args,
340        Stdin:  strings.NewReader(body),
341        Stdout: &messageWriter{kind"stdout"outp.out},
342        Stderr: &messageWriter{kind"stderr"outp.out},
343    }
344    if err := cmd.Start(); err != nil {
345        return err
346    }
347    p.run = cmd
348    return nil
349}
350
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.
353func (p *processstart(body stringopt *Optionserror {
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.
358
359    patherr := ioutil.TempDir("""present-")
360    if err != nil {
361        return err
362    }
363    p.path = path // to be removed by p.end
364
365    out := "prog"
366    if runtime.GOOS == "windows" {
367        out = "prog.exe"
368    }
369    bin := filepath.Join(pathout)
370
371    // write body to x.go files
372    a := txtar.Parse([]byte(body))
373    if len(a.Comment) != 0 {
374        a.Files = append(a.Filestxtar.File{Name"prog.go"Dataa.Comment})
375        a.Comment = nil
376    }
377    hasModfile := false
378    for _f := range a.Files {
379        err = ioutil.WriteFile(filepath.Join(pathf.Name), f.Data0666)
380        if err != nil {
381            return err
382        }
383        if f.Name == "go.mod" {
384            hasModfile = true
385        }
386    }
387
388    // build x.go, creating x
389    args := []string{"go""build""-tags""OMIT"}
390    if opt != nil && opt.Race {
391        p.out <- &Message{
392            Kind"stderr",
393            Body"Running with race detector.\n",
394        }
395        args = append(args"-race")
396    }
397    args = append(args"-o"bin)
398    cmd := p.cmd(pathargs...)
399    if !hasModfile {
400        cmd.Env = append(cmd.Env"GO111MODULE=off")
401    }
402    cmd.Stdout = cmd.Stderr // send compiler output to stderr
403    if err := cmd.Run(); err != nil {
404        return err
405    }
406
407    // run x
408    if isNacl() {
409        cmderr = p.naclCmd(bin)
410        if err != nil {
411            return err
412        }
413    } else {
414        cmd = p.cmd(""bin)
415    }
416    if opt != nil && opt.Race {
417        cmd.Env = append(cmd.Env"GOMAXPROCS=2")
418    }
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 nameerr := packageName(body); err == nil && name != "main" {
424            return errors.New(`executable programs must use "package main"`)
425        }
426        return err
427    }
428    p.run = cmd
429    return nil
430}
431
432// cmd builds an *exec.Cmd that writes its standard output and error to the
433// process' output channel.
434func (p *processcmd(dir stringargs ...string) *exec.Cmd {
435    cmd := exec.Command(args[0], args[1:]...)
436    cmd.Dir = dir
437    cmd.Env = Environ()
438    cmd.Stdout = &messageWriter{kind"stdout"outp.out}
439    cmd.Stderr = &messageWriter{kind"stderr"outp.out}
440    return cmd
441}
442
443func isNacl() bool {
444    for _v := range append(Environ(), os.Environ()...) {
445        if v == "GOOS=nacl" {
446            return true
447        }
448    }
449    return false
450}
451
452// naclCmd returns an *exec.Cmd that executes bin under native client.
453func (p *processnaclCmd(bin string) (*exec.Cmderror) {
454    pwderr := os.Getwd()
455    if err != nil {
456        return nilerr
457    }
458    var args []string
459    env := []string{
460        "NACLENV_GOOS=" + runtime.GOOS,
461        "NACLENV_GOROOT=/go",
462        "NACLENV_NACLPWD=" + strings.Replace(pwdruntime.GOROOT(), "/go"1),
463    }
464    switch runtime.GOARCH {
465    case "amd64":
466        env = append(env"NACLENV_GOARCH=amd64p32")
467        args = []string{"sel_ldr_x86_64"}
468    case "386":
469        env = append(env"NACLENV_GOARCH=386")
470        args = []string{"sel_ldr_x86_32"}
471    case "arm":
472        env = append(env"NACLENV_GOARCH=arm")
473        selLdrerr := exec.LookPath("sel_ldr_arm")
474        if err != nil {
475            return nilerr
476        }
477        args = []string{"nacl_helper_bootstrap_arm"selLdr"--reserved_at_zero=0xXXXXXXXXXXXXXXXX"}
478    default:
479        return nilerrors.New("native client does not support GOARCH=" + runtime.GOARCH)
480    }
481
482    cmd := p.cmd(""append(args"-l""/dev/null""-S""-e"bin)...)
483    cmd.Env = append(cmd.Envenv...)
484
485    return cmdnil
486}
487
488func packageName(body string) (stringerror) {
489    ferr := parser.ParseFile(token.NewFileSet(), "prog.go",
490        strings.NewReader(body), parser.PackageClauseOnly)
491    if err != nil {
492        return ""err
493    }
494    return f.Name.String(), nil
495}
496
497// messageWriter is an io.Writer that converts all writes to Message sends on
498// the out channel with the specified id and kind.
499type messageWriter struct {
500    kind string
501    out  chan<- *Message
502}
503
504func (w *messageWriterWrite(b []byte) (n interr error) {
505    w.out <- &Message{Kindw.kindBodysafeString(b)}
506    return len(b), nil
507}
508
509// safeString returns b as a valid UTF-8 string.
510func safeString(b []bytestring {
511    if utf8.Valid(b) {
512        return string(b)
513    }
514    var buf bytes.Buffer
515    for len(b) > 0 {
516        rsize := utf8.DecodeRune(b)
517        b = b[size:]
518        buf.WriteRune(r)
519    }
520    return buf.String()
521}
522
MembersX
json
Message.Id
socketHandler.BlockStmt.RangeStmt_3635.m
process.cmd.args
process.done
process.Kill
process.naclCmd.BlockStmt.err
handshake.c
process.end.p
time
socketHandler.BlockStmt.enc
limiter
limiter.p
process.start.err
messageWriter.kind
exec
strings
Message.Kind
Options
socketHandler.c
log
url
handshake.err
startProcess.id
process.cmd.dir
messageWriter.Write.b
socketHandler.errc
socketHandler.BlockStmt.BlockStmt.err
process.start.out
process.start.BlockStmt.name
packageName.err
messageWriter.Write
txtar
process.start.p
socketHandler.BlockStmt.RangeStmt_3635.BlockStmt.err
limiter.in
process.naclCmd.p
process.naclCmd.bin
socketHandler
killer
shebang
shebang.path
process.cmd.p
process.naclCmd.pwd
packageName.f
RunScripts
Message.Options
handshake.port
socketHandler.BlockStmt.dec
process.start
process.start.cmd
process.naclCmd.args
safeString.b
os
msgLimit
buffer.out
process.startProcess.err
process.start.path
runtime
startProcess.body
startProcess.err
startProcess.args
process.Kill.p
shebang.fs
process.naclCmd
messageWriter.out
messageWriter.Write.w
handshake.req
process.path
startProcess
process.start.BlockStmt.err
process.naclCmd.BlockStmt.selLdr
utf8
limiter.out
process.startProcess.body
process.startProcess.cmd
process.start.body
safeString
io
websocket
handshake
socketHandler.BlockStmt.BlockStmt.RangeStmt_4256.p
process.run
process.end.m
buffer.BlockStmt.BlockStmt.BlockStmt.ok
process.startProcess.path
process.start.opt
isNacl.RangeStmt_11510.v
Options.Race
process.out
buffer
Message
startProcess.path
buffer.in
buffer.BlockStmt.kind
process.startProcess.args
safeString.BlockStmt.r
NewHandler
handshake.o
process.end.err
buffer.timeAfter
shebang.args
shebang.i
process.start.a
process.naclCmd.err
Message.Body
socketHandler.proc
errors
parser
startProcess.dest
limiter.BlockStmt.n
limiter.BlockStmt.RangeStmt_6235.m
process.cmd.cmd
process.naclCmd.env
messageWriter.Write.err
http
filepath
socketHandler.in
process.startProcess.p
process.cmd
process.end
process.start.args
isNacl
NewHandler.origin
process.startProcess
process.start.RangeStmt_9901.f
messageWriter
safeString.buf
handshake._
process.start.hasModfile
token
ioutil
socketHandler.out
packageName.body
safeString.BlockStmt.size
process
startProcess.BlockStmt.RangeStmt_4835.m
buffer.BlockStmt.buf
process.naclCmd.cmd
bytes
startProcess.opt
packageName
net
socketHandler.BlockStmt.BlockStmt.m
buffer.BlockStmt.BlockStmt.BlockStmt.m
shebang.body
process.start.bin
messageWriter.Write.n
Members
X