Date post: | 15-Jan-2015 |
Category: |
Technology |
Upload: | john-maxwell |
View: | 516 times |
Download: | 0 times |
From Node to GoJohn Maxwell / @jmaxyz
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