Friday 15 April 2011

Clickatell API : 'Internal' Web Listener as a service

Shooooo.....I finally kicked up the courage to setup a blog.......Now I can eventually type out all the ramblings going on in my head (oi....you know you have them too  : ) )....

Okay....enough about me...lets get into some code....yeeeaaaahhhh!!.....here goes!!...hold on to your keyboards guys.... :)

well...first....let me paint a little scenario

I got handed a project the other day of integrating SMS functionality within our current systems.... immediately Clickatell came to mind as I used it at a previous company I worked for...and I just loved the ease of use of the API....simple http calls......

The problem I had was that...at my previous company...we never really cared about the status or intermediate statuses after the SMS was sent....whether it was delivered or not...we didn't really care much......anyways....NOT THE CASE at my current company...they not only wanted the final status...but the intermediate statuses as well.......

Clickatell has a callback functionality, which send SMS status updates to a specified URL of your choice.....most or all users of the Clickatell API allows the SMS status updates to be directed to their websites. My current company's website is not database driven....so doing things that way was totally out of the question. Okay...cool.....now what?.....Clickatell forums......went there hoping to get an answer from someone that has hopefully implemented an internal listener.....nope....nothing there......okay...next point of call....good ol' google....nope....nothing there too.....Then I eventually thought.....I should just just build  my own web listener that immitates a "website \ webserver"..........so if there are any lost 'clickatell' or 'non-clickatell' sheep out there and would like a web listener to comfort them....you have come to the right blog....

In order to make this work...you need to register you company's public IP address (generally your firewall) on your clickatell account as your "callback URL"...put a rule on your firewall.....to re-route the incoming Clickatell IP address (which they give you) to the service we are going to build......


okay.....code time....

firstly....create a windows service....

////Web Listener in C#
 //Added usings using System.Net
using System.Threading;
using System.IO;
using System.ComponentModel;
using System.Net;


public partial class WebListenerService : ServiceBase
    {
       
        private BackgroundWorker _backgroundworker;
        private static HttpListener _listener;
        //In the start method
        protected override void OnStart(string[] args)
        {
            if(_listener == null){
                _listener = new HttpListener();
                //Specify Incoming IP to listen for and a port or just an * to listen to all incoming IP address
                //I would specify an IP..the code below is purely illustration
                _listener.Prefixes.Add("*:1589/");
            }
           
            using (_backgroundworker = new BackgroundWorker())
            {
                _backgroundworker.DoWork += new DoWorkEventHandler(backgroundworker_DoWork);
                _backgroundworker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundworker_RunWorkerCompleted);
                //Set this to indicate whether the thread is allowed to be cancelled\aborted
                _backgroundworker.WorkerSupportsCancellation = true;
                _backgroundworker.RunWorkerAsync();
            }
        }

        void backgroundworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
        ////Don't really need this method but it does exist within the background worker
        }

        void backgroundworker_DoWork(object sender, DoWorkEventArgs e)
        {
            _listener.Start();

            while (true) //Allows you to continue listening
            {
                //The GetContext() will return once a request from Clickatell comes in
                HttpListenerContext request = _listener.GetContext();

                ProcessRequest(request);
            }
        }
        void ProcessRequest(object listenerContext)
        {
            //Get the values from the incoming request and then send a response to Clickatell

            var context = (HttpListenerContext)listenerContext;

            int clientReferenceID = Convert.ToInt32(context.Request.QueryString["cliMsgId"].ToString().Trim());
            string ClickatellReferenceID = context.Request.QueryString["apiMsgId"].ToString().Trim();
            string smsStatusCode = context.Request.QueryString["status"].ToString().Trim();

            ///Save to DB or xml....or do whatever you like to the data
            
            //Create to use within the response message stream
            byte[] msg;
            context.Response.StatusCode = (int)HttpStatusCode.OK;
            msg = Encoding.UTF8.GetBytes("Successful");

            // if for anything goes wrong or encounter and error when using the data or saving the data
            // in order not to lose it...send back and uncessfull response - Clickatell will resend it.
            // context.Response.StatusCode = (int)HttpStatusCode.NotAcceptable;
            // msg = Encoding.UTF8.GetBytes("Status could not be saved");

            SendResponse(msg, context);
        }

        private void SendResponse(byte[] msg, HttpListenerContext context)
        {
            try
            {
                context.Response.ContentLength64 = msg.Length;
                using (Stream s = context.Response.OutputStream)
                s.Write(msg, 0, msg.Length);
            }
            catch (Exception)
            {
            }
        }
        
        protected override void OnStop()
        {
            if (_listener != null)
            {
                _listener.Stop();
                _listener = null;
            }

            if (_backgroundworker != null)
            {
                _backgroundworker.CancelAsync();
                _backgroundworker.Dispose();
            }
        }
    }



and that is it....you now have a web listener as a managed window service.....I really hope this helps someone out there......!!...


Ciao.....for now....over and out.....

NB: Please test all code…as this is written from memory….