Sip Servlet Click-to-Dial example

This is an example of a click-to-dial application using Sip Servlets. Two users register softphones, and may call each other by clicking on a web site.

The example code is available for download here.

About the example

This simple example shows how SIP Servlets can be used along with HTTP servlets to place calls from a web page. This example consists of the following steps:

  1. Alice and Bob each log in to the web application
  2. Alice and Bob each register a SIP Softphone
  3. Alice clicks on the "Call" link to place a call to Bob
  4. Alice's phone rings
  5. When Alice picks up her phone, Bob's phone rings
  6. When Bob answers his phone, the call is connected
  7. When one of them hangs up, the other one is also disconnected

Here are the SIP messages that are exchanged in this process:

To handle these messages, we use two SIP Servlets and one HTTP Servlet. An overview of these servlets is given below.

Registrar SIP Servlet

The first servlet is a SIP Servlet that handles softphone registration. When Alice or Bob launches their softphone, it registers the softphone address with a SIP proxy. We will record the softphone contact information using Java Persistence API. It will be used later to call Alice or Bob.

Below is pseudocode for clicktodial.sip.RegistrarServlet. The full code can be found in the download bundle.

/** A SIP Servlet to handle SIP REGISTER requests. */
public class RegistrarServlet extends SipServlet {

    // inject the SipFactory
    @Resource(mappedName="sip/ClickToDial")
    private SipFactory sf;
    
    /** Handle a SIP Register request. */
    protected void doRegister(SipServletRequest req) 
        throws ServletException, IOException 
    {
        int response;

        // Figure out the name the user is registering with.  This is the
        // user portion of the SIP URI, e.g. "Bob" in "sip:Bob@x.y.z:port"
        String username = ...
        
        // get the Person object from the database
        Person p = mf.getPerson(username);
        if (p == null) {
            // no person found in the database
            response = SipServletResponse.SC_NOT_FOUND;
        } else {
            // the Expires header tells us if this is a registration or
            // unregistration attempt.
            int expires = Integer.parseInt(req.getHeader("Expires"));

            if (expires == 0) {
                // unregister
                response = handleUnregister(req, p);
            } else {
                // register
                response = handleRegister(req, p);
            }
        }

        // send the response
        SipServletResponse resp = req.createResponse(response);
        resp.send();
    }
    
    /** Handle a registration request */
    private int handleRegister(SipServletRequest req, Person p)
        throws ServletException
    {

        // Get the contact address from the request.  Prefer the
        // "Contact" address if given, otherwise use the "To" address
        Address addr = sf.createAddress(req.getHeader("Contact"));

        // store the contact address in the database
        p.setTelephone(addr.getURI().toString());
        
        ModelFacade mf = (ModelFacade) getServletContext().getAttribute("Model");
        mf.updatePerson(p);
        
        return SipServletResponse.SC_OK;
    }    
}

PlaceCall HTTP Servlet

The PlaceCall HTTP Servlet receives an HTTP post when Alice clicks on the "Call" link in her browser. It sends the initial SIP INVITE request to Alice and stores Bob's address in the SipSession. Below is pseudocode for clicktodial.web.PlaceCallServlet.

/** Place a phone call when a user clicks */
public class PlaceCallServlet extends HttpServlet {
    
    // inject the SipFactory
    @Resource(mappedName="sip/ClickToDial")
    private SipFactory sf;
    
    /** Processes requests for HTTP POST method. */
    protected void processRequest(HttpServletRequest request, 
                                  HttpServletResponse response)
        throws ServletException, IOException 
    {
        // get the source and target from the request
        String sourceUserName = request.getParameter("source");
        String targetUserName = request.getParameter("target");
        
        // look up the relevant Person objects in the database
        ModelFacade mf = (ModelFacade) getServletContext().getAttribute("Model");
        Person source = mf.getPerson(sourceUserName);
        Person target = mf.getPerson(targetUserName);

        // get the phone numbers as SIP Addresses
        SipApplicationSession appSession = sf.createApplicationSession();
        Address to = sf.createAddress(source.getTelephone());
        Address from = sf.createAddress(target.getTelephone());
        
        // create an INVITE request to the first party from the second
        SipServletRequest req = sf.createRequest(appSession, "INVITE", from, to);
        
        // store the second party's address in the sip session
        req.getSession().setAttribute("SecondPartyAddress", from);
        
        // send the INVITE request            
        req.send();
    }   
}

Call SIP Servlet

The Call SIP servlet handles the OK response to the INVITE sent by the PlaceCall servlet above. Once an OK is received from Alice, this servlet sends an INVITE to Bob, specifying Alice's media preferences. When an OK is received from Bob, it sends an ACK to both parties. It also handle the BYE and call tear down.

Pseudocode for clicktodial.sip.CallSipServlet is below.

/** A SIP Serlvet that handles responses to click-to-dial invites
 */
public class CallSipServlet extends SipServlet implements SipSessionListener {

