IBM WebSphere Developer Technical Journal: Developing RESTful SIP services for a high availability environment

Creating a service that exposes function for an existing Session Initiation Protocol (SIP) dialog can be challenging. This article explains a pattern for how to design, write, and deploy such an SIP service in a highly available environment.

Share:

Erik Burckart (ejburcka@us.ibm.com), WebSphere Application Server Lead Architect, IBM

Erik Burckart is a lead architect of the WebSphere Application Server product. He is a graduate from the University of Pittsburgh'’s School of Information Science, where he studied telecommunications, software development, and human computer interaction. Through his work with SIP servlets in WebSphere Application Server, he has joined the SIP Servlet 1.1 (JSR 289) Expert Group and has made numerous contributions in combining the state of the art Java EE platform with the latest SIP Servlet specification.



Yutaka Obuchi (obuchi@hitachisoft.jp), Software Engineer, Hitachi Software Engineering Co., Ltd.

Yutaka Obuchi is a Software Engineer for Hitachi Software Engineering in Japan, an IBM Business Partner. Currently he is working at the IBM WebSphere Technology Institute in Raleigh as a trainee. He has three years of experience in J2EE technologies and two years of experience in several areas of telecom system solutions, such as function analysis, customization, implementation, and system integration. He holds a Mater of Science in Physics from Kyoto University in Japan.



28 February 2007

Also available in Japanese

From the IBM WebSphere Developer Technical Journal.

Introduction

When utilizing the Sessions Initiation Protocol (SIP) support in IBM® WebSphere® Application Server V6.1, you might want some applications to provide SIP services for an SIP dialog once the dialog has been established. For example:

  • An application might want the ability to terminate a call after the dialog has been established because the corresponding prepaid service has run out (such as a calling card, pay phone, or prepaid mobile), or because a user is terminating the call via the Web.
  • Another application may want to, from the Web, invite another user into the same media stream, such as with a conference calling application.

To make these actions happen, you need an application to sit in the middle of the call, act as a back-to-back user agent, and provide access to the services mentioned.

This article demystifies how you can write this SIP service, make the service highly available, and expose the service to other applications using WebSphere Application Server V6.1.


The need for a high availability solution

If SIP services are exposed as an EJB or Web service, there is no easy way in a high availability (HA) environment to ensure that the EJB or Web service being executed will be on the same server that owns the SIP session. The problem with this is that the stateless routing tier will need context in order to find the server that has the SIP dialog. In a single server environment (Figure 1), the Web service can easily access the server that is involved in the SIP dialog since there is only the single server. In an HA environment (Figure 2), the Web service has no context for the stateless proxy servers to route the Web service to the correct machine.

Figure 1 shows a single server which handles SIP function, and has a Web service capable of handling SIP functions for that specific SIP server. Figure 2 shows a highly available infrastructure, where the Web service has no context to route to the correct server handling the SIP dialog

Figure 1. A single server environment
A single server environment
Figure 2. A highly available infrastructure
A highly available infrastructure

Restful SIP service pattern

One of the easiest ways to create an SIP-enabled service is to use the representation state transfer (REST) design pattern through utilizing HTTP in a converged HTTP and SIP application. In this type of application, the SIP servlet will act either as a proxy or a back-to-back user agent (B2BUA). Whether this is a proxy or B2BUA influences the capabilities of the SIP service:

  • A B2BUA will be able to act as a universal access server (UAS) and universal access client (UAC) in order to initiate requests in the dialog.
  • A proxy application will not be able to generate messages, but will give access to the information about the dialog itself.

This means, for example, that you can close a connection as a B2BUA by generating a BYE message, but you cannot do so as a proxy.

Figure 3 shows a visual representation of an application that includes SIP servlets, HTTP servlets acting as REST services, and other application elements such as EJBs and Web services.

Figure 3. A visual representation of an application
A visual representation of an application

In Figure 3, the SIP servlet will first capture an encoded URI of the main HTTP service. To do this, the SIP servlet will obtain the SipApplicationSession, cast it to an IBMApplicationSession, and use the IBMApplicationSession to encode a URI that will let an HTTP request get access to the SipApplicationSession that has been already created with the SIP dialog. From there, the HTTP services will have access to the SipSession and be able to perform actions such as terminating the call.

With the encoded URI captured during the SIP dialog establishment, the application will need to publish that encoded URI in a way that clients can access it. At that point, there are a variety of options as to how the REST service can be accessed.

