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  }