blob: 0d463f1c1e14964ea8cde1e4c8751bb7c9c27c61 [file] [log] [blame]
Inside the "present" tool
Andrew Gerrand
Google
@enneff
adg@golang.org
https://go.dev
* 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` package 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/,/^}/