Now that we’ve converted our notification data into a format that’s suitable for sending to Apple, our fledgling push provider needs a connection into APNs in order to send that data. Once again, Apple’s documentation comes to our rescue: the chapter “Provider Communication with Apple Push Notification Service” describes how to manage connections to the push notification service.
Among the important bits in this chapter:
- When connecting, the provider needs the SSL certificate from the Member Center available for authenticating itself to Apple.
- The provider should maintain a persistent connection to APNs, rather than connecting and disconnecting for each notification. (At higher volumes, Apple might even consider this behavior to be a DDOS attack!)
- There are two different environments for push notifications: development and production. A connection is specific to one app’s bundle ID, and therefore (at least in Omni’s bundle ID configuration) specific to one of these environments.
To handle all this, we need to build a little flexibility into the provider. We turned to the open-source gcfg library, which can be imported into a Go project simply by running
go get "code.google.com/p/gcfg"
and then import
ing that same URL at the head of a Go file. With this library,
we can define a configuration file that tells the provider about what APNs
host(s) it should connect to, and what certificates it should use along the way.
Let’s take a look first at the Go struct that the provider reads its APNs configuration into:
type ConnConfig struct {
Domain string
CertFile string
KeyFile string
}
This struct expresses a single connection to APNs. It defines the domain we’ll connect to, letting us switch between sandbox and production environments by changing the host we contact. It also lets us tell the provider where its certificates are, so that it can establish a secure connection and identify itself to APNs, all in one swoop.
For OmniFocus, though, we have multiple bundle IDs – one for the Universal version of the app, and one for the iPhone-only variant – so we’ll need a way to connect to APNs multiple times. To accomplish this, we parse something slightly more complex out of the configuration file:
func ParseConfigFile(filePath string) (map[string]*ConnConfig, error) {
var configuration struct {
Apns map[string]*ConnConfig
}
err := gcfg.ReadFileInto(&configuration, filePath)
if err != nil {
return nil, err
}
return configuration.Apns, nil
}
Instead of just reading a single instance of the ConnConfig
struct, we’ll read
an entire map of them, keyed by strings. The gcfg
package uses maps to
represent configuration subsections – instead of just specifying a single
apns
section, we can specify a bunch, each with its own key:
[apns "com.omnigroup.OmniFocus2.iPad"] ; Universal
domain = sandbox.push.apple.com
certFile = debug-universal.cer
keyFile = debug-universal.key
[apns "com.omnigroup.OmniFocus2.iPhone"] ; iPhone-only
domain = sandbox.push.apple.com
certFile = debug-iphone.cer
keyFile = debug-iphone.key
This way, parsing a configuration file can return one ConnConfig
struct for
each bundle ID that we’ll use to connect. The provider’s connection code can
then iterate over these structs, establishing multiple connections along the
way. For each connection, that code is fairly straightforward:
func (config *ConnConfig) connectAPNs() (*tls.Conn, error) {
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
if err != nil {
return nil, err
}
tlsConfig := new(tls.Config)
tlsConfig.Certificates = []tls.Certificate{cert}
host := net.JoinHostPort("gateway." + config.Domain, "2195")
conn, err := tls.Dial("tcp", host, tlsConfig)
if err != nil {
return nil, err
}
return conn, err
}
Once this connection is established, the provider can hang on to this tls.Conn
pointer as long as it needs to, sending multiple notifications – in the form of
the frames described in the previous post – when changes
occur in OmniFocus. In the next post, we’ll take a look at how the provider
manages all the notifications it needs to send across these connections.