apns

package module
v2.0.0+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2015 License: MIT Imports: 13 Imported by: 0

README

go-libapns

APNS library for go

The idea here is to be a simple low level library that will handle establishing a connection and sending push notifications via Apple's apns service with thought towards throughput and performance.

Handles the latest Apple push notification guidelines at https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html

Specifically will implement the binary framed format by batching push notifications. Each batch will be flushed either every 10ms or when a frame is full. A frame is full when the framed format cannot fit anymore data into a tcp packet (65535 bytes). Due to this framing, when finished with the apns connection, one should call Disconnect() to flush any remaining messages out the door.

##Godoc Located here GoDoc

##Installation

> go get github.com/joekarl/go-libapns

##Basic Usage

package main

import (
    apns "github.com/joekarl/go-libapns"
    "io/ioutil"
)

func main() {
  certPem, err := ioutil.ReadFile("../certs/PushTestCert.pem")
  if err != nil {
    panic(err)
  }
  keyPem, err := ioutil.ReadFile("../certs/PushTestKey-noenc.pem")
  if err != nil {
    panic(err)
  }

  apnsConnection, _ := apns.NewAPNSConnection(&APNSConfig{
      CertificateBytes: certPem,
      KeyBytes: keyPem,
  })

  payload := &apns.Payload {
      Token: "2ed202ac08ea9...cf8d55910df290567037dcc4",
      AlertText: "This is a push notification!",
  }

  apnsConnection.SendChannel <- payload
  apnsConnection.Disconnect()
}

Note This example doesn't take into account essential error handling. See below for error handling details

Payload.Badge Need to Know Apple specifies that one should set the badge key to 0 to clear the badge number. This unfortunately has the side effect of causing the go JSON serializer to omit the badge field. Luckily Apple uses negative badge numbers to clear the badge as well. So for our purposes, a badge > 0 will set the badge number, a badge < 0 will clear the badge number, and a badge == 0 will leave the badge number as is.

##Pem Certs You should provide your apns certificate as separated cert/key pem files. Currently go doesn't support password protected pem files (https://github.com/golang/go/issues/6722) so you'll need remove the password from your key pem.

####Separate pem files from p12

openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12

openssl pkcs12 -nocerts -out key.pem -in key.p12

####Remove password from pem file

openssl rsa -in key.pem -out key-noenc.pem

##Error Handling As per Apple's guidelines, when a connection is closed due to error, the id of the message which caused the error will be transmitted back over the connection. In this case, multiple push notifications may have followed the bad message. These push notifications will be supplied on a channel as well as any other unsent messages and will be then available to re-process. Also when writing to the send channel, you should wrap the send with a select and case both the send and connection close channels. This will allow you to correctly handle the async nature of Apple's error handling scheme. See this gist (https://gist.github.com/joekarl/86d9bdb8f9af044710b7) for a full featured example of how to integrate go-libapns with proper shutdown handling and looped connection handling.

##Persistent Connection go-libapns will use a persistant tcp connection (supplied by the user) to connect to Apple's APNS gateway. This allows for the greatest throughput to Apple's servers. On close or error, this connection will be killed and all unsent push notifications will be supplied for re-process. Note Unlike most other APNS libraries, go-libapns will NOT attempt to re-transmit your unsent payloads. Because it is trivial to write this retry logic, go-libapns leaves that to the user to implement as not everyone needs or wants this behavior (i.e. you may want to put the messages that need resent into a queue or store them for later).

##Feedback Service Apple specifies that you should connect to the feedback service gateway regularly to keep track of devices that no longer have your application installed. go-libapns provides a simple interface to the feedback service. Simply create a APNSFeedbackServiceConfig object and then call ConnectToFeedbackService. This will return a list of device tokens that you should keep track of and not send push notifications to again (specifically this will return a List of *FeedbackResponse)

##Push Notification Length Apple places a strict limit on push notification length (currently at 2048 bytes). go-libapns will attempt to fit your push notification into that size limit by first applying all of your supplied custom fields and applying as much of your alert text as possible. This truncation is not without cost as it takes almost twice the time to fix a message that is too long. So if possible, try to find a sweet spot that won't cause truncation to occur. If unable to truncate the message, go-libapns will close it's connection to the APNS gateway (you've been warned). This limit is configurable in the APNSConfig object.

Note: Prior to iOS 8, the limit was 256 bytes. APNS will accept and deliver up to 2048 bytes to devices running iOS 8 as well as those running on older versions of iOS.

