Converged Http Sessions in SailFin

Converged Http Sessions in SailFin

Converged Http Sessions in SailFin

Introduction

ConvergedHttpSession is a new feature introduced by JSR 289, which provides access to SIP protocol sessions from HTTP and vice versa. This enables the creation of unified, cross-protocol communication services, where SIP calls may be initiated and terminated from a web browser.

A popular example of such a use case is the Click-to-dial application (see below), which enables a client to initiate a SIP call by simply clicking on a URL link.

A ConvergedHttpSession implements the javax.servlet.sip.ConvergedHttpSession interface, which in turn extends the javax.servlet.http.HttpSession interface defined by the Servlet specification.

This blog explains the motivation behind a converged application, provides a step-by-step analysis of how a ConvergedHttpSession may be used to initiate and terminate a SIP call from the HTTP servlet of a converged application, explains how the routing decisions of SailFin's Converged Loadbalancer influence the session id of a SipApplicationSession created from a ConvergedHttpSession, and finally shows how a ConvergedHttpSession fits into SailFin's in-memory replication framework that provides high-availability of HTTP and SIP artifacts in a SailFin cluster.

Converged Applications

A converged application combines SIP protocol functionality with other protocols (generally HTTP) to provide a unified communication service. It bundles both sip.xml and web.xml deployment descriptors for its SIP and HTTP servlet components, respectively.

In a JSR 289 compliant container, such as SailFin, HTTP sessions are exposed to HTTP servlets either as instances of javax.servlet.http.HttpSession or javax.servlet.sip.ConvergedHttpSession, depending on the type of application: In the case of a pure web application (i.e., one that has a web.xml, but no sip.xml), HTTP sessions are exposed as instances of javax.servlet.http.HttpSession, whereas in the case of a converged application, they are exposed as instances of javax.servlet.sip.ConvergedHttpSession.

This means that developers of HTTP servlets that are part of a converged application may cast the result of a call to HttpServletRequest.getSession() to javax.servlet.sip.ConvergedHttpSession, as shown in the following code snippet:

  import java.io.IOException;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import javax.servlet.sip.ConvergedHttpSession;

  public class ConvergedServlet extends HttpServlet {

      public void doGet(HttpServletRequest httpReq, HttpServletResponse httpRes)
              throws ServletException, IOException {

          ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession();
          [...]
      }

      [...]
  }

If invoked from the HTTP servlet of a pure web application, the above cast to ConvergedHttpSession would result in a ClassCastException.

Through its ConvergedHttpSession interface, an HttpSession may be associated with (that is, added as a child protocol session to) a javax.servlet.sip.SipApplicationSession. Once this type of association has occurred, the ConvergedHttpSession interface allows navigation from the HttpSession to its SipApplicationSession parent and SipSession siblings, as shown in the following code snippet:

  import javax.servlet.sip.ConvergedHttpSession;
  import javax.servlet.sip.SipApplicationSession;
  import javax.servlet.sip.SipSession;

  ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession();
  SipApplicationSession sas = chs.getApplicationSession();
  Iterator sipSessions = sas.getSessions("SIP");

Click-To-Dial Use Case

The following example assumes a converged application with one HTTP servlet (ClickToDialServlet) and one SIP servlet (CallServlet). The example focuses on the HTTP servlet, because it is more relevant in terms of the use of ConvergedHttpSession.

The ClickToDialServlet allows to initiate and terminate a SIP call from sip:foo@<localIp>:<localSipPort> to sip:bar@<remoteIp>:<remoteSipPort>, where remoteIp and remoteSipPort are passed in as HTTP request parameters. It either initiates or terminates a SIP call, based on the value of the action request parameter:

  @Resource(mappedName = "sip/convergedapp")
  private SipFactory sipFactory;

  public class ClickToDialServlet extends HttpServlet {

      public void doGet(HttpServletRequest httpReq, HttpServletResponse httpRes)
            throws ServletException, IOException {

          // Initiate or terminate a SIP call, depending on the value of the
          // "action" request parameter
          String action = httpReq.getParameter("action");
          if (action.equals("call")) {
              initiateSipCall(httpReq, httpRes);
          } else if (action.equals("bye")) {
              terminateSipCall(httpReq, httpRes);
          } else {
              throw new ServletException("Invalid action");
          }
      }

      [...]
  }

