An Introduction to SIP, Part 2: SIP Servlets

by Emmanuel Proulx
02/28/2006

The Server Code 

The code for the server will look very familiar if you have developed HTTP servlets in the past. If you don't know servlets, you should first familiarize yourself with them. The SIP Servlet Specification is an extension of the HTTP Servlet Specification. The syntax, the container's behavior, and even the method names are similar.

I'll now dissect the example in question. It is roughly composed of three sections:

1. Lifecycle methods

These methods are called by the container when the servlet is started up or is shut down:

public class ChatRoomServer extends SipServlet {

/** Context attribute key to store user list. */
public static String THE_LIST="dev2dev.chatroomserver.userList";

/** Init parameter key to retrieve the chat room's address. */
public static String THE_NAME="dev2dev.chatroomserver.name";

/** This chat room server's address, retrieved from the init params. */
public String serverAddress;

/** This is called by the container when starting up the service. */
public void init() throws ServletException {
    super.init();
    getServletContext().setAttribute(THE_LIST,new ArrayList());
    serverAddress = getServletConfig().getInitParameter(THE_NAME);
}

/** This is called by the container when shutting down the service. */
public void destroy() {
    try
    {
        sendToAll(serverAddress, "Server is shutting down -- goodbye!");
    } catch (Throwable e)
    { //ignore all errors when shutting down.
        e.printStackTrace();
    }
    super.destroy();
}
...

In the initialization method I create a global (context-scope) attribute that all sessions share. This is the list of users. I also retrieve the address of this chat room (servlet parameter) for future use.

2. Message-processing methods

SIP servlets differ slightly from HTTP servlets. With HTTP servlets, you process incoming requests and send out response messages. With SIP servlets, you can both send and receive requests and responses. I'll show you how to do this.

The following methods are called by the container when messages (requests and responses) are received. They are called by the container following the path on this chart, and you can override them to process the messages according to their type:

void service(ServletRequest, ServletResponse)

If you override, don't forget to call super.service().

The default implementation calls one of the following methods:

void doRequest(SipServletRequest)

If you override, don't forget to call super.doRequest().

The default implementation calls one of the following methods:

void doResponse(SipServletResponse)

If you override, don't forget to call super.doResponse().

The default implementation calls one of the following methods:

One of these request methods (self-explanatory):

  • doAck(SipServletRequest)
  • doBye(SipServletRequest)
  • doCancel(SipServletRequest)
  • doInfo(SipServletRequest)
  • doInvite(SipServletRequest)
  • doMessage(SipServletRequest)
  • doNotify(SipServletRequest)
  • doOptions(SipServletRequest)
  • doPrack(SipServletRequest)
  • doRegister(SipServletRequest)
  • doRequest(SipServletRequest)
  • doResponse(SipServletResponse)
  • doSubscribe(SipServletRequest)

One of these response methods:

  • doProvisionalResponse(SipServletResponse)—Corresponds to the 1xx-class response messages.
  • doSuccessResponse(SipServletResponse)—Corresponds to the 2xx-class response messages.
  • doRedirectResponse(SipServletResponse)—Corresponds to the 3xx-class response messages.
  • doErrorResponse(SipServletResponse)—Corresponds to the 4xx-, 5xx-, and 6xx-class response messages.

As an example, a MESSAGE message would hit the following methods:

  1. service(), passing in a SipServletRequest (you must do a cast), and null
  2. doRequest()
  3. doMessage()

Usually, only methods on the last level are overridden, unless non-standard SIP messages are involved, or you want to gather statistics about messages.

Now, for the code that processes our instant messages:

/** This is called by the container when a MESSAGE message arrives. */
protected void doMessage(SipServletRequest request) throws
        ServletException, IOException {

    request.createResponse(SipServletResponse.SC_OK).send();

    String message = request.getContent().toString();
    String from = request.getFrom().toString();

    //A user asked to quit.
    if(message.equalsIgnoreCase("/quit")) {
        sendToUser(from, "Bye");
        removeUser(from);
        return;
    }

    //Add user to the list
    if(!containsUser(from)) {
        sendToUser(from, "Welcome to chatroom " + serverAddress +
                ". Type '/quit' to exit.");
        addUser(from);
    }

    //If the user is joining the chat room silently, no message
    //to broadcast, return.
    if(message.equalsIgnoreCase("/join")) {
        return;
    }

    //We could implement more IRC commands here,
    //see http://www.mirc.com/cmds.html
    sendToAll(from, message);
}

/**
 * This is called by the container when an error is received
 * regarding a sent message, including timeouts.
 */
protected void doErrorResponse(SipServletResponse response)
        throws ServletException, IOException {
    super.doErrorResponse(response);
    //The receiver of the message probably dropped off. Remove
    //the receiver from the list.
    String receiver = response.getTo().toString();
    removeUser(receiver);
}

/**
 * This is called by the container when a 2xx-OK message is
 * received regarding a sent message.
 */
protected void doSuccessResponse(SipServletResponse response)
        throws ServletException, IOException {
    super.doSuccessResponse(response);
    //We created the app session, we have to destroy it too.
    response.getApplicationSession().invalidate();
}

The first method is called when a MESSAGE message arrives. It first replies with a 200 OK message to indicate that it accepts the message. Then it processes server commands like /join. Finally, it calls a business logic method to broadcast the incoming message.

An incoming error-response message indicates that the last request failed. This may mean that one of the users got disconnected. Simply remove that user from the list.

A success response message means the last MESSAGE message was accepted properly by the instant messenger. We no longer need the session so we discard it. Typically, MESSAGE messages are sent statelessly—connection information isn't kept between messages. (This isn't the case for an INVITE message, which opens a stateful session until a BYE is sent.)