From Node to Go

Post on 15-Jan-2015

516 views 0 download

Tags:

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

From Node to GoJohn Maxwell / @jmaxyz

The Agile League agileleague.com@agileleague

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

Demo time

"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

"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

"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

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

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

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

Why Go?

Why Go?

Go Primer

Blocking I/O & Synchronous Execution

Capitalization is important

• Identifiers (functions, methods, and types) are only exported if they are capitalized

• No public/private keywords

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

Concurrency Support• Goroutines!

• Any function can be called under its own Goroutine!

• Go organizes threads/processes internally!

• Channels!

• How Goroutines communicate

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

"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

"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()

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

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

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

"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

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

"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

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

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

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

Order of operations

1. Listen for new messages

2. Send the responses

3. Then close the connection

Order of implementation

1. Send the responses

2. Listen for new messages

3. Then close the connection

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

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

} 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

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

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

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

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

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

–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.”

“ 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.”

John Maxwell

@jmaxyzjohn@agileleague.com

agileleague.com@agileleague