The initiateSipCall and terminateSipCall methods of the ClickToDialServlet are implemented as follows:

  /\*\*
   \* Initiates a SIP call
   \*/
  private void initiateSipCall(HttpServletRequest httpReq, HttpServletResponse httpRes) throws ServletException, IOException {

      // Create a new ConvergedHttpSession
      ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession();

      // Create a new SipApplicationSession, and link the ConvergedHttpSession to it
      SipApplicationSession sas = chs.getApplicationSession();

      // Create the SIP URIs that will be included in the To and From fields of the SIP INVITE request.
      // The SipFactory to create the SIP URIs has been injected into this servlet, using the @Resource annotation (see above)
      String localIp = InetAddress.getLocalHost().getAddress().toString();
      Address fromAddr = sipFactory.createAddress("sip:foo@" + localIp + ":" + System.getProperty("SIP_PORT"));
      Address toAddr = sipFactory.createAddress("sip:bar@" + httpReq.getParameter("remoteIp") + ":" + httpReq.getParameter("remotePort"));

      // Create the SIP INVITE request using the SipFactory and the toAddr and fromAddr addresses
      SipServletRequest sipReq = sipFactory.createRequest(sas, "INVITE", fromAddr, toAddr);

      // Create a SipSession
      SipSession sipSession = sipReq.getSession();

      // Have any requests and responses related to the new SipSession be handled by the converged application's CallServlet
      sipSession.setHandler("CallServlet");

      // Store the id of the SipSession as a session attribute with name "CallId" in the parent SipApplicationSession,
      // so that this SipSession may later be found and used to terminate the call
      sas.setAttribute("CallId", sipSession.getId());

      // Send the request
      sipReq.send();

      // Return a web form that may be used to terminate the call
      PrintWriter out = httpRes.getWriter();
      out.println("<form action=\\"ClickToDialServlet\\" method=\\"GET\\">");
      out.println("<input type=\\"hidden\\" name=\\"action\\" value=\\"bye\\"/>");
      out.println("<input type=\\"submit\\" value=\\"Terminate call\\"/>");
      out.println("</form>");
  }

  /\*\*
   \* Terminates a SIP call.
   \*
   \* This method is invoked when the FORM returned by the initiateSipCall
   \* method of this servlet is submitted.
   \*/
  private void terminateSipCall(HttpServletRequest httpReq, HttpServletResponse httpRes) throws ServletException, IOException {

      // Resume the ConvergedHttpSession, which is identified by the JSESSIONID cookie sent with the request
      ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession(false);

      // Get the SipApplicationSession parent
      SipApplicationSession sas = chs.getApplicationSession();

      // Get the SipSession whose id had been stored as a session attribute with name "CallId" in the parent SipApplicationSession
      SipSession sipSession = sas.getSipSession((String) sas.getAttribute("CallId"));

      // Send a BYE to the UAS to terminate the call
      sipSession.createRequest("BYE").send();
  }

Colocation Requirements

Calling getApplicationSession() on a ConvergedHttpSession that has not yet been associated with any SipApplicationSession will create a new SipApplicationSession and make it the parent of the ConvergedHttpSession, unless the HTTP request URL that was used to create or resume the ConvergedHttpSession contains a parameter with name sipappsessionid, in which case the corresponding SipApplicationSession will be looked up and made the parent of the ConvergedHttpSession.

If ConvergedHttpSession.getApplicationSession() ends up creating a SipApplicationSession in a SailFin cluster, and the cluster is load-balanced by the Converged Loadbalancer (CLB) with its consistent-hash load-balancing algorithm enabled, special precautions are taken by SailFin's converged container as it forms the id of the new SipApplicationSession.

