Articles
Enterprise Architecture
The SIP Servlet Programming Model
Pages:
1,
2
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.