Sony Manuel's blog

Monday Feb 09, 2009

Using B2BUAHelper for writing a b2bua application.

JSR 289 introduces B2BUAHelper class which greatly simplifies developing b2bua applications. In SipServlet 1.0 developing b2bua class of applications was cumbersome. We had to rely on storing requests and responses for later access. The callflow and state transitions had to be sort of co-ordinated.

Following is a basic b2bua servlet. I've highlighted the code where we use the B2BUAHelper class. If you are familiar with developing b2bua applications with the previous specification you would find this lot more easy and clean.

import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.sip.B2buaHelper;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSession;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.UAMode;

@javax.servlet.sip.annotation.SipServlet
public class B2BUAServlet extends javax.servlet.sip.SipServlet {

    @Resource
    public SipFactory sipFactory;

    public static Logger logger = Logger.getLogger("b2bua");

    @Override
    protected void doInvite(SipServletRequest request)
            throws ServletException, IOException {

        B2buaHelper helper = request.getB2buaHelper();

        SipServletRequest nextRequest = helper.createRequest(request);

        helper.linkSipSessions(request.getSession(), nextRequest.getSession());

        SipURI uri = (SipURI) sipFactory.createURI(request.getHeader("Callee"));

        nextRequest.setRequestURI(uri);

        nextRequest.send();
    }

    @Override
    protected void doResponse(SipServletResponse response)
            throws ServletException, IOException {

        SipSession toSS = response.getSession();

        B2buaHelper b2b = response.getRequest().getB2buaHelper();
        SipSession fromSS = b2b.getLinkedSession(toSS);

        List<SipServletMessage> pendingMessages = b2b.getPendingMessages(fromSS, UAMode.UAS);
        if (pendingMessages.size() > 0) {
            SipServletRequest request = (SipServletRequest) pendingMessages.get(0);

            SipServletResponse b2bResponse = request.createResponse(
                    response.getStatus(),response.getReasonPhrase());
            b2bResponse.send();

        } else {
            logger.severe("No pending messages for Session  " + fromSS);
        }
    }

    @Override
    protected void doAck(SipServletRequest request)
            throws ServletException, IOException {

        B2buaHelper helper = request.getB2buaHelper();        

        SipSession toSS = helper.getLinkedSession(request.getSession());

        List<SipServletMessage> pendingMessages = helper.getPendingMessages(toSS, UAMode.UAC);
        SipServletResponse response = (SipServletResponse) pendingMessages.get(0);

        SipServletRequest toAck = response.createAck();
        toAck.send();
    }

    @Override
    protected void doBye(SipServletRequest request)
            throws ServletException, IOException {
        B2buaHelper helper = request.getB2buaHelper();    

        SipSession toSS = helper.getLinkedSession(request.getSession());

        SipServletRequest byeRequest = helper.createRequest(toSS, request, null);

        byeRequest.send();
    }    
}



Wednesday May 07, 2008

Linking multiple SIP and Converged HTTP sessions in a SailFin cluster

In my previous blog we saw how to link SIP and Converged HTTP Session to a SipApplicationSession (SAS). For SailFin cluster we need to configure Converged Load-Balancer(CLB) so that SIP and HTTP requests for a SAS are processed by the same back-end instance This is achieved by using consistent hash routing policy and appropriate Data Centric Rules (DCR) for extracting the hash key. For details on CLB see the functional specification or read the blog here.

For the conference sample we discussed, we want all SIP and HTTP traffic for a particular conference to hit the same back-end. So the key here is the conference name or id. A DCR file that can be used for this purpose is shown below.

 
<?xml version="1.0" encoding="ISO-8859-1"?>
<user-centric-rules>
  <sip-rules>
    <if>   
      <header name="Conference-Name"
        return="request.Conference-Name">
        <exist/>
      </header>    
      <else return="request.to.uri.resolve.user" />
    </if>
  </sip-rules>
    
  <http-rules>   
    <if>
      <request-uri parameter="Conference-Name" return="parameter.Conference-Name">
        <exist/>
      </request-uri>           
      <else return="request.Host"/>
    </if>
  </http-rules>       
</user-centric-rules>

 