##TCP Framing Most APNS libraries rely on the OS Nagling to buffer data into the socket. go-libapns does not rely on Nagling but does do what it can to optimize the number of bytes sent per TCP frame. The two relevant config options that control this behavior are:

  • MaxOutboundTCPFrameSize - (default TCP_FRAME_MAX) Max number of bytes to send per TCP frame
  • FramingTimeout - (default 10ms) Max time between TCP flushes

TCP_NODELAY can be turned on with this setup by setting the FramingTimeout to anything less than 0 (like -1). In practice you want this buffering to occur, so best to leave defaults. If you're concerned about a (max) 10ms delay between your push notifications being sent onto the socket be aware that this is much much much shorter than the default linux Nagle timeout of 1 second.

##What's with using channels for writing to the connection? Basically, this makes it easier to synchronize error handling and socket errors. Not sure if this is the best idea, but definitely works.

##APNSConfig The only required fields are the CertificateBytes and KeyBytes. The other fields all have sane defaults

InFlightPayloadBufferSize       int                     //number of payloads to keep for error purposes, defaults to 10000
FramingTimeout                  int                     //number of milliseconds between frame flushes, defaults to 10ms
MaxPayloadSize                  int                     //max number of bytes allowed in payload, defaults to 2048
CertificateBytes                []byte                  //bytes for cert.pem : required
KeyBytes                        []byte                  //bytes for key.pem : required
GatewayHost                     string                  //apple gateway, defaults to "gateway.push.apple.com"
GatewayPort                     string                  //apple gateway port, defaults to "2195"
MaxOutboundTCPFrameSize         int                     //max number of bytes to frame data to, defaults to TCP_FRAME_MAX
                                                        //generally best to NOT set this and use the default
SocketTimeout                   int                     //number of seconds to wait before bailing on a socket connection, defaults to no timeout
TlsTimeout                      int                     //number of seconds to wait before bailing on a tls handshake, defaults to 5 sec

#License The MIT License (MIT)

Copyright (c) 2014 Karl Kirch

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Documentation

Overview

Package for creating a connection to Apple's APNS gateway and facilitating sending push notifications via that gateway

Package for creating a connection to Apple's APNS gateway and facilitating sending push notifications via that gateway

Index

Constants

View Source
const (
	//Size of feedback header frame
	FEEDBACK_RESPONSE_HEADER_FRAME_SIZE = 6
)
View Source
const (
	//Max number of bytes in a TCP frame
	TCP_FRAME_MAX = 65535
)

Variables

View Source
var APPLE_PUSH_RESPONSES = map[uint8]string{
	0:   "NO_ERRORS",
	1:   "PROCESSING_ERROR",
	2:   "MISSING_DEVICE_TOKEN",
	3:   "MISSING_TOPIC",
	4:   "MISSING_PAYLOAD",
	5:   "INVALID_TOKEN_SIZE",
	6:   "INVALID_TOPIC_SIZE",
	7:   "INVALID_PAYLOAD_SIZE",
	8:   "INVALID_TOKEN",
	10:  "SHUTDOWN",
	128: "INVALID_FRAME_ITEM_ID",
	255: "UNKNOWN",
}

This enumerates the response codes that Apple defines for push notification attempts.

Functions

func ConnectToFeedbackService

func ConnectToFeedbackService(config *APNSFeedbackServiceConfig) (*list.List, error)

Create a new apns feedback service connection with supplied config If invalid config an error will be returned Also if unable to create a connection an error will be returned Will return a list of *FeedbackResponse or error

Types

type APNSConfig

type APNSConfig struct {
	//number of payloads to keep for error purposes, defaults to 10000
	InFlightPayloadBufferSize int
	//number of milliseconds between frame flushes, defaults to 10
	FramingTimeout int
	//max number of bytes allowed in payload, defaults to 2048
	MaxPayloadSize int
	//bytes for cert.pem : required
	CertificateBytes []byte
	//bytes for key.pem : required
	KeyBytes []byte
	//apple gateway, defaults to "gateway.push.apple.com"
	GatewayHost string
	//apple gateway port, defaults to "2195"
	GatewayPort string
	//max number of bytes to frame data to, defaults to TCP_FRAME_MAX
	//generally best to NOT set this and use the default
	MaxOutboundTCPFrameSize int
	//number of seconds to wait for connection before bailing, defaults to no timeout
	SocketTimeout int
	//number of seconds to wait for Tls handshake to complete before bailing, defaults to no timeout
	TlsTimeout int
}

Config for creating an APNS Connection

type APNSConnection

type APNSConnection struct {
	//Channel to send payloads on
	SendChannel chan *Payload
	//Channel that connection close is received on
	CloseChannel chan *ConnectionClose
	// contains filtered or unexported fields
}

APNS Connection state

func NewAPNSConnection

