Retrieving sipsorcery notifications

I had a question on the forums about creating a PC based widget to display the callerID on an incoming call by maintaining a persistent connection to the sipsorcery telnet monitoring server. One problem with the design is that sipsorcery no longer uses telnet the end of telnet for sipsorcery, so the design would need to change to use SSH instead. The question mentioned using Adobe’s AIR to build the application and while I don’t know a lot about AIR it’s likely that a telnet client would be easy to implement, since it’s not much more than a TCP socket, but SSH would be a lot harder as there’s a lot of extra work to negotiate and set up the encrypted channel.

As it happens at the same time the SSH change was made I changed the way the Siverlight client retrieves the notifications messages from the server from using a telnet connection to a HTTPS one. Part of the motivation was exactly the same as the problem in the previous paragraph, it was going to be tricky to implement an SSH client in Silverlight. Another reason was that the Silverlight client can only establish TCP connections to destination ports 4302 to 4332 and I had begun to find that a bit frustrating, I couldn’t use the console from a previous workplace, some internet cafes etc.

The HTTPS service that the Silverlight client uses to get notifications is . The “pull” at the end of the service URL is pertinent and the notifications mechanism requires that clients poll the sipsorcery web server to pull notifications down. Such a pull mechanism is not ideal as it means unneccessary traffic and load but after spending the best part of two weeks fighting with Microsoft’s PollingDuplexHttpBinding only to conclude that it’s completely broken with IIS6 and switch back to a traditional pull mechansim. Maybe at some point I’ll revisit it, the sipsorcery web site has since moved to Windows Azure which uses IIS7 and on another front HTML5 has introduced Web Sockets which achieve the same as the PollingDuplexHttpBinding.