For example, if the desire is to expose a Web service, then the SIP application can publish the URI to a database, JMS, or other means such that the Web service that is invoked has access to the URI. That Web service can then drive the REST service to provide the actions desired. This is illustrated in Figure 4, in which a Web service is called by an external machine, then drives the request for the REST service back through the proxy servers to load balance and route the REST service requests. These are the steps, as indicated in the diagram:

  1. An SIP dialog is established and the server has an SipSession for it.
  2. The application encodes a URI for the HTTP REST service through the IBMApplicationSession. It then inserts that into the database.
  3. Another client makes a Web service to take an action, such as closing a call for a specific user.
  4. The proxy, not having any context to route with affinity, chooses a backend server to which to send the Web service request.
  5. The Web service fetches the encoded URI from the database.
  6. The Web service makes a call to that REST service. This request is made back through the proxy servers for the affinity-based routing.
  7. The proxy, utilizing the affinity in the URI, sends the request to the server that has the session.
Figure 4. Web service called by an external machine
Web service called by an external machine

In a second example, shown in Figure 5, the client is a simple Web browser. Here, the client makes a request to display any sessions to which the user can have access. At that point, the user is given a set of encoded URIs that will enable them to manipulate those sessions and dialogs established. In Figure 5, the user makes a Web request for all sessions available for an action and takes action for those sessions. (This example also utilizes JMS as the means to publish the encoded URIs.) Here are the steps outlined in the diagram:

  1. An SIP dialog is established and the server has an SipSession for it.
  2. The application encodes a URI for the HTTP REST service through the IBMApplicationSession. It then publishes that through a JMS queue.
  3. A browser subsequently makes a request for any actions. This request could be filtered by authenticated user name.
  4. The proxy, not having any context to route with affinity, chooses a backend server to which to send the Web request.
  5. The HTTP servlet uses a subscription to the JMS queue to which the SIP application is publishing in order to return a list of encoded URIs and information back to the browser so that the client can take any actions.
  6. Once the client selects an action (represented by an encoded URI) to proceed, the browser the makes a call to that REST service. This request is made through the proxy server.
  7. The proxy, utilizing the affinity in the URI, sends the request to the server that has the session.
Figure 5. User makes a Web request for all sessions available for an action
User makes a Web request for all sessions available for an action

Next, we will walk through this second example in greater detail with a sample application.


Sample application

In the sample application included with this article, you can terminate the calls that are established by two SIP endpoints, a UAC (client) and UAS (server), from a Web browser. The first page that loads in this Web application shows a list of the calls that have already been established at that time. This page also asynchronously fetches and displays any calls that are established sometime after the page was first loaded. The page enables access to information about the calls that are outstanding and easily terminates any of the calls with a click of a button.

This application started from a basic B2BUA application. In this type of application, the SIP servlet acts as a UAS for the call coming in from the UAC, and acts as a UAC to the UAS the call is destined for. This provides the SIP servlet the capability to generate requests in the message flow and also do things like terminate the call.

There are four important parts of the application, detailed below:

  1. Fetch an encoded URI from the logic within the SIP servlet
  2. Publish the encoded URI from an SIP servlet
  3. Fetch that published encoded URI from an HTTP servlet
  4. Terminate the call from the HTTP servlet

1. Fetch an encoded URI from the logic within the SIP servlet

In this sample, the application server generates encoded URIs so that when the HTTP servlet is called, the servlet can have access to the IBMApplicationSession. The base URI is important as it maps to the HTTP servlet using the web.xml file. The base URI which is used adheres to this pattern:

http://[IPAddress]:[Port#]/sample/calls/