Once CLB is configured with consistent hash routing and the DCR file it would route SIP and HTTP traffic for the same hash key to a particular back-end instance. The hash is applied only for initial requests. For subsequent requests it uses the sticky information for routing (Cookie in case of HTTP and bekey in request URI for SIP requests).


Tuesday May 06, 2008

SIPp and SailFin

If you using SIPp to make calls to SailFin read Jens's blog for some of the parameters that has to be copied from Contact header of the 200 OK to the request URI of subsequent requests. For SailFin single instance you need to copy parameter fid (fragment id). This is used by the container for associating dialog to a call. For  SailFin cluster you need to copy both fid and bekey. bekey is used by Converged Load Balancer front-end to route request to the backend.

For a UAC  generate the default scenario from SIPp using

$sipp -sd uac > uac.xml

Update the uac.xml as highlighted.

 

  <recv response="200" rtd="true">
    <action>
        <ereg regexp="fid=([[:alnum:]]*)_([[:alnum:]]*)" search_in="hdr" header="Contact:" check_it="true" assign_to="1" />
        <ereg regexp="bekey=([[:alnum:]]*)" search_in="hdr" header="Contact:" check_it="true" assign_to="2" />
    </action>

  </recv>

  <!-- Packet lost can be simulated in any send/recv message by         -->
  <!-- by adding the 'lost = "10"'. Value can be [1-100] percent.       -->
  <send>
    <![CDATA[

      ACK sip:[service]@[remote_ip]:[remote_port];[$1];[$2] SIP/2.0
      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
      From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
      To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
      Call-ID: [call_id]
      CSeq: 1 ACK
      Contact: sip:sipp@[local_ip]:[local_port]
      Max-Forwards: 70
      Subject: Performance Test
      Content-Length: 0

    ]]>
  </send>

  <!-- This delay can be customized by the -d command-line option       -->
  <!-- or by adding a 'milliseconds = "value"' option here.             -->
  <pause/>

  <!-- The 'crlf' option inserts a blank line in the statistics report. -->
  <send retrans="500">
    <![CDATA[

      BYE sip:[service]@[remote_ip]:[remote_port];[$1];[$2] SIP/2.0
      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
      From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
      To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
      Call-ID: [call_id]
      CSeq: 2 BYE
      Contact: sip:sipp@[local_ip]:[local_port]
      Max-Forwards: 70
      Subject: Performance Test
      Content-Length: 0

    ]]>
  </send>


 Tracing the SIPp call you can see the following.

 

UDP message received [357] bytes :

SIP/2.0 200 OK^M
Content-Length: 0^M
Contact: <sip:$127.0.0.1:15060;fid=instance2_1;bekey=sailfin>^M
To: "sut"<sip:service@127.0.0.1:15060>;tag=ffvglcl9-d^M
Cseq: 1 INVITE^M
Via: SIP/2.0/UDP 127.0.0.1:5061;branch=z9hG4bK-11792-1-0^M
Server: Glassfish_SIP_1.0.0^M
Call-Id: 1-11792@127.0.0.1^M
From: "sipp"<sip:sipp@127.0.0.1:5061>;tag=11792SIPpTag001^M
^M

----------------------------------------------- 2008-05-06 01:11:57:894.606
UDP message sent (382 bytes):

ACK sip:service@127.0.0.1:15060;fid=instance2_1;bekey=sailfin SIP/2.0^M
Via: SIP/2.0/UDP 127.0.0.1:5061;branch=z9hG4bK-11792-1-5^M
From: sipp <sip:sipp@127.0.0.1:5061>;tag=11792SIPpTag001^M
To: sut <sip:service@127.0.0.1:15060>;tag=ffvglcl9-d^M
Call-ID: 1-11792@127.0.0.1^M
CSeq: 1 ACK^M
Contact: sip:sipp@127.0.0.1:5061^M
Max-Forwards: 70^M
Subject: Performance Test^M
Content-Length: 0^M
^M

----------------------------------------------- 2008-05-06 01:11:59:897.111
UDP message sent (382 bytes):

