Sending and receiving stanzas using EXMPP
A couple of posts ago, I introduced EXMPP, the Erlang library developed by Process One for XMPP.
In that post we used EXMPP to connect an external component to eJabberd server by using the XEP-0114.
This time I will show you how to process streams directed to our component, analyze them and reply to the sender.
First of all I used a gen_server behaviour and put all the code regarding the handshake inside the init([]) method:
init([]) -> exmpp:start(), Session = exmpp_component:start_link(), exmpp_component:auth(Session, ?COMPONENT, ?SECRET), _StreamId = exmpp_component:connect(Session, ?SERVER_HOST, ?SERVER_PORT), ok = exmpp_component:handshake(Session), {ok, #state{session = Session}}
You may notice that I used a set of Erlang macros to identify the variables needed for the connection,
those macros were previously defined in the code as:
-define(COMPONENT, get_conf_value(component)). -define(SECRET, get_conf_value(secret)). -define(SERVER_HOST, get_conf_value(server_host)). -define(SERVER_PORT, get_conf_value(server_port)).
Where get_conf_value(Value) is simply a function I implemented to parse a configuration file.
EXMPP provides to developers a set of records (a record may be thought as a complex data structure and I will write about
them in the future); among these records we need in our code the one representing a received_packet.
It is defined as following:
-record(received_packet, { packet_type, % message, iq, presence type_attr, % depend on packet. Example: set, get, subscribe, etc from, % JID id, % Packet ID queryns, % IQ only: Namespace of the query raw_packet % raw exmpp record }).
After the handshake we can send any kind of stanzas defined in XMPP realm towards the server we are connected to as long as
we have a reference to the session we established before.
A really cool feature of EXMPP is that two different processes spawend by our gen_server can send a stanza at the same time over the same session and the stanzas will not be mixed during their “trip” to the XMPP server.
About incoming stanzas, you must know that all of them will be processed by the gen_server as normal Erlang messages.
I told you before that all the stanzas can be handled by processes spawned by the gen_server, so it seems sensible to spawn
a new process any time a packet is received; this new process will elaborate the stanza and if required will send a reply stanza, then it
terminates.
To do that we add the gen_server something like this:
handle_info(#received_packet{} = Packet, #state{session = Session} = State) -> spawn(fun() -> process_received_packet(Session, Packet) end), {noreply, State};
In practice whenever we receive a Packet we spawn (without linking!) a new process which takes as input the current session and the packet itself.
This process is represented by the following function:
process_received_packet(Session, Packet) -> Packet_Type = Packet#received_packet.packet_type, case Packet_Type of iq -> process_received_iq(Session, Packet); message -> process_received_message(Session, Packet); presence -> process_received_presence(Session, Packet); end.
The previous function is pretty simple: basically it checks the type of the packet and then based on it a different function is called.
Let’s implement process_received_message(Session, Packet) so that whenever a message stanza is received our component sends back the same message:
process_received_message(Session, #received_packet{packet_type=message, raw_packet=Packet}) -> %% retrieve recipient jid From = exmpp_xml:get_attribute(Packet, from, <<"unknown">>), %% retrieve sender jid To = exmpp_xml:get_attribute(Packet, to, <<"unknown">>), %% set recipient jid Pkt1 = exmpp_xml:set_attribute(Packet, from, To), %% set sender jid Pkt2 = exmpp_xml:set_attribute(Pkt1, to, From), %% remove old id NewPacket = exmpp_xml:remove_attribute(Pkt2, id), %% send new packet exmpp_session:send_packet(Session, NewPacket).
Here is the result (captured by using PSI):
Let’s implement process_received_iq(Session, Packet) that sends back a simple message if an iq stanza is received:
process_received_iq(Session, #received_packet{packet_type=iq, type_attr=Type, raw_packet=IQ}) -> %% get namespace of IQ stanza NS = exmpp_xml:get_ns_as_atom(exmpp_iq:get_payload(IQ)), %% set text value of IQ stanza Reply = exmpp_xml:element(NS, 'response', [], [{xmlcdata,<<"your iq has been received">>}]); %% build result packet Result = exmpp_iq:result(IQ, exmpp_xml:element(NS, 'query', [], [Reply])); %% sends new packet exmpp_component:send_packet(Session, Result).
Ok! For now it is enough…try to read EXMPP specifications and see how you can change slightly this code!
Hello,
How can i retrive body from message packet?
I receive message, then i try:
Body = exmpp_xml:get_attribute(Packet, body, <>),
But i get unknown. How can i get receiving message?
Thank you.
hi,
why don’t you use exmpp_message:get_body/1? here is the link http://www.process-one.net/docs/exmpp/devdoc/trunk/exmpp_message.html
Hi,
Thanks for your nice introduction to EXMPP. I am newbie in Exmpp. I would like to use EXMPP and Ejabberd to send and receive message in s2s, c2s and s2c. What reference do thing is good to start with.
Thanks in advance,
Regards,
RK
hi,
thanks for your comment. I haven’t used exmpp very much lately, anyhow i hope i will write again on it soon
paolo