The SIP Servlet Programming Model
Pages: 1, 2

Learning the API

This section discusses various interfaces specified by the SSAPI. The SSAPI is a fairly simple API from the perspective of application developers. The SSAPI defines interfaces to access SIP request and response messages; there is a SipServletRequest and a SipServletResponse interface for this purpose. SIP also makes use of SIP URIs and SIP addresses; there are convenience classes ( URI, SipURI, TelURL, Address) to access SIP headers implementing these interfaces. Then there is a class of objects that deals with sessions, as SIP dialogs are a form of session. This concept of sessions corresponds to SipSession and SipApplicationSession objects in the API. This section of the article starts by discussing the SipServlet interface, which is the entry point from container to application.

SipServlet objects

The SipServlet class extends the GenericServlet class in the servlet base package, and the service method dispatches the SIP message to either the doRequest() or the doResponse(), which in turn invokes one of the doXXX() methods for requests (for example doInvite(), doSubscribe()) or doXXX() methods for responses like doSuccessResponse() and doErrorResponse().

A servlet-mapping element is defined in the deployment descriptor by which the developer can define the rule that must be satisfied before invoking the servlet. The mapping rules have a well-defined grammar in JSR 116. As an example, the following mapping element ensures that the servlet defining this rule will be invoked only if the request is INVITE and the host part of the Request-URI contains the string bea.com.

<pattern>
  <and>
    <equal>
      <var>request.method</var>
      <value>INVITE</value>
   </equal>
    <contains ignore-case="true">
      <var>request.from.uri.host</var>
      <value>bea.com</value>
    </contains>
  </and>
</pattern>

Usually just one SipServlet object is accessed by concurrent requests, so evidently this is not a place for any call-/session-specific data structure. Your doXXX() methods will usually hold the business logic to act on a call. Consider the following code snippet:

1: package test;

2: import javax.servlet.sip.SipServlet;
3: import javax.servlet.sip.SipServletRequest;
4: import java.io.IOException;

5: public class SimpleUasServlet extends SipServlet {
6:   protected void doInvite(SipServletRequest req) 
7:      throws IOException {
8:     req.createResponse(180).send();
9:     req.createResponse(200).send();
10:  }
11:  protected void doBye(SipServletRequest req) throws IOException {
12:    req.createResponse(200).send();
13:    req.getApplicationSession().invalidate();
14:  }
15: }

This is a simple UAS servlet that gets invoked on an incoming INVITE request (triggered by a rule similar to that defined above). The container invokes the application by invoking the doInvite() method. The application chooses to send a 180 response (line 8) followed by a 200 response (line 9). As you may have noticed, the application is not interested in doing anything with the ACK that would be sent by the UAC. In this case, the container will receive the ACK and silently ignore it. (Had it been a stateful proxy, it would have proxied it). The applications do only what they need to do and nothing more.

SIP factory

As the name suggests, this class is used to create various SSAPI objects like Request, SipApplicationSession, and Addresses. An application acting as a UA will use this class to create a new request, and requests created through the factory have a new Call-ID (with the exception of a particular method for B2BUA in which the application can choose to reuse the Call-ID on the upstream leg) and do not have a tag in the To header. The factory object can be retrieved through the javax.servlet.sip.SipFactory attribute on the ServletContext.

SIP messages

There are two classes of SIP messages: SipSevletRequest and SipServletResponse, representing SIP requests (INVITE, ACK, INFO, for example) and SIP responses (1xx, 2xx, for example). These messages are delivered to the application through various doXXX() methods defined in the SipServlet class. SIP is an asynchronous protocol and therefore it is not obligatory for the applications to respond to a request when the doRequest() method is invoked. The applications may respond to the request at a later stage, given that they have the access to the original request object.

The SipServletRequest and SipServletResponse objects are both derived from the base SipServletMessage object, which provides some common accessor/mutator methods like getHeader(), getContent(), and setContent(). The SipServletRequest defines some useful methods for request processing. The SipServletRequest.createResponse() method creates an instance of the SipServletResponse class representing the response to the request that was used to create it.

Similarly, the SipServletRequest.createCancel() creates a CANCEL request to a previously sent request. Note that the CANCEL is sent if the UAC decides not to proceed with the call if it has not received a response to the original request. Sending a CANCEL if you have received a 200 response or have not received a 100 response would be incorrect protocol behavior; luckily, the SSAPI comes to the rescue here too. The UAC application can create and send a CANCEL oblivious to these details. The container makes sure that the CANCEL is sent out only if a 1xx class response is received and any >200 response is not received.

The SipServletRequest.getProxy() gets you the associated Proxy object to let the application perform various proxy operations. The SipServletRequest.pushRoute(SipURI) method allows the UAC or a proxy to route the request through a server identified by the SipURI. The effect of this method is to add a Route header to the request at the top of the Route header list.

Another method of interest is SipServletRequest.isInitial(). It is important to understand the concept of initial and subsequent requests as they may have different treatments within your application. As an example, if you get a Re-INVITE request, it will also be delivered to the servlet's doInvite() method, but the isInitial() will return false.