where: [IPAddress] is the IP address of the server which the sample application is running on, and [Port#] is the port number which the sample application is listening to.

You need to create one of these encoded URIs for each call that passes through the SIP servlet, which publishes the encoded URI (called PubMsgServlet in our sample). When the SipServlet's doInvite method gets called, the servlet can get access to the SipApplicationSession from the request message, which is the primary parameter of the doInvite method. From the request message, the servlet can call the getApplicationSession method and cast it to an IBMApplicationSession. From there, the URI above can be used to encode a URI using the IBMApplicationSession's encodeURI method as shown here:

InetAddress in = InetAddress.getLocalHost();
IBMApplicationSession iasess = (IBMApplicationSession)req1.getApplicationSession();
String apURL = iasess.encodeURI("http://"+in.getHostAddress()+":9080/sample/calls");

2. Publish the encoded URI from an SIP servlet

As mentioned earlier, the application will publish this encoded URI information using JMS. The sample includes a plain old Java™ object (POJO), CallInfo, which is published using the messaging engine in WebSphere Application Server V6.1. CallInfo includes the encoded URI created in the first step, plus some information about the call, such as who is calling, who is being called, and what time the call is starting:

public class CallInfo implements Serializable{
	private String accessPointURL;
	private Date callStartTime;
	private String callFrom;
	private String callTo;	

	public CallInfo(String apURL, String callFrom, String callTo, Date callStartTime) {
		this.accessPointURL = apURL;
		this.callFrom = callFrom;
		this.callTo = callTo;
		this.callStartTime = callStartTime;
	}	.		.
}

In the SIP servlet sample, PubMsgServlet, the CallInfo object is seeded with the information in the doInvite method:

CallInfo cInfo = new CallInfo(apURL,req1.getFrom().toString(),req1.getTo().toString(),
	new Date());

After creating the CallInfo object, the application publishes it to the messaging engine:

Context ctx = new InitialContext();
callInfoQueue = (javax.jms.Queue)ctx.lookup("java:comp/env/jms/CallInfo/dest");
callInfoQCF = (javax.jms.ConnectionFactory)ctx.lookup("java:comp/env/jms/CallInfo/cf");
javax.jms.Connection conn = null;
MessageProducer msgProducer = null;
Session sess = null;
try{
	conn = callInfoQCF.createConnection(); 
	sess = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
	msgProducer = sess.createProducer(callInfoQueue);
	ObjectMessage message = sess.createObjectMessage(cInfo);
	msgProducer.send(message);

3. Fetch that published encoded URI from an HTTP servlet

In this sample, the container can receive HTTP GET requests to two URLs from the Web browser. One is for (A) the initial request from the browser, and the other is for (B) subsequent asynchronous access from the javascript function, which is triggered periodically after the page is loaded. In both cases, doGet method in the HTTP servlet, called MngCallsServlet, is triggered:

Context ctx = null;
Session session = null;
ArrayList cInfos = new ArrayList();
try{
	ctx = new InitialContext();
	cf = (ConnectionFactory)ctx.lookup("java:comp/env/jms/CallInfo/cf");
	dest = (Destination)ctx.lookup("java:comp/env/jms/CallInfo/dest");
	
	// initializing the flag which is for checking 
	// if the last message in the queue has been already fetched 
	boolean lastMsg_already_fetched = false; 
	
	while(!lastMsg_already_fetched){
		try{
			conn = cf.createConnection();
			conn.start();
			session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
			
			MessageConsumer consumer = null;
			Message message = null;
			try{
				consumer = session.createConsumer(dest);
				CallInfo cInfo = null;
				message = consumer.receive(1);
				if(message!=null){
					cInfo = (CallInfo)((ObjectMessage)message).getObject();
					cInfos.add(cInfo);
				}else{
					lastMsg_already_fetched = true;
				}

(A) If the request is the initial access, it is to http://[IP address]:[Port#]/sample/calls. Then, MngCallsServlet fetches information about the calls published and forwards the information to CallsList.jsp, which makes a table from the information about the calls like this:

<body>
<h2>Restful SIP service Sample Application</h2>
<hr/>
<h3>[Call Manager]</h3>
<form>
<table border=4 id="table">
<thead>
<tr>
<td></td>
<td>CallFrom</td>
<td>CallTo</td>
<td>CallStartTime</td>
</tr>
</thead>
<tbody>
<% Iterator i =((ArrayList)request.getAttribute("CallInfo")).iterator();
while(i.hasNext()){ 
	CallInfo cInfo = (CallInfo)i.next();%>
<tr>
<td>
<input type="checkbox" name="CallTerm" value="<%= cInfo.getAccessPointURL()%>"/>
</td>
<td><%= cInfo.getCallFrom().replaceAll("<","<")%></td>
<td><%= cInfo.getCallTo().replaceAll("<","<")%></td>
<td><%= cInfo.getCallStartTime().toString()%></td>
</tr>
<%} %>
</tbody>
</table>
<input type="button" value="terminate" onClick="cterm(this.form)"/>
</form>
</body>

(B) If the request is for subsequent asynchronous access, the URL is to http://[IP address]:[Port#]/sample/calls;async. There, MngCallsServlet creates an XML response from information in the messages:

PrintWriter out = response.getWriter();
response.setContentType("text/xml");
Iterator i = cInfos.iterator();
CallInfo cInfo = null;
out.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
out.println("<calls>");
while(i.hasNext()){ 
	cInfo = (CallInfo)i.next();
	out.println("<call>");
			out.println("<from>"+cInfo.getCallFrom().replaceAll("<","<")+"</from>");
			out.println("<to>"+cInfo.getCallTo().replaceAll("<","<")+"</to>");
			out.println("<time>"+cInfo.getCallStartTime().toString()+"</time>");
		out.println("<url>"+cInfo.getAccessPointURL()+"</url>");
	out.println("</call>");
}
out.println("</calls>");
out.flush();

These XML responses are handled by the JavaScript™ function in the browser and made into an HTML table. The resulting page in the browser looks like this Figure 6.

Figure 6. Sample application results page
Sample application results page

The asynchronous request from this page is made like this:

var url="http://<%= in.getHostAddress()%>:9080/sample/calls;diff";

function func(){
	 var req = false;
	if(window.XMLHttpRequest && !(window.ActiveXObject)){
		.
		req = new XMLHttpRequest;
		.
		.
		.
	}

	if(req){
		req.onreadystatechange = function()
		{
			if(req.readyState == 4){
				if(req.status ==200){
					var reXML = req.responseXML;
					processReqChange(reXML);
				}
			}
		}
		req.open("GET", url+'&t='+new Date(),true);
		req.send(null);
		}
}

The response this page receives from MngCallsServlet for the request above is processed and made into an HTML table like this:

function processReqChange(res){
			var calls = res.getElementsByTagName("call");
			var tbl = document.getElementById("table");
			var disp = tbl.getElementsByTagName("tbody")[0];
			for(var i=0;i<calls.length;i++)
			{
			
			var tr = document.createElement("tr");
			.
			.
			.
			disp.appendChild(tr);
			}
}
onload = function(){setInterval("func()",5000);}
-->
</script>
</html><input type="button" value="terminate" onClick="cterm(this.form)"/>
</form>
</body>

4. Terminate the call from the HTTP servlet

Once the page above is loaded, you (as the user) can select the checkboxes for the calls you want to terminate and click the Terminate button. The JavaScript function below is triggered to create DELETE requests to MngCallsServlet, utilizing the encoded URI that was published before. This request could be of a different type, such as GET or POST, but DELETE is shown here for variety:

function cterm(form){
	for(var i=0;i<form.elements.length-1;i++)
	{
		if(form.elements[i].checked)
		{
		var req = false;
		if(window.XMLHttpRequest && !(window.ActiveXObject)){
			.
			.
			.
		}else if(window.ActiveXObject){
			.
			.
			.
		}

		if(req){
			url_for_delete = form.elements[i].value;
			req.onreadystatechange = function()
			{
			}
			req.open("DELETE", url_for_delete,true);
			req.send(null);
		}
		}
	}
}

The container receives those requests and triggers MngCallsServlet's doDelete method. In that method, the HttpSession is fetched from the request parameter using the getSession method. The HttpSession is then cast to an IBMSession. From that, the IBMApplicationSession is fetched with the getIBMApplicationSession() method on IBMSession. The IBMApplicationSession is then cast to a SipApplicationSession. Finally, the application can fetch the SipSession objects from the SipApplicationSession's getSessions method and pass in "SIP" as the parameter. In this sample application, each call is established using a B2BUA type SIP servlet, so each SipApplicationSession instance should have two SipSession instances. For each of these instances, the application creates a BYE request to terminate the call. To do this, the createRequest method on the SipSession is called with "BYE" as the parameter:

IBMSession isess = (IBMSession)request.getSession();
IBMApplicationSession iasess = isess.getIBMApplicationSession();
Iterator i = ((SipApplicationSession)iasess).getSessions("SIP");
SipSession ss = null;
SipServletRequest bye = null;
while(i.hasNext()){
	ss = (SipSession)i.next();
	bye = ss.createRequest("BYE");
	bye.setAttribute("FromHTTP", true);
	bye.send();
	ss.invalidate();
}

Conclusion

To expose services that will utilize an existing SIP session, one of the best design patterns is through a REST-based service. With the converged container in WebSphere Application Server V6.1, the development of a REST-based service that interacts with SIP is fairly easy to develop and execute, and with the proxy server in WebSphere Application Server V6.1, this REST-based service can be made highly available, and solve many of the issues that exist in this environment.


Download

DescriptionNameSize
Sample applicationConvergedSample202.sar78 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=197994
ArticleTitle=IBM WebSphere Developer Technical Journal: Developing RESTful SIP services for a high availability environment
publish-date=02282007