    // inject the SipFactory
    @Resource(mappedName="sip/ClickToDial")
    private SipFactory sf;
    
    
    /** Handle an OK response. */  
    protected void doSuccessResponse(SipServletResponse resp)
        throws ServletException, IOException 
    {
        SipSession session = resp.getSession();
                
        // if the response was OK, invite the other party
        if (resp.getStatus() == SipServletResponse.SC_OK) {
            
            // we are in one of two states here.  Either this OK is from
            // the original invitee, meaning they answered and it is time
            // to ring the second pa
2000
rty, or the OK is from the second
            // party, and we should acknowlege the call with both sides
            
            
            // check for the first case, where this OK is from the original
            // invitee
            Address secondPartyAddress = 
                    (Address) resp.getSession().getAttribute("SecondPartyAddress");  
            if (secondPartyAddress != null) {
                

                // invite the second party
                SipServletRequest invite = sf.createRequest(resp.getApplicationSession(),
                        "INVITE", session.getRemoteParty(), secondPartyAddress);
                
                // get the SDP from the OK message
                if (resp.getContentType().equals("application/sdp")) {
                    invite.setContent(resp.getContent(), "application/sdp");
                }        
                
                // send the invite
                invite.send();
            } else {
                // send acks
                SipServletRequest secondPartyAck = resp.createAck();
                SipServletRequest firstPartyAck = 
                        (SipServletRequest) resp.getSession().getAttribute("FirstPartyAck");
            
                // get the SDP from the second party's OK message
                if (resp.getContentType().equals("application/sdp")) {
                    firstPartyAck.setContent(resp.getContent(), "application/sdp");
                }
                
                firstPartyAck.send();
                secondPartyAck.send();
            }
        }
    }
}

Installing the example

Please follow the following steps to install and start the server

  1. Download and install SailFin..
  2. The example is distributed as a NetBeans project. You can build and deploy it directly from NetBeans to a SailFin container. You can also use the SIP Application Development Module for NetBeans to develop new SIP Application projects.
  3. Start SailFin (SF_HOME/bin/asadmin start-domain)
  4. Start JavaDB (SF_HOME/bin/asadmin start-database)

Follow the steps below to the application

  1. Unzip the download bundle
  2. Open the project in NetBeans
  3. In the project properties, under "Run" make sure SailFin is selected as the server
  4. Right click on the project and select "Run"

Alternatively, you can skip NetBeans and use the <b>ant</b> scripts. There is a <b>build.xml</b> in the top level directory that will automatically build it and generate a deployable archive in <b>dist/ClickToDial.war</b>. You can deploy using <b>asadmin deploy dist/ClickToDIal.war</b>.

Before you deploy, create a JDBC resource called jdbc/sample. You can do that by logging into the Admin console (http://localhost:4848) and clicking on the little triangle to expand <b>Resources</b>. Click on <b>JDBC</b>. Click on <JDBC Resources</b>. You will see a list of already existing JDBC resources. If you do not see <b>jdbc/sample</b>,you can create it by clicking on <b>New</b> button. Use <b>jdbc/sample</b> for name and choose the pre-defined <b>DerbyPool</b> for the database connection pool. Click <b>OK</b>.

Running the example

You can test this example using any SIP-based software phone. Instructions are provided for freely available X-Lite softphones: x-lite

1. Browse to the login screen

In your web browser, open http://hostname:8080/ClickToDial, where hostname is the name of the host where SailFin is running.

2. Log in as Bob

Start off by logging in as Bob. Select 'Bob' in the drop-down menu and click Login. This should bring up Bob's home page, with no softphone registered:

3. Setup Bob's Softphone

X-Lite is a freely available softphone for many platforms. To set up Bob's softphone, I used the settings below, where 129.148.173.187 is the IP address of my SailFin server. It is running on port 5060.

With X-Lite setup, enable the account to perform the registration to SailFin server which is acting as a registrar. You should see a message like the following in the SailFin log:

Received register request: "Bob"<sip:Bob@192.168.2.103>
Register address: "Bob"<sip:Bob@192.168.2.103:9990;transport=udp>;expires=3600

X-Lite also will show a message: <b>You are logged in as Bob</b>. Once X-Lite successfully registers, you can reload Bob's Login web page to get more information about his registration:

4. Log in as Alice

On another computer, navigate to the login page and login as Alice.

5. Setup the x-lite Softphone. Same as above, but configured for Alice.

After Alice's phone registers, you should be able to reload the page and see both softphone registered and ready:

6. Place a Call

Now we are all set up to have Alice place a call to Bob (or Bob to Alice). When Alice clicks on the "Call" link next to Bob's name, her softphone should ring. When she picks up, Bob's sofphone should ring. When he answers, the call is connected. Either can disconnect first and the other is automatically disconnected by CallServlet.

Make sure, you do the web login first. If the SIP Soft Phone connects to SailFin before the web login, Registration will fail. The sample can be modified to remove this limitation, if you wish.

For More Information

See the SailFin site as well as the Sip wiki page.


SipMessage.png (image/png)
BobProp.png (image/png)
Alice.png (image/png)
Login.png (image/png)
BobRegistered.png (image/png)
ekiga.png (image/png)
AliceRegistered.png (image/png)
xtenbob.png (image/png)
Bob1.png (image/png)
bobready.png (image/png)
BothLoggedIn.png (image/png)
xten.png (image/png)