Slide 1

Slide 1 text

Doing stuff with Go A true story @IvanVanderbyl Thursday, 16 May 13 Doing stuff with Go A true story. (Also the title of my first book)

Slide 2

Slide 2 text

RailsCamp NZ#2 March 2013 Thursday, 16 May 13 I was at RailsCamp NZ back in March

Slide 3

Slide 3 text

RailsCamp isn’t for learning Rails Thursday, 16 May 13 For those who have never been to a RailsCamp, you should know that RailsCamp is not for learning Rails. It is for climbing mountains and drinking whisky, sometimes at the same time, if you’re Dylan Egan. But it’s also widely accepted as a place to learn new stuff, like Go

Slide 4

Slide 4 text

Thursday, 16 May 13 Does anyone know this guy? This is Josh, he builds Travis CI, or as his twitter puts it, he ships technical debt daily.

Slide 5

Slide 5 text

@j2h Josh Kalderimis Thursday, 16 May 13 Does anyone know this guy? This is Josh, he builds Travis CI, or as his twitter puts it, he ships technical debt daily.

Slide 6

Slide 6 text

A quick story Thursday, 16 May 13 He also can’t be trusted with a computer while getting beer at RailsCamp.

Slide 7

Slide 7 text

crashlog.io Thursday, 16 May 13 That aside, I was telling him about CrashLog, and he said...

Slide 8

Slide 8 text

ZOMG GO IS AMAZINGG (Actual quote) Thursday, 16 May 13 let’s rewrite it in Go, ZOMG GO IS AMAZING.

Slide 9

Slide 9 text

TOP SECRET TRAVIS REDESIGN Thursday, 16 May 13 So he sat down with me for two days and taught me Go. Thanks Josh, I may now never get around to redesigning Travis CI for you because I like Go so much...

Slide 10

Slide 10 text

Go at work Thursday, 16 May 13 So I immediately wanted to Go all the things

Slide 11

Slide 11 text

Go ALL THE THINGS Thursday, 16 May 13 So I immediately wanted to Go all the things

Slide 12

Slide 12 text

Thursday, 16 May 13 So using Go at work. If you haven’t seen what we do, basically we let you run iOS apps inside a web browser. I’m not going to talk about how we do that.

Slide 13

Slide 13 text

Thursday, 16 May 13 But We do use a lot of Node on the backend. And we have one piece of infrastructure which is really simple

Slide 14

Slide 14 text

<300 lines Thursday, 16 May 13 It’s really small, but it is critical to everything working. It is also really stupid, and in my spare time I’ve almost rewritten it in Go.

Slide 15

Slide 15 text

Request Locate the user by IP Find available servers {server id: 1 state: “busy”} [{server state: “ready”}] Find closest server Request session Reply to user Thursday, 16 May 13 Request => Geo Locate user => find nearest available server => Ask the server to start a session => Send server details to User

Slide 16

Slide 16 text

AMQP Async Express GeoIP Redis Socket.io Winston Winston-Loggly Thursday, 16 May 13 Now we do rely on quite a few node modules for this to work, which is telling in just how few lines of code we wrote to make this work. However,

Slide 17

Slide 17 text

AMQP Async Express GeoIP Redis Socket.io Winston Winston-Loggly Thursday, 16 May 13 Most of these we don’t need when using Go, they have equivalents in the language or there are good packages

Slide 18

Slide 18 text

AMQP Async Express GeoIP Redis Socket.io Winston Winston-Loggly github.com/streadway/amqp github.com/gorilla/mux C bindings to libgeoip github.com/alphazero/Go-Redis Thursday, 16 May 13 All except for Socket.io

Slide 19

Slide 19 text

Not Maintained Not a server Not complete Not Maintained Not a server Thursday, 16 May 13 All except for Socket.io

Slide 20

Slide 20 text

How hard can that be? Thursday, 16 May 13 I love rolling my own things, so I figured, how hard could Socket.io be, it’s just a messaging format and some transports...

Slide 21

Slide 21 text

Socket.IO HTTP requests Socket.IO HTTP URIs take the form of: [scheme] '://' [host] '/' [namespace] '/' [protocol version] '/' [transport id] '/' [session id] '/' ( '?' [query] ) Only the methods GET and POST are utilized (for the sake of compatibility with old user agents), and their usage varies according to each transport. The main transport connection is always a GET request. URI scheme The URI scheme is decided based on whether the client requires a secure connection or not. Defaults to http, but https is the recommended one. URI host The host where the Socket.IO server is located. In the browser environment, it defaults to the host that runs the page where the client is loaded (location.host) Namespace The connecting client has to provide the namespace where the Socket.IO requests are intercepted. This defaults to socket.io for all client and server distributions. Protocol version Each client should ship with the revision ID it supports, available as a public interface to developers. For example, the browser client supports io.protocolVersion. Transport ID Socket.io spec Thursday, 16 May 13 The spec looks pretty simple, accept a HTTP request, do an upgrade to a transport, transport some frames. Simple.

