from node to go
DESCRIPTION
This presentation covers how I wrote a little 39 line Node program and adapted it into an 88 line Go program. Serves as a "show me the code" example of getting started with Go. Code is available at: https://github.com/agileleague/httpwebsockettest The Node server in this project is hosted on Heroku at: http://httpwebsocketspeedtest.herokuapp.com/TRANSCRIPT
![Page 1: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/1.jpg)
From Node to GoJohn Maxwell / @jmaxyz
![Page 3: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/3.jpg)
![Page 4: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/4.jpg)
![Page 5: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/5.jpg)
Functional Spec• Serve static HTML, CSS, and JS assets!
• /xhr: HTTP endpoint!
• {"now":"2014-08-12T03:29:23.721Z"}!
• /ws: Websocket endpoint!
• {"id":9,"now":"2014-08-12T03:30:03.398Z"}!
• Sending “delay” param to /xhr or /ws will trigger a 100ms delay in response
![Page 6: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/6.jpg)
Demo time
![Page 7: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/7.jpg)
![Page 8: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/8.jpg)
"use strict"; var http = require('http');var express = require('express'); var WebSocketServer = require('ws').Server;var app = express();app.use(express.static("./public"));app.use(app.router);app.get('/xhr', function (req, res) { bufferResponse(function () { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(buildResponse()); }, req.query.delay == '1'); });var port = process.env.PORT || 3000; var server = http.createServer(app).listen(port);var wss = new WebSocketServer({server: server, path: "/ws"});wss.on('connection', function(socket) { socket.on('message', function(message) { message = JSON.parse(message); bufferResponse(function () { socket.send(buildResponse({id: message.id})); }, message.delay == '1'); });});console.log("listening on " + port);var buildResponse = function (properties) { properties = properties || {}; properties.now = new Date(); return JSON.stringify(properties);}; var bufferResponse = function (responder, delay) { delay ? setTimeout(responder, 100) : responder();};
Node Server
![Page 9: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/9.jpg)
"use strict"; var http = require('http');var express = require('express'); var WebSocketServer = require('ws').Server;var app = express();app.use(express.static("./public"));app.use(app.router);app.get('/xhr', function (req, res) { bufferResponse(function () { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(buildResponse()); }, req.query.delay == '1'); });var port = process.env.PORT || 3000; var server = http.createServer(app).listen(port);var wss = new WebSocketServer({server: server, path: "/ws"});wss.on('connection', function(socket) { socket.on('message', function(message) { message = JSON.parse(message); bufferResponse(function () {
Node Server
![Page 10: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/10.jpg)
"use strict"; var http = require('http');var express = require('express'); var WebSocketServer = require('ws').Server;var app = express();app.use(express.static("./public"));app.use(app.router);app.get('/xhr', function (req, res) { bufferResponse(function () { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(buildResponse()); }, req.query.delay == '1'); });var port = process.env.PORT || 3000; var server = http.createServer(app).listen(port);var wss = new WebSocketServer({server: server, path: "/ws"});wss.on('connection', function(socket) { socket.on('message', function(message) { message = JSON.parse(message); bufferResponse(function () {
Node Server
![Page 11: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/11.jpg)
app.get('/xhr', function (req, res) { bufferResponse(function () { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(buildResponse()); }, req.query.delay == '1'); });var port = process.env.PORT || 3000; var server = http.createServer(app).listen(port);var wss = new WebSocketServer({server: server, path: "/ws"});wss.on('connection', function(socket) { socket.on('message', function(message) { message = JSON.parse(message); bufferResponse(function () { socket.send(buildResponse({id: message.id})); }, message.delay == '1'); });});console.log("listening on " + port);var buildResponse = function (properties) { properties = properties || {}; properties.now = new Date(); return JSON.stringify(properties);};
Node Server
![Page 12: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/12.jpg)
message = JSON.parse(message); bufferResponse(function () { socket.send(buildResponse({id: message.id})); }, message.delay == '1'); });});console.log("listening on " + port);var buildResponse = function (properties) { properties = properties || {}; properties.now = new Date(); return JSON.stringify(properties);}; var bufferResponse = function (responder, delay) { delay ? setTimeout(responder, 100) : responder();};
Node Server
![Page 13: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/13.jpg)
Functional Spec• Serve static HTML, CSS, and JS assets!
• /xhr: HTTP endpoint!
• {"now":"2014-08-12T03:29:23.721Z"}!
• /ws: Websocket endpoint!
• {"id":9,"now":"2014-08-12T03:30:03.398Z"}!
• Sending “delay” param to /xhr or /ws will trigger a 100ms delay in response
![Page 14: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/14.jpg)
Why Go?
![Page 15: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/15.jpg)
![Page 16: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/16.jpg)
![Page 17: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/17.jpg)
Why Go?
![Page 18: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/18.jpg)
Go Primer
![Page 19: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/19.jpg)
Blocking I/O & Synchronous Execution
![Page 20: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/20.jpg)
Capitalization is important
• Identifiers (functions, methods, and types) are only exported if they are capitalized
• No public/private keywords
![Page 21: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/21.jpg)
Static Typing• Type of every identifier is set and cannot be altered!
• Variables (function parameters and locals)!
• Return values!
• Elements of Structs, Maps, and Slices!
• Channels!
• Compile time enforcement!
• Function arguments are passed by value
![Page 22: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/22.jpg)
Concurrency Support• Goroutines!
• Any function can be called under its own Goroutine!
• Go organizes threads/processes internally!
• Channels!
• How Goroutines communicate
![Page 23: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/23.jpg)
package mainimport ( "net/http" "github.com/gorilla/mux" "github.com/gorilla/websocket" "time" "encoding/json" "fmt" "os") func main() { r := mux.NewRouter() r.HandleFunc("/xhr", handleXHR) r.HandleFunc("/ws", handleWS) r.PathPrefix("/").Handler( http.FileServer(http.Dir("./public/")), )
Web Server
![Page 24: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/24.jpg)
"os") func main() { r := mux.NewRouter() r.HandleFunc("/xhr", handleXHR) r.HandleFunc("/ws", handleWS) r.PathPrefix("/").Handler( http.FileServer(http.Dir("./public/")), ) port := os.Getenv("PORT") if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) { if r.FormValue("delay") == "1" { time.Sleep(100 * time.Millisecond) } response, _ := json.Marshal(WebResponse{time.Now()}) w.Write(response)
Web Server
![Page 25: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/25.jpg)
"os") func main() { r := mux.NewRouter() r.HandleFunc("/xhr", handleXHR) r.HandleFunc("/ws", handleWS) r.PathPrefix("/").Handler( http.FileServer(http.Dir("./public/")), ) port := os.Getenv("PORT") if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) {
main()
![Page 26: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/26.jpg)
if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) { if r.FormValue("delay") == "1" { time.Sleep(100 * time.Millisecond) } response, _ := json.Marshal(WebResponse{time.Now()}) w.Write(response)} type WebResponse struct { Now time.Time `json:"now" ̀} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, }
HTTP request handler
![Page 27: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/27.jpg)
if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) { if r.FormValue("delay") == "1" { time.Sleep(100 * time.Millisecond) } response, _ := json.Marshal(WebResponse{time.Now()}) w.Write(response)} type WebResponse struct { Now time.Time `json:"now" ̀} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, }
JSON serialization
![Page 28: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/28.jpg)
if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) { if r.FormValue("delay") == "1" { time.Sleep(100 * time.Millisecond) } response, _ := json.Marshal(WebResponse{time.Now()}) w.Write(response)} type WebResponse struct { Now time.Time `json:"now" ̀} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, }
JSON serialization
![Page 29: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/29.jpg)
"fmt" "os") func main() { r := mux.NewRouter() r.HandleFunc("/xhr", handleXHR) r.HandleFunc("/ws", handleWS) r.PathPrefix("/").Handler( http.FileServer(http.Dir("./public/")), ) port := os.Getenv("PORT") if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) { if r.FormValue("delay") == "1" { time.Sleep(100 * time.Millisecond) } response, _ := json.Marshal(WebResponse{time.Now()}) w.Write(response)} type WebResponse struct { Now time.Time `json:"now" ̀} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024,
Web Server
![Page 30: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/30.jpg)
w.Write(response)} type WebResponse struct { Now time.Time `json:"now" ̀} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) { for { content, more := <-writes conn.WriteJSON(content) if !more { return } }} type WSRequest struct { Id int `json:id ̀ Delay bool `json:delay ̀} func (request *WSRequest) respond( writes chan WSResponse) { if request.Delay { time.Sleep(100 * time.Millisecond) } response := WSResponse{time.Now(), request.Id} writes <- response} type WSResponse struct { Now time.Time `json:"now" ̀ Id int `json:"id" ̀}
WebSocket Server
![Page 31: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/31.jpg)
"os") func main() { r := mux.NewRouter() r.HandleFunc("/xhr", handleXHR) r.HandleFunc("/ws", handleWS) r.PathPrefix("/").Handler( http.FileServer(http.Dir("./public/")), ) port := os.Getenv("PORT") if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) { if r.FormValue("delay") == "1" {
Integration with Web Server
![Page 32: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/32.jpg)
ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) {
WebSocket Server
![Page 33: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/33.jpg)
Now time.Time `json:"now" ̀} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }}
Open Connection
![Page 34: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/34.jpg)
ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) {
WebSocket Server
![Page 35: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/35.jpg)
Order of operations
1. Listen for new messages
2. Send the responses
3. Then close the connection
![Page 36: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/36.jpg)
Order of implementation
1. Send the responses
2. Listen for new messages
3. Then close the connection
![Page 37: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/37.jpg)
ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) {
WebSocket Server
![Page 38: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/38.jpg)
ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) {
Enable concurrent responses
![Page 39: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/39.jpg)
} go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) { for { content, more := <-writes conn.WriteJSON(content) if !more { return } }} type WSRequest struct { Id int `json:id ̀ Delay bool `json:delay ̀
Receive and Send Responses
![Page 40: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/40.jpg)
ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) {
Enable concurrent responses
![Page 41: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/41.jpg)
ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) {
Listen on Connection
![Page 42: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/42.jpg)
}} type WSRequest struct { Id int `json:id ̀ Delay bool `json:delay ̀} func (request *WSRequest) respond( writes chan WSResponse) { if request.Delay { time.Sleep(100 * time.Millisecond) } response := WSResponse{time.Now(), request.Id} writes <- response} type WSResponse struct { Now time.Time `json:"now" ̀ Id int `json:"id" ̀}
Build Response
![Page 43: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/43.jpg)
package mainimport ( "net/http" "github.com/gorilla/mux" "github.com/gorilla/websocket" "time" "encoding/json" "fmt" "os") func main() { r := mux.NewRouter() r.HandleFunc("/xhr", handleXHR) r.HandleFunc("/ws", handleWS) r.PathPrefix("/").Handler( http.FileServer(http.Dir("./public/")), ) port := os.Getenv("PORT") if port == "" { port = "8080" } fmt.Printf("listening on %s\n", port) http.ListenAndServe(":" + port, r)} func handleXHR(w http.ResponseWriter, r *http.Request) { if r.FormValue("delay") == "1" { time.Sleep(100 * time.Millisecond) } response, _ := json.Marshal(WebResponse{time.Now()}) w.Write(response)} type WebResponse struct { Now time.Time `json:"now" ̀} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } writes := make(chan WSResponse) go respondWS(conn, writes) for { message := WSRequest{} err := conn.ReadJSON(&message) if err != nil { close(writes) return } go message.respond(writes) }} func respondWS(conn *websocket.Conn, writes chan WSResponse) { for { content, more := <-writes conn.WriteJSON(content) if !more { return } }} type WSRequest struct { Id int `json:id ̀ Delay bool `json:delay ̀} func (request *WSRequest) respond( writes chan WSResponse) { if request.Delay { time.Sleep(100 * time.Millisecond) } response := WSResponse{time.Now(), request.Id} writes <- response} type WSResponse struct { Now time.Time `json:"now" ̀ Id int `json:"id" ̀}
Go Server
![Page 44: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/44.jpg)
What’s the score?
• 88 lines of Go vs 39 lines of JavaScript!
• JSON interaction was kind of clunky!
• Haven’t figured out how to deploy to Heroku!
• Doing what the cool kids are doing
![Page 45: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/45.jpg)
–Effective Go
“ A straightforward translation of a C++ or Java program into Go is unlikely to produce a satisfactory result—Java programs are written in Java, not Go.”
![Page 46: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/46.jpg)
“ A straightforward translation of a C++ or Java JavaScript program into Go is unlikely to produce a satisfactory result—Java JavaScript programs are written in Java JavaScript, not Go.”
![Page 47: From Node to Go](https://reader033.vdocuments.site/reader033/viewer/2022051110/54b6f2194a7959fd608b458a/html5/thumbnails/47.jpg)
John Maxwell
agileleague.com@agileleague