Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Doing stuff with Go

Doing stuff with Go

A quick talk I did at the Melbourne Go Nuts meetup (May 2013)

Ivan Vanderbyl

May 16, 2013
Tweet

More Decks by Ivan Vanderbyl

Other Decks in Programming

Transcript

  1. 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)
  2. 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
  3. 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.
  4. @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.
  5. A quick story Thursday, 16 May 13 He also can’t

    be trusted with a computer while getting beer at RailsCamp.
  6. ZOMG GO IS AMAZINGG (Actual quote) Thursday, 16 May 13

    let’s rewrite it in Go, ZOMG GO IS AMAZING.
  7. 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...
  8. Go ALL THE THINGS Thursday, 16 May 13 So I

    immediately wanted to Go all the things
  9. 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.
  10. 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
  11. <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.
  12. 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
  13. 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,
  14. 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
  15. 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
  16. Not Maintained Not a server Not complete Not Maintained Not

    a server Thursday, 16 May 13 All except for Socket.io
  17. 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...
  18. 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.
  19. [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
  20. 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...
  21. 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