The ability to pull notifications from the sipsorcery web server is something that can be used right now to build an application like the callerID widget. The Silverlight client consumes the service using a WCF SOAP endpoint. Connecting to WCF SOAP endpoints is ok if you’re going to be writing a client in .Net (C#, VB.Net etc) but it can get tricky from non .Net languages especially when the service involves authorisations which the sipsorcery one does. One great thing about WCF is that with very little effort a service endpoint can be added to support an alternative interface. In this case REST and JSON (I’m not deliberately trying to set a record for acronyms) are a lot more universally understood compared to SOAP and .Net’s XML serialisation. I’ve spent a few hours adding the REST and JSON interface to the notifications service and have deployed it to the sipsorcery Windows Azure web site, the URL is https://www.sipsorcery.com/notificationspull.svc/rest/. The service interface is:

[ServiceContract(Namespace = "http://www.sipsorcery.com/notifications/pull")]
    public interface INotifications
    {
        [OperationContract]
        [WebGet(UriTemplate = "isalive", ResponseFormat = WebMessageFormat.Json)]
        bool IsAlive();

        [OperationContract]
        [WebGet(UriTemplate = "login?username={username}&password={password}", ResponseFormat = WebMessageFormat.Json)]
        string Login(string username, string password);

        [OperationContract]
        [WebGet(UriTemplate = "logout")]
        void Logout();

        [OperationContract]
        [WebGet(UriTemplate = "getpollperiod", ResponseFormat = WebMessageFormat.Json)]
        int GetPollPeriod();

        [OperationContract]
        [WebGet(UriTemplate = "subscribeforaddress?subject={subject}&filter={filter}&addressid={addressid}", ResponseFormat = WebMessageFormat.Json)]
        string SubscribeForAddress(string subject, string filter, string addressID);
        
        [OperationContract]
        [WebGet(UriTemplate = "getnotificationsforaddress?addressid={addressid}", ResponseFormat = WebMessageFormat.Json)]
        Dictionary<string, list=""><string>> GetNotificationsForAddress(string addressID);

        [OperationContract]
        [WebGet(UriTemplate = "closeconnectionforaddress?addressid={addressid}")]
        void CloseConnectionForAddress(string addressID);
    }

I’ve put together a Ruby sample that hooks up to the service and pulls down the notifications. I’ve put the sample in github so that it can be updated if needs be.

require 'httpclient'
require 'json'
require 'UUIDTools'
 
puts "sipsorcery get notifications sample"
 
notificationsURL = "https://www.sipsorcery.com/notificationspull.svc/rest/"
myUsername = "username"
myPassword = "password"
addressID = UUIDTools::UUID.random_create
filter = "event%2053"
 
client = HTTPClient.new
resp = client.get_content("#{notificationsURL}login?username=#{myUsername}&password=#{myPassword}")
authID = resp.delete('"')
puts "authID=#{authID}"
 
resp = client.get_content("#{notificationsURL}subscribeforaddress?subject=console&filter=#{filter}&addressid=#{addressID}", nil, "authID" => authID)
puts "Notifications subscribe response=#{resp}"
 
30.times do
  resp = client.get_content("#{notificationsURL}getnotificationsforaddress?addressid=#{addressID}", nil, "authID" => authID)
  if !resp.empty?
    notifications = JSON.parse(resp.to_s)
    notifications[0]["Value"].each do |notification|
      puts notification.chomp
    end
  end
  sleep(1)
end
 
# Close the notifications connection.
client.get_content("#{notificationsURL}closeconnectionforaddress?addressid=#{addressID}", nil, "authID" => authID)
 
puts "finished"

I will explain each of the service methods in my next post.

  1. trav’s avatar

    i wanted a preview so i tried the ruby sample out in netbeans and for line 18:
    resp = client.get_content(“#{notificationsURL}subscribeforaddress?subject=controlclient&filter=#{filter}&addressid=#{addressID}”, nil, “authID” => authID)

    i get an error of unexpected ‘=’ (SyntaxError). guess i’ll wait for the next post 😉

    Reply

    1. sipsorcery’s avatar

      The problem was my Ruby editor copies => as =>. I’ve fixed up the sample manually and it’s now free of syntax errors.

      Reply

    2. trav’s avatar

      thanks! works as advertised-now i don’t have to open a separate browser window for console events. btw, what ruby IDE are you using? i’m using netbeans, but not sure if there’s something easier to use.

      Reply

      1. sipsorcery’s avatar

        I tried half a dozen or so different Ruby IDE’s including NetBeans and also a plugin to VS.Net which is my main C# IDE. I didn’t really like any of them and have since come across RubyMine from JetBrains which is the best one I have used.

        Reply

      2. Simon Booth’s avatar

        You could also implement a screenpop widget using sip – just base it on the sip user agent Aaron includes in the standalone version of the project – as you dont need the media stack (good job as its not there yet). Your screenpop could also then have basic functionality like rejecting the call or diverting.

        Reply

      3. Seth Wisely’s avatar

        *cough* *cough* jabber *cough* *cough*

        Reply

      4. James’s avatar

        Hi. I’ve done some basic web development before and I saw this code for the notifications service and thought I’d investigate a little. It gave me the idea to write a script that will pause a torrent client whenever there is an active call, since QoS on my router has failed me.

        First, is it even a plausible goal? Second, when I try the code directly in Firefox (by sending a custom header with my authID) I get a response “400 Bad Request” with an exception of “Requested value ‘controlclient’ was not found.” I constructed my URIs exactly as in the sample code. Am I overlooking something?

        Reply

        1. sipsorcery’s avatar

          There was a problem with the sample. I’d changed the back end to use subject=console and forgotten to update it, it’s fixed now.

          Your goal seems plausible enough to me. Try using:

          subject=machine&filter=dialog%20sip:*@sipsorcery.com

          That should result in you only getting notification messages when a call is established (event ID 7) and terminated (event ID 8).

          Reply

        2. Seth Wisely’s avatar

          How does this play out with SIMPLE?

          Reply

          1. sipsorcery’s avatar

            Nothing to do with it.

            Reply

Reply

Your email address will not be published. Required fields are marked *