Slide 22

Slide 22 text

[message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data]) 5:::{"name":"ping","args":[{"id":1}]} Socket.io Frame Thursday, 16 May 13 The message frame looked pretty straight forward, and Go has decent web socket support

Slide 23

Slide 23 text

github.com/appio/go-socketio Thursday, 16 May 13 So I forked the socketio project from 2 years ago and spent the weekend rebuilding it, and adding tests...

Slide 24

Slide 24 text

socket.io/tests/parser.test.js go-socketio/parser_test.go 'decoding error packet with reason and advice': function () { parser.decodePacket('7:::2+0').should().eql({ type: 'error' , reason: 'unauthorized' , advice: 'reconnect' , endpoint: '' }); }, 'decoding error packet with endpoint': function () { parser.decodePacket('7::/woot').should().eql({ type: 'error' , reason: '' , advice: '' , endpoint: '/woot' }); }, 'decoding ack packet': function () { parser.decodePacket('6:::140').should().eql({ type: 'ack' , ackId: '140' , endpoint: '' , args: [] }); }, 'decoding ack packet with args': function () { parser.decodePacket('6:::12+["woot","wa"]').should().eql({ type: 'ack' , ackId: '12' , endpoint: '' , args: ['woot', 'wa'] }); }, 'decoding ack packet with bad json': function () { var thrown = false; try { parser.decodePacket('6:::1+{"++]').should().eql({ type: 'ack' , ackId: '1' , endpoint: '' , args: [] }); } catch (e) { thrown = true; type TestFrame struct { ! Packet string ! Type uint8 ! Endpoint string ! Parsable bool } var frames []TestFrame func TestDecodePackets(t *testing.T) { ! frames = append(frames, TestFrame{Packet: `7:::0`, Type: PACKET_ERROR, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `7:::`, Type: PACKET_ERROR, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `7:::2+0`, Type: PACKET_ERROR, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `7::/woot`, Type: PACKET_ERROR, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `6:::140`, Type: PACKET_ACK, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `6:::12+["woot","wa"]`, Type: PACKET_ACK, Parsable: true ! frames = append(frames, TestFrame{Packet: `6:::1+{"++]`, Type: PACKET_ACK, Parsable: false}) ! frames = append(frames, TestFrame{Packet: `4:::"2"`, Type: PACKET_JSONMESSAGE, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `4:1+::{"a":"b"}`, Type: PACKET_JSONMESSAGE, Parsable: t ! frames = append(frames, TestFrame{Packet: `5:::{"name":"ping","args":[{"id":1}]}`, Type: PACKET_EV ! frames = append(frames, TestFrame{Packet: `5:::{"name":"ping","args":{"id":1}}`, Type: PACKET_EVEN ! frames = append(frames, TestFrame{Packet: `5:::{"name":"ping","args":[{"id":"1"}]}`, Type: PACKET_ ! frames = append(frames, TestFrame{Packet: `5:::{"name":"woot"}`, Type: PACKET_EVENT, Parsable: tru ! frames = append(frames, TestFrame{Packet: `5:1+::{"name":"tobi"}`, Type: PACKET_EVENT, Parsable: t ! frames = append(frames, TestFrame{Packet: `5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}`, Type: ! frames = append(frames, TestFrame{Packet: `3:::woot`, Type: PACKET_MESSAGE, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `3:5:/tobi`, Type: PACKET_MESSAGE, Endpoint: "/tobi", Pa ! frames = append(frames, TestFrame{Packet: `2:::`, Type: PACKET_HEARTBEAT, Parsable: true}) ! frames = append(frames, TestFrame{Packet: `1::/tobi`, Type: PACKET_CONNECT, Endpoint: "/tobi", Par ! frames = append(frames, TestFrame{Packet: `1::/test:?test=1`, Type: PACKET_CONNECT, Endpoint: "/te ! frames = append(frames, TestFrame{Packet: `0::/woot`, Type: PACKET_DISCONNECT, Endpoint: "/woot", ! totalFrames := len(frames) ! decodedFrames := 0 ! for _, data := range frames { ! ! packet, err := decodePacket([]byte(data.Packet)) ! ! // log.Println(data.Packet) ! ! if data.Parsable == false { ! ! ! // Should not be parsable and raise an error ! ! ! if err != nil { ! ! ! } else { ! ! ! ! t.Fatal("Expected an error parsing invalid packet") ! ! ! } ! ! } else { ! ! ! if err != nil { ! ! ! ! t.Fatalf("Error with packet '%s': %s", data.Packet, err.Error()) ! ! ! } Thursday, 16 May 13 Tests based on the socket.io client tests

Slide 25

Slide 25 text

github.com/appio/go-socketio Thursday, 16 May 13 Not quite ready yet, but it will be shortly.

Slide 26

Slide 26 text

tomtrex.tumblr.com/ Thursday, 16 May 13 All except for Socket.io