BYE sip:service@127.0.0.1:15060;fid=instance2_1;bekey=sailfin SIP/2.0^M
Via: SIP/2.0/UDP 127.0.0.1:5061;branch=z9hG4bK-11792-1-7^M
From: sipp <sip:sipp@127.0.0.1:5061>;tag=11792SIPpTag001^M
To: sut <sip:service@127.0.0.1:15060>;tag=ffvglcl9-d^M
Call-ID: 1-11792@127.0.0.1^M
CSeq: 2 BYE^M
Contact: sip:sipp@127.0.0.1:5061^M
Max-Forwards: 70^M
Subject: Performance Test^M
Content-Length: 0^M
^M

 


Saturday Apr 19, 2008

Linking multiple SIP and Converged Http sessions to a SipApplicationSession (SAS) in SailFin

See Jan Luehe's blog for an introduction to Converged Http Sessions. In this blog I'll discuss how we can link multiple SIP and Converged Http sessions to the same SAS.  

Lets take the example of a converged conference application where SAS represents a conference instance. Here we will use the conference name to form the SAS ID (though any String can be used for the same). SIP Session (SS) represents each call in the conference. Converged Http Session (CHS) represents users accessing the conference details through the web.

For SipServlets we should use the @SipApplicationKey annotation to link a new request to a particular SAS.

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.sip.SipServlet;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.annotation.SipApplicationKey;

public class ConferenceSipServlet extends SipServlet {

    @SipApplicationKey
    public static String sessionKey(SipServletRequest request) {

        String conferenceName = request.getHeader("Conference-Name");
        return conferenceName;
    }

    @Override
    public void doInvite(SipServletRequest request)
            throws ServletException, IOException {

        SipServletResponse response = request.createResponse(180);
        response.send();

        response = request.createResponse(200, "OK");
        String conferenceName = request.getHeader("Conference-Name");
        
        String sasId = request.getSession().getApplicationSession().getId();
        getServletContext().setAttribute(conferenceName, sasId);

        String userName = request.getTo().getURI().toString();
        request.getSession().setAttribute("UserName", userName);
               
        response.send();
    }

    @Override
    public void doBye(SipServletRequest request) throws IOException {
        SipServletResponse response = request.createResponse(200, "OK");
        response.send();
        request.getSession().invalidate();
    }
}
 
Here the INVITE request has a header 'Conference-Name' which is the conference the user intends to join. The SIP container will link all requests returning the same value for sessionKey() to the same SAS.

The CHS functional specification for SailFin describes different ways of linking CHS and SAS. In this particular example we will use a SAS encoded URL for linking.

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Date;
import java.util.Iterator;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.sip.ConvergedHttpSession;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipSession;
import javax.servlet.sip.SipSessionsUtil;

public class ConferenceHTTPServlet extends HttpServlet {

    @Resource
    SipSessionsUtil ssu;
    static Logger logger = Logger.getLogger("ConferenceHTTPServlet");


    protected void processRequest(HttpServletRequest request,         HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        logger.info("processRequest  " + request.getRequestURL());
        PrintWriter out = response.getWriter();
        try {

            String conferenceName = request.getParameter("Conference-Name");

            if (conferenceName == null) {
                listParticipants(request, out);
            } else {
                redirectRequest(request, response, conferenceName);
            }
        } catch (Exception e) {
            e.printStackTrace(out);
        } finally {
            out.close();
        }
    }

    private void redirectRequest(HttpServletRequest request,
            HttpServletResponse response, String conferenceName) throws Exception {
        ConvergedHttpSession chs = (ConvergedHttpSession) request.getSession(true);
        chs.setAttribute("Conference-Name", conferenceName);

        String sasId = (String) getServletContext().getAttribute(conferenceName);

        logger.info("SAS id = " + sasId);

        SipApplicationSession sas = ssu.getApplicationSession(sasId);
        String chsEncoded = chs.encodeURL("/ConferenceHttpServlet", "http");
        URL url = sas.encodeURL(new URL(chsEncoded));

        logger.info("encoded url = " + url);

        response.sendRedirect(url.toString());
    }

    private void listParticipants(HttpServletRequest request, PrintWriter out)
            throws Exception {
        ConvergedHttpSession chs = (ConvergedHttpSession) request.getSession(false);
        SipApplicationSession sas = chs.getApplicationSession();
        String conferenceName = (String) chs.getAttribute("Conference-Name");
       
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Servlet ConferenceHTTPServlet</title>");
        out.println("</head>");
        out.println("<body>");

        out.println("<h3> Welcome to conference " + conferenceName + "</h3>");

        Iterator<SipSession> sessions = (Iterator<SipSession>) sas.getSessions("SIP");
        out.println("<h4> Participants </h4>");
       
        while (sessions.hasNext()) {
            SipSession session = sessions.next();
            String name = (String) session.getAttribute("UserName");
            out.println(name + " . Call started " + new Date(session.getCreationTime()));   
            out.println("<br>");   
        }

        out.println("<br>");

        out.println("<form action=\"ConferenceHTTPServlet\" method=\"get\">");
        out.println("<input type=\"submit\" value=\"Refresh\"/>");
        out.println("</form>");

        out.println("</body>");
        out.println("</html>");
    }
    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /**
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Short description";
    }

    // </editor-fold>
}


On accessing the Servlet at http://localhost:8080/simple-conf/ConferenceHttpServlet?Conference-Name=sailfin, processRequest() is invoked which in turns calls redirectRequest(). Within this function the SAS instance is fetched using the SAS ID stored in ServletContext by invoking SipSessionsUtil.getApplicationSession(sasId). At this point the CHS and SAS are not linked. The SAS encoded URL looks like this.

http://localhost:8080/simple-conf/ConferenceHttpServlet;sipappsessionid=7,7,sailfin/simple-conf

A redirect or subsequent request on this URL will cause the converged container to link the CHS and SAS. After linking we can access the SIP Sessions that are a part of the same SAS. See listParticipants() in the code sample above which lists all the SIP sessions.

Running 2 SIPp UAC instances and accessing the web Servlet gives the following response:

Welcome to conference sailfin

Participants
sip:foo@127.0.0.1:5060 . Call started Fri Apr 18 16:59:34 IST 2008
sip:bar@127.0.0.1:5060 . Call started Fri Apr 18 16:59:39 IST 2008

The above example will work on a single SailFin instance.
In my next blog, I will discuss how to configure the same in a SailFin cluster.

Saturday Sep 29, 2007

NetBeans project template for SailFin tests

In my previous blog I had mentioned we developed the quicklook invite test (sailfin/sailfin-tests/quicklook/invite) as a NetBeans free-form project. I  updated the NB project.xml for the test so that we can run all targets from NetBeans as we do from the command line. Some targets like (build, rebuild, run) directly map to ide-actions in NetBeans and can be accessed using the IDE shortcuts. Other targets like setup/unsetup and deploy/undeploy can be run from the project context-menu.

sailfin-tests build scripts use a number of properties which are not available to NetBeans. For the targets to work correctly from NB, edit sailfin/sailfin-tests/netbeans.config.properties file. Set the properties as per your local settings.

Also created a template project under sailfin-tests/templates/netbeans-template. To use the template while writing new tests follow these steps.

  1. Copy the template to the test location. Rename the directory appropriately
  2. Change the project name in build.xml and nbproject/project.xml
  3. Edit build.properties and set the relative path to netbeans.config.properties file. This file is available in sailfin/sailfin-tests directory.
  4. Edit build.xml and fix the import location of common.xml. This file is available in sailfin/sailfin-tests/config directory.

With the above changes the template can be used like any regular NB free-form project.


Monday Sep 10, 2007

SailFin test repository created.

The test repository for the SailFin project has been created. It is available under module sailfin/sailfin-tests in the SailFin CVS repository  . The users can also browse the sources here

With the initial version we have tried to put together the overall structure for the test workspace. At the top-level you can find config (directory for common build scripts, properties files etc),  lib, quicklook and community directories and a few config property files. All the tests would go under the community directory. quicklook directory will have the SailFin QuickLook tests which is a subset of the community tests and will be hooked into the SailFin build system.

We use ant build scripts for various tasks like compile, package and wrapping SailFin CLI commands. We also plan to support maven/maven2 for better integration with the SailFin build system.

To start we have put a SIP INVITE test under quicklook/invite. For the UAC we have used SIPp and JAIN-SIP api. The test is developed as a free-form NetBeans project. This allows users to use the NB editor for sources and use the common build scripts for compile, packaging, deploy and other tasks. The top-level README provides the steps for setting up and running the tests. Please feel free to provide any feedback/suggestions at quality@sailfin.dev.java.net


Calendar

Feeds

Search

Links

Navigation

Referrers