func NewAPNSConnection(config *APNSConfig) (*APNSConnection, error)

Create a new apns connection with supplied config If invalid config an error will be returned See APNSConfig object for defaults

func (*APNSConnection) Disconnect

func (c *APNSConnection) Disconnect()

Disconnect from the Apns Gateway Flushes any currently unsent messages before disconnecting from the socket

type APNSFeedbackServiceConfig

type APNSFeedbackServiceConfig struct {
	//bytes for cert.pem : required
	CertificateBytes []byte
	//bytes for key.pem : required
	KeyBytes []byte
	//apple gateway, defaults to "feedback.push.apple.com"
	GatewayHost string
	//apple gateway port, defaults to "2196"
	GatewayPort string
	//number of seconds to wait for connection before bailing, defaults to 5 seconds
	SocketTimeout int
	//number of seconds to wait for Tls handshake to complete before bailing, defaults to 5 seconds
	TlsTimeout int
}

Config for creating an APNS Feedback Service Connection

type APSAlertBody

type APSAlertBody struct {
	// Text of the alert
	Body string `json:"body,omitempty"`

	// Other alert options
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`

	// New Title fields and localizations. >= iOS 8.2
	Title        string   `json:"title,omitempty"`
	TitleLocKey  string   `json:"title-loc-key,omitempty"`
	TitleLocArgs []string `json:"title-loc-args,omitempty"`
}

type AppleError

type AppleError struct {
	//Internal ID of the message that caused the error
	MessageID uint32
	//Error code returned by Apple (see APPLE_PUSH_RESPONSES)
	ErrorCode uint8
	//String name of error code
	ErrorString string
}

Details from Apple regarding a connection close

type BadgeNumber

type BadgeNumber struct {
	// contains filtered or unexported fields
}

Struct representing the badge number over the app icon on iOS

func NewBadgeNumber

func NewBadgeNumber(number int) BadgeNumber

Get a new badge number, set to the initial number, and included in the payload

func (*BadgeNumber) IsSet

func (b *BadgeNumber) IsSet() bool

Returns whether or not this BadgeNumber is set and should be sent in the APNS payload

func (BadgeNumber) MarshalJSON

func (b BadgeNumber) MarshalJSON() ([]byte, error)

func (*BadgeNumber) Number

func (b *BadgeNumber) Number() int

Returns the set badge number

func (*BadgeNumber) Set

func (b *BadgeNumber) Set(number int) error

Sets the badge number and includes it in the payload to APNS. call .Set(0) to have the badge number cleared from the app icon

func (*BadgeNumber) UnSet

func (b *BadgeNumber) UnSet()

Resets the BadgeNumber to 0 and removes it from the APNS payload

func (*BadgeNumber) UnmarshalJSON

func (b *BadgeNumber) UnmarshalJSON(data []byte) error

type ConnectionClose

type ConnectionClose struct {
	//Any payload objects that weren't sent after a connection close
	UnsentPayloads *list.List
	//The error details returned from Apple
	Error *AppleError
	//The payload object that caused the error
	ErrorPayload *Payload
	//True if error payload wasn't found indicating some unsent payloads were lost
	UnsentPayloadBufferOverflow bool
}

Object returned on a connection close or connection error

type FeedbackResponse

type FeedbackResponse struct {
	//A timestamp indicating when APNs
	//determined that the app no longer exists on the device.
	//This value represents the seconds since 12:00 midnight on January 1, 1970 UTC.
	Timestamp uint32
	//Device push token
	Token string
}

Feedback Response

type Payload

type Payload struct {
	// Basic alert structure
	AlertText        string
	Badge            BadgeNumber
	Sound            string
	ContentAvailable int
	Category         string

	// If this is an enhanced message, use
	// an APSAlertBody instead of .Alert
	AlertBody APSAlertBody

	// Any custom fields to be added to the apns payload
	// These exist outside of the `aps` namespace
	CustomFields map[string]interface{}

	// Payload server fields
	// UNIX time in seconds when the payload is invalid
	ExpirationTime uint32
	// Must be either 5 or 10, if not one of these two values will default to 5
	Priority uint8

	// Device push token, should contain no spaces
	Token string

	// Any extra data to be associated with this payload,
	// Will not be sent to apple but will be held onto for error cases
	ExtraData interface{}
}

Object describing a push notification payload

func (*Payload) Marshal

func (p *Payload) Marshal(maxPayloadSize int) ([]byte, error)

Convert a Payload into a json object and then converted to a byte array If the number of converted bytes is greater than the maxPayloadSize an attempt will be made to truncate the AlertText If this cannot be done, then an error will be returned

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL