且听疯吟如此生活三十年
ejabberd: Apple Push Notification Service

主要是通过 hook ejabberd 的离线消息处理,从而实现针对离线消息进行推送

1. Hook offline message

  • 记录 device token
    这一步没啥好说的,iOS 连接到 APNs 后获取到 device token,发给服务器,服务器负责维护好对应关系即可。
    注意客户端退出时清理掉相应记录。

  • 在 ejabberd 中新增一个模块,注册 offline_message_hook,大致如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    start(Host, Opts) ->
    ?INFO_MSG("Starting mod_push_service", []),
    register(?MODULE, spawn(?MODULE, init, [Host, Opts])),
    ok.

    init(Host, _Opts) ->
    ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, send_notification, 10),
    ok.

    stop(Host) ->
    ?INFO_MSG("Stopping mod_push_service", []),
    ejabberd_hooks:delete(offline_message_hook, Host,
    ?MODULE, send_notification, 10),
    ok.

    send_notification(From, To, Packet) ->
    dosomething.

2. Implement APNs

  • 从 Apple 获取推送证书

  • 在服务端使用该证书建立 ssl 连接

  • 按照 Apple 文档生成指定格式的 Payload 并发送

  • Apple 同时提供了 feedback channel 来获取失效 token 便于服务端获取记录,实现方式和发送消息类似,区别在于一个是发送消息,一个是接收消息

  • 连接的粗略代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    -module(apntest).
    -export([connect/0]).

    connect() ->
    application:start(asn1),
    application:start(crypto),
    application:start(public_key),
    application:start(ssl),
    Address = "gateway.sandbox.push.apple.com",
    Port = 2195,
    Cert = "cert.pem",
    Options = [{certfile, Cert}, {mode, binary}],
    Timeout = 10000,
    case ssl:connect(Address, Port, Options, Timeout) of
    {ok, Socket} ->
    PayloadString = "{\"aps\":{\"alert\":\"Hello world.\",\"sound\":\"chime\", \"badge\":1}}",
    Payload = list_to_binary(PayloadString),
    PayloadLength = size(Payload),
    Packet = <<0:8,
    32:16/big, 16#ff496f96352abb7c875bedfc755287XXXXXXe14bfadade9ff6ba75360de65441:256/big,
    PayloadLength:16/big,
    Payload/binary>>,
    ssl:send(Socket, Packet),
    ssl:close(Socket),
    PayloadString;
    {error, Reason} ->
    Reason
    end.
  • 有一些开源的 Erlang APNs 推送服务实现,比如 apns4erl,不过这个项目实在引用了太多不必要的东西了 XD,还是推荐自己看懂后根据需求重新实现,代码也不复杂