Initial requests usually are requests outside of a dialog, for which the container does not have any information. The container, on receiving an initial request, will go through the mechanism it has established to find out which application to invoke for this request. This may involve looking up the servlet-mapping rules. As you have seen, some requests create dialogs—so any request received after a dialog is created falls in the category of a subsequent request.

Closely linked with the dialog construct in SIP is the SipSession object in SSAPI, which will be discussed next. One of SipServletResponse's methods that is of particular interest is createAck(), which creates an ACK request on a 2xx response received for the INVITE transaction. ACKs for non 2xx responses of the INVITE transaction are created by the container itself.

SipSession

The SipSession roughly corresponds to a SIP dialog; for UAs it maintains the dialog state as specified by the RFC to correctly create a subsequent request in a dialog. So if your application is acting as a UA (UAC or B2BUA) and, after having processed an initial request, wants to send out a subsequent request in a dialog (like a Re-INVITE, or a BYE), then it must use the SipSession.createRequest() method rather than use one of the SipFactory methods, which would result in requests created out of dialog.

The SipSession is also a place for the applications to store the session-specific state they want to maintain in addition to what the container maintains. The application can set/unset attributes on the SipSession object; these attributes are made available to the application between different invocations. SipSession also provides for a SipSession.setHandler(String nameOfAServlet), which assigns a particular servlet in the application to get subsequent requests for that SipSession.

SipApplicationSession

The SipApplicationSession logically represents an instance of the application. An application may have one or more protocol sessions associated with it. These protocol sessions can be SipSession or HttpSession, as of JSR 116. Applications can store application-wide data as an attribute of the SipApplicationSession. One important thing to note is that any attribute set on a SipApplicationSession or its associated SipSessions is visible only to that particular application. The SSAPI defines a mechanism by which more than one application can be invoked on the same call, a feature known as application composition. SipApplicationSession provides a getSessions() method that returns the protocol sessions associated with this application session. Figure 3 shows the containment hierarchy of the different sessions in the SSAPI.

Sessions in SSAPI
Figure 3. Sessions in SSAPI

One of the most interesting methods in the SipServletApplication interface is encodeUri(URI). This method encodes the SipApplication identifier with the URI in the argument. If the container sees a new request with this encoded URI, even if on a different call, it associates the encoded SipApplicationSession with the request. This innocuous-looking method has the power to link two disparate calls and can be used in a number of other innovative ways. SipApplicationSession is also associated with application session timers, which is the subject of the next topic.

Proxy interface

A proxy is a behavior specified by the SIP RFC 3261 in great detail. In a nutshell, a proxy forwards a request to a destination. However, a proxy comes in different flavors. You can have a stateless or a stateful proxy based on whether you need to maintain SIP transaction states or not. A proxy may decide to record-route, whereby it will receive subsequent requests for the dialog; otherwise it falls out of the ongoing dialog after the initial routing decision. A proxy can fork a request to multiple destinations; it can do this forking in parallel (at the same time) or sequentially (one after the other). All the complex details of the proxy behavior are nicely hidden behind an easy-to-use proxy interface. So a simple routing proxy could be as simple as this piece of code:

protected void doInvite(SipServletRequest req)
   throws ServletException, IOException {
      Proxy p = req.getProxy();
      SipURI uri = (SipURI) req.getRequestURI().clone();
      uri.setPort(5081);
        p.proxyTo(uri);
}

Here the servlet proxies the request to the same URI but on a different port.

Application timers

The SSAPI provides a timer service that can be used by the applications. The TimerService interface can be used and can be retrieved from the ServletContext as an attribute. The TimerService defines a createTimer(SipApplicationSession appSession, long delay, boolean isPersistent, java.io.Serializable info) method to start an application-level timer. As you can see, a SipApplicationSession is implicitly associated with the timer. When the timer fires an application, the defined TimerListener is invoked and the ServletTimer object passed up, through which the SipApplicationSession can be retrieved. This provides the right context for the timer expiry.

Some Examples

Now let's look at some sample code and see how the constructs studied so far can be put to use. I start with a converged application. If you remember, a converged application is one that involves more than one protocol, in this case SIP and HTTP.

1: <html>
2: <body>
3: <%
4:   if (request.getMethod().equals("POST")) {
5:    javax.servlet.sip.SipFactory factory = 
6:      (javax.servlet.sip.SipFactory)
   application.getAttribute(javax.servlet.sip.SipServlet.SIP_FACTORY);
      
7:    javax.servlet.sip.SipApplicationSession appSession =
8:       factory.createApplicationSession();
   
9:    javax.servlet.sip.Address to = 
10:      factory.createAddress("sip:localhost:5080");
11:   javax.servlet.sip.Address from = 
12:      factory.createAddress("sip:localhost:5060");
   
13:   javax.servlet.sip.SipServletRequest invite =
14:       factory.createRequest(appSession, "INVITE", from, to);
   
15:   javax.servlet.sip.SipSession sess = invite.getSession(true);
16:    
                        
sess.setHandler("sipClickToDial");
17:   //invite.setContent(content, contentType);
18:   invite.send();
19:   }
20: %>
   
