47
From Node to Go John Maxwell / @jmaxyz

From Node to Go

Embed Size (px)

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/

Citation preview

Page 1: From Node to Go

From Node to GoJohn Maxwell / @jmaxyz

Page 2: From Node to Go

The Agile League agileleague.com@agileleague

Page 3: From Node to Go
Page 4: From Node to Go
Page 5: From Node to Go

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

Demo time

Page 7: From Node to Go
Page 8: From Node to Go

"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

"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

"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

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

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

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

Why Go?

Page 15: From Node to Go
Page 16: From Node to Go
Page 17: From Node to Go

Why Go?

Page 18: From Node to Go

Go Primer

Page 19: From Node to Go

Blocking I/O & Synchronous Execution

Page 20: From Node to Go

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

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

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

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

"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

"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

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

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

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

"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

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

"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

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

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

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

Order of operations

1. Listen for new messages

2. Send the responses

3. Then close the connection

Page 36: From Node to Go

Order of implementation

1. Send the responses

2. Listen for new messages

3. Then close the connection

Page 37: From Node to Go

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

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

} 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

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

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

}} 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

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

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

–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

“ 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

John Maxwell

@[email protected]

agileleague.com@agileleague