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  }