apns

package module
v0.0.0-...-ac248c4 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2014 License: Apache-2.0 Imports: 13 Imported by: 1

README

apns-go: Client for Apple Push Notification and Feedback services

GoDoc

Package apns-go provides client for interacting with APNS and corresponding Feedback service.

Part of the Kwik-messenger project.

Example:

package main

import "github.com/Kwik-messenger/apns-go"
import "time"

func main() {
    var cert, key []byte
    // load certificate and its key, perhaps from file

    // create client
    client, err := apns.CreateClient("gateway.push.apple.com", cert, key, false)
    if err != nil {
        panic(err)
    }

    // if you want handle rejected messages, provide a callback
    var badMessageCallback apns.BadMessageCallback = func(m *apns.Message, code uint8) {}
    client.SetCallback(badMessageCallback)

    // if you need feedback client, derive it from APNS client
    // they share access credentials
    feedbackClient, err := client.SetupFeedbackClient("feedback.push.apple.com",
        time.Hour * 24)
    // poll for updates
    go func() {
        for {
            tokens, err := feedbackClient.GetBadTokens()
            // handle tokens there
        }
    }

    // start client. It will create workers who then will throw messages to APNS
    // servers.
    // note: it will take some time, depending on worker count and rtt to APNS,
    // but you can already queue messages to send
    var workerCount = 5
    err = client.Start(workerCount)
    if err != nil {
        panic(err)
    }

    // send messages
    var payload = map[string]interface{}{"aps": map[string]interface{"alert":"hello world!"},
        "data": 42}
    // token to send to
    var hexToken = "0000000000000000000000000000000000000000000000000000000000000000"
    err = client.Send(hexToken, time.Now().Add(time.Hour * 24), apns.PRIORITY_IMMEDIATE, 
        payload)
    // Send will return an error when your message is malformed - payload is too
    // large or not json.Marshall'able etc
    // however Send will succeed if token have been expired. Those messages will
    // be handled when APNS will reject them. If you set up callback, it will be
    // invoked with this message

    // gracefully stop client. It will send all remaining messages from queue
    // and wait for APNS to consume them. Then Stop will return. You must not
    // call Send after calling Stop. Otherwise it will result in panic.
    client.Stop()
}

Implications:

  • APNS have no way to notify that message have been queued to deliver. APNS will only complain about rejected messages. Thus, library just shoves messages into socket and hopes for the best. Every written message also have been enqueued into 'infligh queue'. If after specified timeout after write APNS remains silent then message assumed delivered and have been removed from inflight queue. Otherwise library will pop erroneous message from inflight queue and handle it. Messages further in that queue will be sent again.

  • This best-effort delivery scheme also means that Stop will can take apns.WORKER_ASSUME_SENT_TIMEOUT to finish.

  • Connections to APNS tend to stale and hang up. To work around this issue workers now being respawned after apns.WORKER_IDLE_TIMEOUT. If you still experience silent delivery fails when traffic is low, consider lowering this timeout.

Documentation

Overview

Package apns provides a client for Apple Push Notification Service (APNS) and corresponding feedback service.

Index

Constants

View Source
const (
	// How many times try to connect to APNS (prevents denial on dns failures).
	WORKER_RETRIES = 3
	// Waits specified amount of time for next connection attempt.
	WORKER_RESSURECT_TIMEOUT = 15 * time.Second
	// Message queue length.
	CLIENT_QUEUE_LENGTH = 32
	// Worker respawn queue length.
	RESPAWN_QUEUE_LENGTH = 4
	// Default port for APNS.
	APNS_DEFAULT_PORT = 2195
	// Default port for feedback service.
	FEEDBACK_DEFAULT_PORT = 2196
)
View Source
const (
	// Tell APNS to deliver message without delay.
	PRIORITY_IMMEDIATE = 10
	// Tell APNS to deliver message in background.
	PRIORITY_DELAYED = 5
)
View Source
const (
	FRAME_TOKEN_LENGTH           uint16 = 32
	FRAME_NOTIFICATION_ID_LENGTH uint16 = 4
	FRAME_EXPIRATION_DATE_LENGTH uint16 = 4
	FRAME_PRIORITY_LENGTH        uint16 = 1

	FRAME_TOKEN_ID           uint8 = 1
	FRAME_PAYLOAD_ID         uint8 = 2
	FRAME_NOTIFICATION_ID_ID uint8 = 3
	FRAME_EXPIRATION_DATE_ID uint8 = 4
	FRAME_PRIORITY_ID        uint8 = 5

	FRAME_PROTO_VERSION uint8 = 2
)

There are constants for APNS protocol frames.

View Source
const (
	// Timeout for messages to be 'assumed delivered' to APNS. After message being written into
	// APNS connection, worker will put it into inflight queue. If no errors was recieved from APNS
	// during that amount of time, message assumed delivered to APNS.
	WORKER_ASSUME_SENT_TIMEOUT = time.Second * 5
	// Initial buffer size for inflight messages.
	WORKER_INFLIGHT_QUEUE_SIZE = 128
	// Period to check inflight queue.
	WORKER_QUEUE_UPDATE_FREQ = time.Millisecond * 500

	// Long-living connections to APNS tend to stale and stop working but server does not close
	// them. Thus worker will die after that time of inactivity.
	WORKER_IDLE_TIMEOUT = time.Minute * 5
)
View Source
const (
	// Multiplier for message flight queue growth
	MESSAGE_FLIGHT_QUEUE_ENLARGE = 0.33
)