In SailFin, the id of a SipApplicationSession consists of three components:

  • a key component, which is used to determine the instance where (according to the consistent hash algorithm) the SipApplicationSession belongs,
  • an appName, which identifies the application to which the SipApplicationSession belongs, and
  • a random uuid.

In its consistent-hash mode, the CLB computes a hashkey from the header values of an initial request, based on the rules specified in the Data Centric Rules (DCR) configuration file. This hashkey is used to look up, with the help of a consistent hash function, the cluster instance that is supposed to service the request, and the CLB then forwards the request to this instance. The hashkey, which is computed only once per initial request, is included in the response in the form of a BEKey.

For example, in the case of a 200 OK SIP response, the BEKey is included in the Contact header, and in the case of a SIP response with a status code greater than or equal to 300, it is included in the To header. In the case of a HTTP response, the BEKey is returned as a cookie, or encoded in the URL if cookies have been disabled.

The presence of a BEKey in subsequent requests ensures that these requests will be routed by the CLB to the same instance that processed the initial request, unless that instance has gone down in the meantime, in which case the BEKey will have been remapped to a different instance.

See the CLB Functional Specification and blog for details.

For example, the submission of the FORM returned by the initiateSipCall method of the ClickToDialServlet in the above example may result in the following HTTP request:

  GET /convergedapp/ClickToDialServlet?action=bye HTTP/1.1
  Host: [...]
  Cookie: BEKey=hashkey; JSESSIONIDVERSION=/convergedapp:0; JSESSIONID=0045773156aee0763caf8862c242
  [...]

Notice how the BEKey is carried in the request's Cookie header.

When the CLB intercepts and proxies an initial or subsequent HTTP request, it adds the BEKey as a header with name proxy-bekey to the HTTP request.

If a call to ConvergedHttpSession.getApplicationSession() ends up creating a new SipApplicationSession, SailFin's converged container checks the HTTP request for the proxy-bekey header, and if present, assigns its value to the key component of the id of the newly generated SipApplicationSession.

This ensures that the instance to which a request that resumes a ConvergedHttpSession is routed by the CLB, and the instance on which the ConvergedHttpSession's associated SipApplicationSession belongs (according to the consistent hash algorithm), always match.

High-Availability

SailFin provides high-availability of SIP and HTTP session artifacts in a cluster of SailFin instances, by leveraging and extending the in-memory replication feature that was first introduced in GlassFish v2. SailFin's high-availability feature, which is also known under the acronym SSR (Sip Session Retainability), will be covered in a separate blog.

Once a ConvergedHttpSession has been associated with a SipApplicationSession, the session id of the SipApplicationSession is stored as an instance field in the ConvergedHttpSession and becomes part of that session's serialized representation. The fact that an HTTP session being replicated may really be an instance of javax.servlet.sip.ConvergedHttpSession remains hidden from the in-memory replication framework.

Should a ConvergedHttpSession become separated from its parent SipApplicationSession in a cluster after a failover, it will be possible for the ConvergedHttpSession to retrieve its parent SipApplicationSession, based on its id.

Comments:

Hi Jan,

Interesting post!

I think you no longer need to provide the mappedName when injecting the SipFactory, i.e. the following would suffice:

@Resource
private SipFactory sipFactory;

cheers
Yvo

Posted by Yvo on April 07, 2008 at 06:01 PM PDT #

[Trackback] JSR 289 went final just about a month back. And we have the first implementation of JSR 289 at alpha stage available now. SailFin V1 Alpha has been released.

Posted by Binod's Blog on August 13, 2008 at 05:45 AM PDT #

Hello,

I'm interessing by your article.

I just want to know where can i found the SIP servlet "CallServlet" ?!?

I don't see a link in the page.

Sincerely

Posted by Thomas on November 18, 2008 at 09:31 PM PST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

jluehe

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today