|
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/,/^}/ |
|
|
|
|