21: <p>
22: Message sent ...
23: </body>
24: </html>
                      

This is a simple JSP page that can be accessed through an HTTP URL. This JSP needs to be part of the same application as the SIP servlet which you call sipClickToDial(). At a high level this HTTP servlet creates a SIP request from the factory and sends it out to a SIP URI. This is a skeleton of a click-to-dial application where, by clicking on a Web page, you can initiate a SIP call.

When an HTTP POST request is sent up to this HTTP servlet, it gets hold of the SipFactory on line 5-6. Next, it creates an application session on lines 7-8. This application session will be the centerpiece of all future SIP and HTTP interactions of this application. The purpose here is to send out a SIP request, which occurs on lines 13-14, but before that, you create the From and To headers that will be used to form this INVITE request. On line 16 you assign a handler to the SipSession that is associated with the INVITE Request you just created; this ensures that the response that will be sent by a UAS that receives this INVITE request is dispatched to a SIP servlet for processing.

As of this writing, however, there is no mechanism to access the SipApplicationSession from the HTTP scope once the control returns from the HTTP servlet that created the SipApplicationSession and associated SipSessions. In simple terms, this means that any subsequent HTTP request in the context of the same HTTP session cannot access the SipApplicationSession.

I'll conclude with another simple example. In this example the application receives a SUBSCRIBE request and sends out a NOTIFY request. The application wants to wait for the notification recipient for three seconds and, if it does not see a success response (2xx class response), then it may take some action like update a database of the missed attempt.

1:public class Sample_TimerServlet extends SipServlet
2:  implements TimerListener {
  
3:  private TimerService timerService;
4:  private static String TIMER_ID = "NOTIFY_TIMEOUT_TIMER";
  
5:  public void init() throws ServletException {
6:    try {
7:      timerService = 
8:        (TimerService)getServletContext().getAttribute
9:          ("javax.servlet.sip.TimerService");
10:    }   
11:    catch(Exception e) {
12:     log ("Exception initializing the servlet "+ e);
13:    }
14:  }
  
15:  protected void doSubscribe(SipServletRequest req)
16:       throws ServletException, IOException {
17:    req.createResponse(200).send();
18:    req.getSession().createRequest("NOTIFY").send();
19:    ServletTimer notifyTimeoutTimer = 
20:      timerService.createTimer(req.getApplicationSession(), 3000, 
21:               false, null);
22:    req.getApplicationSession().setAttribute(TIMER_ID, 
23:             notifyTimeoutTimer);
24:  }
  
25:  protected void doSuccessResponse(SipServletResponse res) 
26:       throws javax.servlet.ServletException, java.io.IOException {
27:    if (res.getMethod().equals("NOTIFY")) {
28:      ServletTimer notifyTimeoutTimer =      
29:        (ServletTimer)(res.getApplicationSession().
             getAttribute(TIMER_ID));
30:      if (notifyTimeoutTimer != null) {
31:        notifyTimeoutTimer.cancel();
32:        res.getApplicationSession().removeAttribute(TIMER_ID);
33:      }
34:    }
35:  }
  
  
36:  public void timeout(ServletTimer timer) {
37:    // This indicates that the timer has fired because a 200 to
38:    // NOTIFY was not received. Here you can take any timeout 
39:    // action. 
40:    // .........
41:    timer.getApplicationSession().removeAttribute
         ("NOTIFY_TIMEOUT_TIMER");
42:  }   
43:}

Here the servlet itself implements TimerListener, so the servlet will be notified of the timeout. You start by getting hold of the TimerService from the ServletContext in lines 7-9, and then you set the timer for three seconds on getting the SUBSCRIBE request on line 20. You could set the timer at any stage. Note that there is an option of attaching an object with the timer; this option can be used as an identifier or an invocable message at a later stage. For this sample you just identify the timer with a literal. After sending the NOTIFY, you create the timer and save its reference in the SipApplicationSession for later use on line 22.

Now if you get a 200 response to the NOTIFY, you can extract the timer reference and cancel the timer (line 25); however, if you did not get the 200 response in three seconds, then the timer will fire and the timeout() callback will be called by the container. (line 36).

Summary

SIP has emerged as the protocol of choice for VoIP networks. The final seal of approval for SIP has been the mandate of 3GPP (3rd Generation Partnership Project) for SIP as the base protocol for the IMS architecture. SIP servlet technology is increasingly being adopted as the programming model of choice for application servers, primarily because of its ease of use and power. In addition, the SIP servlet specifications are more close to the Java EE standards and are an automatic choice for building converged applications. I hope this article gives you a solid grounding in the SIP servlet programming model and helps you get started with these technologies.

References

  • JSR 116 - The SIP Servlet Specification 1.0
  • JSR 289 - The SIP Servlet Specification 1.1 (WIP)
  • JSR 53 - The Servlet Specification 2.3
  • JSR 154 - The Servlet Specification 2.6 (WIP)
  • RFC 3261 - Base SIP Protocol Specification

Nasir Khan is the architect of BEA's Weblogic SIP Server product. He has about 12 years of experience of designing and building scalable enterprise Java applications.