Upload
john-maxwell
View
521
Download
0
Tags:
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
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
agileleague.com@agileleague