JabberWerxC Library Getting Started Guide |
This tutorial will step you through the process of creating a basic XMPP client using JabberWerxC. For simplicity most error handling is omitted.
Some basic requirements before proceeding are:
Examples in this document use the following header files:
#include <jabberwerx/jabberwerx.h> #include <jabberwerx/client.h> #include <jabberwerx/crypto/tls.h> #include <jabberwerx/util/log.h> #include <event2/event.h>
In order to build and run your project, the JWC library and its dependencies need to be in the appropriate paths. To aid in deployment, the JWC package includes all necessary dependencies. If JWC is installed in /usr/local/jabberwerxc
(JABBERWERXC_HOME), you will usually have the following for Linux:
LD_LIBRARY_PATH=$JABBERWERXC_HOME/lib:$LD_LIBRARY_PATHand for Mac OS X:
DYLD_LIBRARY_PATH=$JABBERWERXC_HOME/lib:$DYLD_LIBRARY_PATH
Also, the the following compiler and linker flags are necessary:
CFLAGS=-I${JABBERWERXC_HOME}/include LDFLAGS=-L${JABBERWERXC_HOME}/lib -levent_core -levent_extra -ljabberwerx
JabberWerxC uses libevent for underlying connectivity and eventing. libevent uses a struct event_base*
to drive this process. Application writers must create the base and let libevent continually dispatch until terminated by jw_client when the connection is closed.
Add this code to your main before using the jw_client
:
struct event_base *base = event_base_new();
JabberWerxC uses a jw_client
to connect and authenticate with an XMPP server.
jw_htable *config; jw_workq *workq; jw_client *client; jw_err err; jw_htable_create(0, jw_strcase_hashcode, jw_strcase_compare, &config, NULL); jw_htable_put(config, JW_STREAM_CONFIG_SELECTOR, base, NULL, NULL); jw_workq_create(config, &workq); jw_client_create(workq, &client, &err);
A connection attempt is made using the jw_client_connect
function. Connection configuration is passed to the function using a jw_htable. The same config htable can be used for all related objects, such as the previously-created workq. For simplicity in the code below, the origins of the strings stored in the hashtable are not explained here.
// user jid jw_htable_put(config, JW_CLIENT_CONFIG_USERJID, jid_str, NULL, NULL); // password jw_htable_put(config, JW_CLIENT_CONFIG_USERPW, password_str, NULL, NULL); // attempt to connect if (!jw_client_connect(client, config, &err)) { jw_err_log(JW_LOG_ERROR, &err, "could not connect"); }
Since jw_client
is completely asynchronous, nothing beyond validation has been done. Libevent needs to be told to continue to process network events, usually by calling event_base_dispatch
. Add the following code after jw_client_connect
:
// this will return once the client disconnects event_base_dispatch(base); // release resources jw_workq_destroy(workq); event_base_free(base);
TLS and BOSH over https require an acceptance callback configuration option. This callback gives users a chance to allow a connection that would fail because of an invalid certificate.
static void clientTLSAcceptCertCB(jw_tls_session *sess) { // always proceed with a connection by calling jw_tls_proceed jw_tls_proceed(sess, true); }And in main, as part of the configuration but before the connection attempt, add the following:
jw_tls_accept_cb_htable_value *acbv; acbv = jw_data_malloc(sizeof(jw_tls_accept_cb_htable_value)); acbv->cb = clientTLSAcceptCertCB; jw_htable_put(config, JW_TLS_CONFIG_ACCEPT_CB, acbv, jw_htable_free_data_cleaner, NULL);
jw_client_connect
connects asynchronously. jw_client
defines connection events where the user should perform post connection work. This is true of the entire library: most operations are asynchronous and users should always implement consequent actions in event handlers.
static void clientConnectedCallback(jw_event_data event, void *arg) { jw_log(JW_LOG_INFO, "client connected"); } static void clientDisconnectedCallback(jw_event_data event, void *arg) { jw_log(JW_LOG_INFO, "client disconnected"); jw_dom_node *errDom = event->data; if (errDom) { // examine errDom for error information } }In order to connect the previous functions to their events, make the following calls in your main before calling
jw_client_connect
:
jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_CONNECTED), clientConnectedCallback, NULL); jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_DISCONNECTED), clientDisconnectedCallback, NULL);
clientConnectedCallback
is triggered when the connection completes successfully and clientDisconnectedCallback
when the client disconnects. Note that the JW_CLIENT_EVENT_DISCONNECTED event is fired when a connection attempt ends in error, but only if the call to jw_client_connect
originally returned true
.
static void clientPresenceReceivedCB(jw_event_data event, void *arg) { jw_log(JW_LOG_INFO, "presence received"); jw_dom_node *node = event->data; if (node) { // examine node for presence information } } static void clientMessageReceivedCB(jw_event_data event, void *arg) { jw_log(JW_LOG_INFO, "message received"); jw_dom_node *node = event->data; if (node) { // examine node for message information } }And once again, these events must be bound before the connection attempt:
jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_PRESENCE_RECEIVED), clientPresenceReceivedCB, NULL); jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_MESSAGE_RECEIVED), clientMessageReceivedCB, NULL);
jw_client
exposes a function to send an XMPP stanza.
JABBERWERX_API bool jw_client_send_stanza(jw_client *client, jw_dom_node *stanza, jw_err *err);Here is a JW_CLIENT_EVENT_MESSAGE_RECEIVED handler that echoes chat messages back to the sender.
static void clientMessageReceivedCB(jw_event_data event, void *arg) { jw_dom_node *node = event->data; jw_client *client = arg; const char *type = jw_dom_get_attribute(node, "type"); if (0 == jw_strcmp(type, "chat")) { jw_dom_ctx *dom_ctx; jw_dom_node *dup; jw_dom_context_create(&dom_ctx, NULL); jw_dom_import(dom_ctx, node, true, &dup, NULL); const char *to = jw_dom_get_attribute(dup, "{}to"); const char *from = jw_dom_get_attribute(dup, "{}from"); jw_dom_set_attribute(dup, "{}from", to, NULL); jw_dom_set_attribute(dup, "{}to", from, NULL); jw_client_send_stanza(client, dup, NULL); } }
To clean up, you can call jw_client_destroy
in your JW_CLIENT_EVENT_DISCONNECTED event (or any time after the disconnected event is fired):
static void clientDisconnectedCallback(jw_event_data event, void *arg) { jw_log(JW_LOG_INFO, "client disconnected"); jw_dom_node *errDom = event->data; if (errDom) { // examine errDom for error information } jw_client_destroy(event->source); }
Cleaning up the configuration and other client-related data is done asynchronously in (or after) a JW_CLIENT_EVENT_DESTROYED event:
static void clientDestroyedCallback(jw_event_data event, void *arg) { // be sure to register config as the arg when binding the event jw_htable *config = arg; jw_htable_destroy(config); // additional cleanup }
The only structures that cannot be destroyed in this callback are the ones that may be in use at the time the callback is running, such as the libevent event_base* and the workq. Instead, these should be destroyed after event_base_dispatch
returns.
jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_DESTROYED), clientDestroyedCallback, config);
To cleanup the values added to the configuration table, specify a cleaner function when calling jw_htable_put
. If the key is static and the value is created with one of the JabberWerxC memory allocation functions (e.g. jw_data_malloc, jw_strdup, etc), the convenience function jw_htable_free_data_cleaner
can be used. Assuming jid_str and password_str are allocated using jw_strdup
, replace the configuration setup for USERJID and USERPW calls with the following:
// user jid jw_htable_put(config, JW_CLIENT_CONFIG_USERJID, jid_str, jw_htable_free_data_cleaner, NULL); // password jw_htable_put(config, JW_CLIENT_CONFIG_USERPW, password_str, jw_htable_free_data_cleaner, NULL);