Variables

View Source
var (
	// Client.Send returns this error when caller specified wrong priority.
	ErrInvalidPriority = errors.New("invalid priority")
	// Client.Start returns this error when worker counter is not applicable.
	ErrInvalidWorkerCount = errors.New("worker count must be greater than zero")
	// Feedback.GetBadTokens return this error when it have been stopped.
	ErrFeedbackClientStopped = errors.New("feedback client have been stopped")
	// Client.Send returns this error when payload misses 'aps' section.
	ErrNoAPS = errors.New("no 'aps' section in payload")
	// Client.Send returns this error when payload is too large.
	ErrPayloadIsTooLarge = errors.New("payload exceed 256 bytes")
	// Client.Send returns this error when token is not well-formed.
	ErrInvalidTokenLength = errors.New("invalid token length")
	// worker returns this error when apple returns an unknown error code (should not happen).
	ErrInvalidAppleResponse = errors.New("invalid apple response")
	// worker returns this error when idle timeout is reached.
	ErrIdlingTimeout = errors.New("idle timeout")
)
View Source
var APNSErrors = 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",
	255: "Unknown",
}

APNSErrors contains descriptions for error codes.

Functions

This section is empty.

Types

type BadMessageCallback

type BadMessageCallback func(m *Message, code uint8)

BadMessageCallback defines function type to call back on messages APNS fails to deliver.

type BadToken

type BadToken struct {
	// Hex-encoded device token
	Token string
	// Timestamp of token expiration date
	Timestamp time.Time
}

BadToken represents entry recieved from feedback service. It contains token and expiration date.

type Client

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

Client is an APNS client. It provides API to send messages, manages message queue, orchestrates workers. It is also used to instantiate feedback service client.

func CreateClient

func CreateClient(gw string, cert, certKey []byte, anyServerCert bool) (*Client, error)

CreateClient creates Client, loads tls certificate. If parameter 'anyServerCert' is set to true, client will be able to connect to APNS gateway with invalid certificate. Can be used for testing purposes.

After client has been created it should be started using Start() to actually send messages. But even if not having been started it still can be used - all messages will be queued and then sent. It can be handy because workers on startup can take some time to connect to APNS.

func (*Client) Send

func (c *Client) Send(to string, expiry time.Time, priority uint8,
	payload map[string]interface{}) error

Send enqueues message to delivery. 'to' is a hex-encoded device token, 'expiry' - expiration time of message, 'priority' - either PRIORITY_IMMEDIATE for immediate notification delivery or PRIORITY_DELAYED for background delivery (it is an APNS property. It does not affect client's schedule for message), 'payload' - message payload.

Send will make a message and put it into delivery queue. Thus, Send will return an error only in case of malformed input - invalid token, expiry, priority or payload. Send() to invalid but well-formed token will succeed but later, when actual delivery fails, BadMessageCallback will be invoked.

Client's delivery queue has limited capacity and Send() will block if queue is full. This can happen when workers are overloaded or not yet started.

Send can accept messages before client has been started. Messages will be queued and sent after client startup.

Send does not guarantee ordering of messages sent. Also keep in mind that APNS itself does not guarantee message delivery. However, every sent message either will be 'assumed delivered' to APNS or callback will be called.

func (*Client) SetCallback

func (c *Client) SetCallback(cb BadMessageCallback)

SetCallback sets callback for handling erroneous messages. Every call executed in its own goroutine.

func (*Client) SetupFeedback

func (c *Client) SetupFeedback(gw string, poll time.Duration) (*FeedbackClient, error)

SetupFeedback setups feedback client from APNS client config. Feedback client will inherit a certificate and 'anyServerCert' option. After client is created it needs to be started to actually talk to feedback service.

func (*Client) Start

func (c *Client) Start(workerCount int) error

Start starts a client, returns error if connection to APNS does not succeed.

func (*Client) Stop

func (c *Client) Stop() error

Stop stops client gracefully. Users should not send messages after invoking Stop; those calls will result in panic. Pending messages will be sent to APNS as usual (callbacks will be invoked for malformed messages). Stop call will return when all pending messages assumed delivered or appropriate callbacks are invoked.

type FeedbackClient

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

FeedbackClient is a client for APNS Feedback service. It provides API to retrieve device tokens that are no longer valid.

func (*FeedbackClient) GetBadTokens

func (fc *FeedbackClient) GetBadTokens() ([]BadToken, error)

GetBadTokens returns bad tokens retrieved from feedback service. It will block until feedback service sends some tokens or an error occurs.

func (*FeedbackClient) Start

func (fc *FeedbackClient) Start() error

Start starts a client. Will return error if cant resolve feedback service name.

func (*FeedbackClient) Stop

func (fc *FeedbackClient) Stop()

Stop stops the client.

type Message

type Message struct {
	Token     []byte
	MessageID uint32
	Expiry    int32
	Priority  uint8
	Payload   []byte
	// contains filtered or unexported fields
}

Message represents a ready-to-go-to-wire message.

func (*Message) WriteTo

func (m *Message) WriteTo(to io.Writer) (n int64, err error)

WriteTo implements io.WriterTo.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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