I am trying to add Apple's voip push notification into our app. Our backend provider is written by Erlang's Ejabberd server with apns4erl server 1.0.4.
Currently, the apns4erl 2 has the capability to send voip push notification. But it require OTP 19+ to compile and our system is running on OTP 17.3.
So may I know is that possible to run these two OTP at the same time? I can't upgrade OTP to 19+. And new library require 19+.
Are there good way to make this requirement possible or I need to porting the new library into our old one?
Thanks,
Eric
Keep in mind while reading this that you should really find a way to update your existing service to keep up to date with newer runtimes. I've dealt with being stuck on a legacy runtime only because someone thought they needed to fork a particular module somewhere in a way that made it impossible to upgrade -- and that was just a nightmare.
Yes, I just confirmed that you can connect an R17 and an R20 node via disterl and send messages:
R17 node:
ceverett@changa:/opt/erlang/R17.5/bin$ ./erl -name bar -cookie walnut
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.4 (abort with ^G)
(bar@changa.shinden.tsuriai.jp)1> P = spawn(fun Wait() -> receive {From, Message} -> From ! {received, Message}, Wait() end end).
<0.44.0>
(bar@changa.shinden.tsuriai.jp)2> global:register_name(waiter, P).
yes
R20 node:
ceverett@changa:~$ erl -name foo -cookie walnut
Erlang/OTP 20 [RELEASE CANDIDATE 2] [erts-9.0] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.0 (abort with ^G)
(foo@changa.shinden.tsuriai.jp)1> net_kernel:connect('bar@changa.shinden.tsuriai.jp').
true
(foo@changa.shinden.tsuriai.jp)2> global:send(waiter, {self(), "blah blah blah"}).
<7489.44.0>
(foo@changa.shinden.tsuriai.jp)3> flush().
Shell got {received,"blah blah blah"}
ok
Note that above the R20 node was started first, so that was the version of EPMD that was running. I have no idea whether that would matter, nor do I know if EPMD has changed between R17 and R20.
This is all undocumented functionality. Read below for a much more future-proof way to do this.
The documented way to connect two nodes of different versions is with the +R
runtime flag. I regard this as a wildly unreliable hack (precisely as unreliable as what I demonstrated above) unless you've tested it thoroughly first -- and it may have unintended side effects depending on the versions involved (and no telling what is coming in the future). But this is an actual runtime flag and it obviously exists for a reason. See legoscia's answer for more detail about this.
Whether or not two versions of Erlang's runtime are compatible over disterl, writing network applications in Erlang is really easy. You can always connect two of any different things over TCP.
The simple solution to this would be to write a network application in Erlang using the current version of Erlang (R20.1 at the moment) that receives Apple voip pushes, and forwards them to your main application.
Write:
Treat the Apple VOIP service within your system as if it exists as a native part of your application. The socket handler in the R17 node is the VOIP service. Make sure you write its interface functions with that in mind -- later if you can migrate your code to R20 then you won't have to worry with this detail because it will be already abstracted by the internal protocol in Erlang.
As for the push updates themselves, you can create whatever sort of protocol you want.
Erlang's external term format has not changed between R17 and R20, so you will be able to send native messages between the two nodes by having the Apple VOIP side socket handler (on the R20 node) do something like:
notify_node(Socket, VOIP_Data) ->
Message = term_to_binary({push, VOIP_Data}),
ok = gen_tcp:send(Socket, Message),
log(info, "Message sent").
And on the receiving node (the R17 node):
loop(Parent, Debug, State = #s{socket = Socket}) ->
receive
{tcp, Socket, Bin} ->
{push, VOIP_Data} = binary_to_term(Bin, [safe]),
{ok, NewState} = do_stuff(VOIP_Data, State)
loop(Parent, Debug, NewState);
%% Your other stuff
%% OTP system stuff
end.
You could write the R17 side as a gen_server also, listening for:
handle_info({tcp, Socket, Bin}, State = #s{socket = Socket}) ->
%% whatever
I just happen to most often see socket handling processes as proc_lib
processes instead of gen_servers most of the time. But it doesn't matter in most cases.
Another approach is to use binaries:
notify_node(Socket, VOIP_Data) ->
Message = <<"PUSH ", VOIP_Data>>,
ok = gen_tcp:send(Socket, Message),
log(info, "Message sent").
And on the receiving node (the R17 node):
loop(Parent, Debug, State = #s{socket = Socket}) ->
receive
{tcp, Socket, <<"PUSH ", VOIP_Data/binary>>} ->
{ok, NewState} = do_stuff(VOIP_Data, State)
loop(Parent, Debug, NewState);
%% Your other stuff
%% OTP system stuff
end.
It really depends on the nature of VOIP_Data
. If it is a binary itself and the R20 Apple push service should just pass it along without inspecting it, then the raw binary method is easy. If the R20 side is going to be interpreting the message and converting it to an Erlang message of its own then you'll do much better with the binary_to_term/1
/term_to_binary/2
form.