go.talks: add "inside present" talk
R=r
CC=golang-dev
https://golang.org/cl/6718050
diff --git a/2012/insidepresent.slide b/2012/insidepresent.slide
new file mode 100644
index 0000000..842923b
--- /dev/null
+++ b/2012/insidepresent.slide
@@ -0,0 +1,144 @@
+Inside the "present" tool
+
+Andrew Gerrand
+Google
+@enneff
+adg@golang.org
+http://golang.org
+
+
+* The Playground API
+
+The API used by the Playground (and the Tour) is a simple HTTP POST request
+that returns a JSON-encoded response.
+
+Request:
+
+ POST /compile HTTP/1.1
+ Host:play.golang.org
+ Content-Length:113
+ Content-Type:application/x-www-form-urlencoded; charset=UTF-8
+
+ body=package+main%0A%0Aimport+%22fmt%22%0A%0Afunc+main()+%7B%0A%09fmt.Println(%22Hello%2C+playground%22)%0A%7D%0A
+
+Response body:
+
+ {"compile_errors":"","output":"Hello, playground\n"}
+
+
+* Playground drawbacks
+
+The compile service has no concept of time. (Necessary to limit resource use.)
+
+The API reflects this; output is sent in one blob, not streamed.
+
+Even when running locally, the API is bad for demonstrating code that uses time.
+
+Rob needed to use time in his _Go_Concurrency_Patterns_ talk.
+
+
+* Enter WebSockets
+
+WebSockets are a bi-directional communication channel between a JavaScript program running in a web browser and a web server. They are part of HTML 5.
+
+The `websocket` pacakge in Go's `go.net` sub-repository provides a WebSocket client and server.
+
+I thought I could use WebSockets to stream program output to a running
+presentation.
+
+And thus the `present` tool was born.
+
+
+* Hello, WebSocket
+
+.code insidepresent/websocket.js
+.play insidepresent/websocket.go
+
+
+* Messages
+
+The client (browser) and server (present) communicate with JSON-encoded messages.
+
+.code insidepresent/socket.go /Message is/,/^}/
+
+Go's `encoding/json` format can convert these `Message` values to and from JSON.
+
+Go:
+
+ Message{Id: "0", Kind: "run", Body: `package main; func main() { print("hello"); }`}
+
+JSON:
+
+ {"Id":"0","Kind":"run","Body":"package main; func main() { print(\"hello\"); }"}
+
+
+* On the wire
+
+.play insidepresent/hello.go
+
+.html insidepresent/wire.html
+
+
+* Implementation
+
+* socketHandler (1/3)
+
+First, register the handler with the `net/http` package:
+
+ http.Handle("/socket", websocket.Handler(socketHandler))
+
+Implementation:
+
+.code insidepresent/socket.go /func socketHandler/,/errc/
+
+* socketHandler (2/3)
+
+.code insidepresent/socket.go /Decode messages/,/END/
+
+* socketHandler (3/3)
+
+.code insidepresent/socket-simple.go /Start and kill/,/^}/
+
+
+* Process
+
+.code insidepresent/socket.go /Process represents/,/^}/
+
+* StartProcess
+
+.code insidepresent/socket.go /StartProcess builds/,/^}/
+
+* Process.start (1/2)
+
+.code insidepresent/socket.go /start builds/,/END/
+
+* Process.start (2/2)
+
+.code insidepresent/socket.go /build x\.go/,/^}/
+
+* Process.cmd
+
+.code insidepresent/socket.go /cmd builds/,/^}/
+.code insidepresent/socket.go /messageWriter is/,/END/
+
+* Process.wait and Process.end
+
+.code insidepresent/socket.go /wait waits/,/^}/
+.code insidepresent/socket.go /end sends/,/^}/
+
+* Process.Kill
+
+.code insidepresent/socket.go /Kill stops/,/^}/
+
+
+* One more thing
+
+* Limiting output (1/2)
+
+.code insidepresent/socket.go /switch m\.Kind/,/^ }/
+
+* Limiting output (2/2)
+
+.code insidepresent/socket.go /limiter returns/,/^}/
+
+
diff --git a/2012/insidepresent/hello.go b/2012/insidepresent/hello.go
new file mode 100644
index 0000000..308f6d1
--- /dev/null
+++ b/2012/insidepresent/hello.go
@@ -0,0 +1,10 @@
+package main
+
+import ( "fmt"; "time" )
+
+func main() {
+ for {
+ fmt.Println("Hello, Gophers!")
+ time.Sleep(time.Second)
+ }
+}
diff --git a/2012/insidepresent/socket-simple.go b/2012/insidepresent/socket-simple.go
new file mode 100644
index 0000000..aebadf9
--- /dev/null
+++ b/2012/insidepresent/socket-simple.go
@@ -0,0 +1,226 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !appengine
+
+package main
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+
+ "code.google.com/p/go.net/websocket"
+)
+
+const socketPresent = true
+
+func HandleSocket(path string) {
+ http.Handle(path, websocket.Handler(socketHandler))
+}
+
+const msgLimit = 1000 // max number of messages to send per session
+
+var uniq = make(chan int) // a source of numbers for naming temporary files
+
+func init() {
+ go func() {
+ for i := 0; ; i++ {
+ uniq <- i
+ }
+ }()
+}
+
+// Message is the wire format for the websocket connection to the browser.
+// It is used for both sending output messages and receiving commands, as
+// distinguished by the Kind field.
+type Message struct {
+ Id string // client-provided unique id for the process
+ Kind string // in: "run", "kill" out: "stdout", "stderr", "end"
+ Body string
+}
+
+// socketHandler handles the websocket connection for a given present session.
+// It handles transcoding Messages to and from JSON format, and starting
+// and killing Processes.
+func socketHandler(c *websocket.Conn) {
+ in, out := make(chan *Message), make(chan *Message)
+ errc := make(chan error, 1)
+
+ // Decode messages from client and send to the in channel.
+ go func() {
+ dec := json.NewDecoder(c)
+ for {
+ var m Message
+ if err := dec.Decode(&m); err != nil {
+ errc <- err
+ return
+ }
+ in <- &m
+ }
+ }()
+
+ // Receive messages from the out channel and encode to the client.
+ go func() {
+ enc := json.NewEncoder(c)
+ for m := range out {
+ if err := enc.Encode(m); err != nil {
+ errc <- err
+ return
+ }
+ }
+ }()
+ // END OMIT
+
+ // Start and kill Processes and handle errors.
+ proc := make(map[string]*Process)
+ for {
+ select {
+ case m := <-in:
+ switch m.Kind {
+ case "run":
+ proc[m.Id].Kill()
+ proc[m.Id] = StartProcess(m.Id, m.Body, out)
+ case "kill":
+ proc[m.Id].Kill()
+ }
+ case err := <-errc:
+ // A encode or decode has failed; bail.
+ log.Println(err)
+ // Shut down any running processes.
+ for _, p := range proc {
+ p.Kill()
+ }
+ return
+ }
+ }
+}
+
+// Process represents a running process.
+type Process struct {
+ id string
+ out chan<- *Message
+ done chan struct{} // closed when wait completes
+ run *exec.Cmd
+}
+
+// StartProcess builds and runs the given program, sending its output
+// and end event as Messages on the provided channel.
+func StartProcess(id, body string, out chan<- *Message) *Process {
+ p := &Process{
+ id: id,
+ out: out,
+ done: make(chan struct{}),
+ }
+ cmd, err := p.start(body)
+ if err != nil {
+ p.end(err)
+ return nil
+ }
+ p.run = cmd
+ go p.wait(cmd)
+ return p
+}
+
+// Kill stops the process if it is running and waits for it to exit.
+func (p *Process) Kill() {
+ if p == nil {
+ return
+ }
+ if p.run != nil {
+ p.run.Process.Kill()
+ }
+ <-p.done
+}
+
+// start builds and starts the given program, sending its output to p.out,
+// and returns the associated *exec.Cmd.
+func (p *Process) start(body string) (*exec.Cmd, error) {
+ // x is the base name for .go and executable files
+ x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq))
+ src := x + ".go"
+ bin := x
+ if runtime.GOOS == "windows" {
+ bin += ".exe"
+ }
+
+ // write body to x.go
+ defer os.Remove(src)
+ if err := ioutil.WriteFile(src, []byte(body), 0666); err != nil {
+ return nil, err
+ }
+ // END OMIT
+
+ // build x.go, creating x
+ dir, file := filepath.Split(src)
+ err := p.cmd(dir, "go", "build", "-o", bin, file).Run()
+ defer os.Remove(bin)
+ if err != nil {
+ return nil, err
+ }
+
+ // run x
+ cmd := p.cmd("", bin)
+ if err = cmd.Start(); err != nil {
+ return nil, err
+ }
+ return cmd, nil
+}
+
+// wait waits for the running process to complete and returns its error state.
+func (p *Process) wait(cmd *exec.Cmd) {
+ defer close(p.done)
+ p.end(cmd.Wait())
+}
+
+// end sends an "end" message to the client, containing the process id and the
+// given error value.
+func (p *Process) end(err error) {
+ m := &Message{Id: p.id, Kind: "end"}
+ if err != nil {
+ m.Body = err.Error()
+ }
+ p.out <- m
+}
+
+// cmd builds an *exec.Cmd that writes its standard output and error to the
+// Process' output channel.
+func (p *Process) cmd(dir string, args ...string) *exec.Cmd {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ cmd.Stdout = &messageWriter{p.id, "stdout", p.out}
+ cmd.Stderr = &messageWriter{p.id, "stderr", p.out}
+ return cmd
+}
+
+// messageWriter is an io.Writer that converts all writes to Message sends on
+// the out channel with the specified id and kind.
+type messageWriter struct {
+ id, kind string
+ out chan<- *Message
+}
+
+func (w *messageWriter) Write(b []byte) (n int, err error) {
+ w.out <- &Message{Id: w.id, Kind: w.kind, Body: string(b)}
+ return len(b), nil
+}
+
+// END OMIT
+
+var tmpdir string
+
+func init() {
+ // find real path to temporary directory
+ var err error
+ tmpdir, err = filepath.EvalSymlinks(os.TempDir())
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/2012/insidepresent/socket.go b/2012/insidepresent/socket.go
new file mode 100644
index 0000000..d3f54d6
--- /dev/null
+++ b/2012/insidepresent/socket.go
@@ -0,0 +1,249 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !appengine
+
+package main
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+
+ "code.google.com/p/go.net/websocket"
+)
+
+const socketPresent = true
+
+func HandleSocket(path string) {
+ http.Handle(path, websocket.Handler(socketHandler))
+}
+
+const msgLimit = 1000 // max number of messages to send per session
+
+var uniq = make(chan int) // a source of numbers for naming temporary files
+
+func init() {
+ go func() {
+ for i := 0; ; i++ {
+ uniq <- i
+ }
+ }()
+}
+
+// Message is the wire format for the websocket connection to the browser.
+// It is used for both sending output messages and receiving commands, as
+// distinguished by the Kind field.
+type Message struct {
+ Id string // client-provided unique id for the process
+ Kind string // in: "run", "kill" out: "stdout", "stderr", "end"
+ Body string
+}
+
+// socketHandler handles the websocket connection for a given present session.
+// It handles transcoding Messages to and from JSON format, and starting
+// and killing Processes.
+func socketHandler(c *websocket.Conn) {
+ in, out := make(chan *Message), make(chan *Message)
+ errc := make(chan error, 1)
+
+ // Decode messages from client and send to the in channel.
+ go func() {
+ dec := json.NewDecoder(c)
+ for {
+ var m Message
+ if err := dec.Decode(&m); err != nil {
+ errc <- err
+ return
+ }
+ in <- &m
+ }
+ }()
+
+ // Receive messages from the out channel and encode to the client.
+ go func() {
+ enc := json.NewEncoder(c)
+ for m := range out {
+ if err := enc.Encode(m); err != nil {
+ errc <- err
+ return
+ }
+ }
+ }()
+ // END OMIT
+
+ // Start and kill Processes and handle errors.
+ proc := make(map[string]*Process)
+ for {
+ select {
+ case m := <-in:
+ switch m.Kind {
+ case "run":
+ proc[m.Id].Kill()
+ lOut := limiter(in, out) // HL
+ proc[m.Id] = StartProcess(m.Id, m.Body, lOut) // HL
+ case "kill":
+ proc[m.Id].Kill()
+ }
+ case err := <-errc:
+ // A encode or decode has failed; bail.
+ log.Println(err)
+ // Shut down any running processes.
+ for _, p := range proc {
+ p.Kill()
+ }
+ return
+ }
+ }
+}
+
+// Process represents a running process.
+type Process struct {
+ id string
+ out chan<- *Message
+ done chan struct{} // closed when wait completes
+ run *exec.Cmd
+}
+
+// StartProcess builds and runs the given program, sending its output
+// and end event as Messages on the provided channel.
+func StartProcess(id, body string, out chan<- *Message) *Process {
+ p := &Process{
+ id: id,
+ out: out,
+ done: make(chan struct{}),
+ }
+ if err := p.start(body); err != nil {
+ p.end(err)
+ return nil
+ }
+ go p.wait()
+ return p
+}
+
+// Kill stops the process if it is running and waits for it to exit.
+func (p *Process) Kill() {
+ if p == nil {
+ return
+ }
+ p.run.Process.Kill()
+ <-p.done
+}
+
+// start builds and starts the given program, sends its output to p.out,
+// and stores the running *exec.Cmd in the run field.
+func (p *Process) start(body string) error {
+ // x is the base name for .go and executable files
+ x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq))
+ src := x + ".go"
+ bin := x
+ if runtime.GOOS == "windows" {
+ bin += ".exe"
+ }
+
+ // write body to x.go
+ defer os.Remove(src)
+ if err := ioutil.WriteFile(src, []byte(body), 0666); err != nil {
+ return err
+ }
+ // END OMIT
+
+ // build x.go, creating x
+ dir, file := filepath.Split(src)
+ err := p.cmd(dir, "go", "build", "-o", bin, file).Run()
+ defer os.Remove(bin)
+ if err != nil {
+ return err
+ }
+
+ // run x
+ cmd := p.cmd("", bin)
+ if err = cmd.Start(); err != nil {
+ return err
+ }
+
+ p.run = cmd
+ return nil
+}
+
+// wait waits for the running process to complete
+// and sends its error state to the client.
+func (p *Process) wait() {
+ defer close(p.done)
+ p.end(p.run.Wait())
+}
+
+// end sends an "end" message to the client, containing the process id and the
+// given error value.
+func (p *Process) end(err error) {
+ m := &Message{Id: p.id, Kind: "end"}
+ if err != nil {
+ m.Body = err.Error()
+ }
+ p.out <- m
+}
+
+// cmd builds an *exec.Cmd that writes its standard output and error to the
+// Process' output channel.
+func (p *Process) cmd(dir string, args ...string) *exec.Cmd {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ cmd.Stdout = &messageWriter{p.id, "stdout", p.out}
+ cmd.Stderr = &messageWriter{p.id, "stderr", p.out}
+ return cmd
+}
+
+// messageWriter is an io.Writer that converts all writes to Message sends on
+// the out channel with the specified id and kind.
+type messageWriter struct {
+ id, kind string
+ out chan<- *Message
+}
+
+func (w *messageWriter) Write(b []byte) (n int, err error) {
+ w.out <- &Message{Id: w.id, Kind: w.kind, Body: string(b)}
+ return len(b), nil
+}
+// END OMIT
+
+// limiter returns a channel that wraps dest. Messages sent to the channel are
+// sent to dest. After msgLimit Messages have been passed on, a "kill" Message
+// is sent to the kill channel, and only "end" messages are passed.
+func limiter(kill chan<- *Message, dest chan<- *Message) chan<- *Message {
+ ch := make(chan *Message)
+ go func() {
+ n := 0
+ for m := range ch {
+ switch {
+ case n < msgLimit || m.Kind == "end":
+ dest <- m
+ if m.Kind == "end" {
+ return
+ }
+ case n == msgLimit:
+ // Process produced too much output. Kill it.
+ kill <- &Message{Id: m.Id, Kind: "kill"}
+ }
+ n++
+ }
+ }()
+ return ch
+}
+
+var tmpdir string
+
+func init() {
+ // find real path to temporary directory
+ var err error
+ tmpdir, err = filepath.EvalSymlinks(os.TempDir())
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/2012/insidepresent/websocket.go b/2012/insidepresent/websocket.go
new file mode 100644
index 0000000..6d1547d
--- /dev/null
+++ b/2012/insidepresent/websocket.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "code.google.com/p/go.net/websocket"
+ "fmt"
+ "net/http"
+)
+
+func main() {
+ http.Handle("/", websocket.Handler(handler))
+ http.ListenAndServe("localhost:4000", nil)
+}
+
+func handler(c *websocket.Conn) {
+ var s string
+ fmt.Fscan(c, &s)
+ fmt.Println("Received:", s)
+ fmt.Fprint(c, "How do you do?")
+}
diff --git a/2012/insidepresent/websocket.js b/2012/insidepresent/websocket.js
new file mode 100644
index 0000000..f9809d6
--- /dev/null
+++ b/2012/insidepresent/websocket.js
@@ -0,0 +1,3 @@
+var sock = new WebSocket("ws://localhost:4000/");
+sock.onmessage = function(m) { console.log("Received:", m.data); }
+sock.send("Hello!\n")
diff --git a/2012/insidepresent/wire.html b/2012/insidepresent/wire.html
new file mode 100644
index 0000000..2a98a7d
--- /dev/null
+++ b/2012/insidepresent/wire.html
@@ -0,0 +1,15 @@
+<style>
+#wire {
+ border: 1px solid #E0E0E0;
+ background: #F0F0F0;
+ height: 300px;
+ font-family: 'Droid Sans Mono', 'Courier New', monospace;
+ font-size: 18px;
+ line-height: 24px;
+ overflow: auto;
+}
+#wire div { margin-bottom: 10px; }
+#wire .send { color: #900; }
+#wire .recv { color: #009; }
+</style>
+<div id="wire"></div>