Colin's Journal

Colin's Journal: A place for thoughts about politics, software, and daily life.

January 23rd, 2016

Closures

I’ve found an increasing number of uses for closures in Go, to the point where they are one of my favourite features of the language.  Closures are created when an anonymous function is declared and it references variables in the surrounding function, for example:

func main() {
    a := 0
    b := func() {
        // a is shared between the main and anonymous function
        a++
    }
    b()
    // Value of a is 1
    fmt.Printf("Value of a is %v\n", a)
}

Functions are first class types in Go, so a function can return a function that uses the variables and arguments of it’s parent.  Such functions are a good alternative to producing a number of low value traditional struct{} types with a single method.

Closures also help in producing loosely coupled functions that serve as end points for a web service.  Dependency Injection is used to provide the services required by the end point in a clearer way than defining end points as methods on a struct that holds all of the potential dependencies.

Closures are also good for creating iterators / generators, which don’t come up that often, but are a good way to encapsulate the complexity of navigating a complex data structure.  By passing functions to functions that return functions, it’s possible to create simple micro-languages that make filtering and processing data records much easier to think about.

Finally closures enable in-line inversion of control in a very natural way.  I’ve found this particular useful for iterating over SQL queries, significantly reducing boilerplate code and improving consistency of error handling.

While I’m clearly a big fan of using Closures in Go, you do have to be careful about exactly which variables you are capturing in a closure.  For example:

func loop() {
    var funcs []func() int

    for i := 0; i < 5; i++ {
        funcs = append(funcs, func() int {
            return i
        })
    }

    for _, f := range funcs {
        fmt.Printf("Func value: %v\n", f())
    }
}

The closures being created in this loop all capture the same variable i that is shared between them.  As a result the output of the function is a series of 5s – the value of i at the end of the loop.  If we create a new variable on each iteration of the loop and capture the value there, we get the desired behaviour of printing out 0,1,2,3,4:

func loop() {
    var funcs []func() int

    for i := 0; i < 5; i++ {
        j := i
        funcs = append(funcs, func() int {
            return j
        })
    }

    for _, f := range funcs {
        fmt.Printf("Func value: %v\n", f())
    }
}

 

October 4th, 2015

HTTP Push Notfications

 

Arran Beach

Arran Beach

Owlauth allows users to register a device (e.g. a phone) and then use this device to confirm their identify when logging into a website or application.  For this to work the Owlauth app running on the phone needs to be able to receive push notifications of authentication requests.  There are many centralised ways to push notifications to a phone, but as each domain owner can run their own Owlauth server, a centralised solution isn’t a good fit.

By taking advantage of Go’s cheap handling of concurrent processing with Goroutines, a simple HTTP based push approach can be used.  Clients connect to the server:

  1. Passing a Bearer token that authorizes them as a registered device.
  2. Passing an If-None-Match ETag if it has one.
  3. The HTTP GET then blocks until either:
    1. The HTTP Keep Alive timeout is approaching – in which case the server returns nothing.
    2. A new authentication request is available or an existing request goes away.
  4. When the GET returns, an ETag header reflecting the current state.
  5. The client loops, issuing a new HTTP GET request with the latest ETag.

The server returns before the Keep Alive timeout, which means that the client will reuse the same TCP (and SSL) session as for the first request.  This makes this timeout and GET operation an effective ping that proves the connection is still established.  If the TCP connection becomes invalid, the client’s HTTP library will open a new connection to the server, giving the desired reconnect behaviour.

The server implementation in Go is straightforward:

        var requestedEtag string
        requestedEtag = r.Header.Get("If-None-Match")

        authChannel := reqStore.RegisterListener(dev.DeviceOwner)
        defer reqStore.UnregisterListener(dev.DeviceOwner, authChannel)
        clientGone := w.(http.CloseNotifier).CloseNotify()
        var request *reqdb.PendingRequests
        timeout := time.NewTimer(DeviceRequestGetTimeout)

        for {
            select {
            case req := <-authChannel:
                if req.GetEtag() != requestedEtag {
                    request = &req
                    // Return the ETag
                    w.Header().Add("ETag", req.GetEtag())
                    return request, nil
                }
                // Just loop and try again.
            case <-clientGone:
                // Nothing to be done - just return
                log.Printf("Device gone - returning\n")
                w.Header().Add("ETag", requestedEtag)
                return nil, nil
            case <-timeout.C:
                log.Printf("Device request get timeout reached.\n")
                w.Header().Add("ETag", requestedEtag)
                return nil, nil
            }
        }

The reqStore object keeps track of outstanding authorisation requests and provides the details on a channel to all registered clients that are sat waiting in this GET.

The only other element of the equation on the client side is adopting a suitable retry strategy when http connections are not working.  For desktops that could be just a simple back-off to a few seconds sleep.  For the Android client it needs to taken into account the current network state to avoid excessive battery drain.

I’ve now got a basic version of this working on my phone.  I’ll run it for a while and see how much battery impact it has.

Copyright 2015 Colin Stewart

Email: colin at owlfish.com