The SIP Servlet Tutorial

Chapter 2 Simple SIP Servlet Examples

This chapter describes several of the simple SIP servlet examples that are included with Communications Server.

Prerequisites for Running the Examples

    You should have done the following before you can run the examples:

  1. Downloaded and installed the example bundle as described in Sample Applications.

  2. Installed NetBeans IDE as described in NetBeans IDE.

  3. Downloaded and installed the SIP NetBeans IDE modules, including the SIP Test Agent module as described in SIP Modules for NetBeans IDE.

The SipProxy Example

This example is a simple SIP proxy servlet. The proxy servlet will forward all SIP messages from the caller client to the callee server.

Developing the SIP Servlet

The SIP servlet is called SimpleProxyServlet, and extends the base SipServlet class and implements the SipErrorListener and Servlet interfaces.

@SipListener
@SipServlet
public class SimpleProxyServlet 
            extends SipServlet 
            implements SipErrorListener,Servlet {
    
    /** Creates a new instance of SimpleProxyServlet */
    public SimpleProxyServlet() {
    }
    
    
    protected void doInvite(SipServletRequest request) 
        	throws ServletException, IOException {
    	 
			if (request.isInitial()) {
				Proxy proxy = request.getProxy();
				proxy.setRecordRoute(true);
				proxy.setSupervised(true);
				proxy.proxyTo(request.getRequestURI()); // bobs uri
			}
			System.out.println("SimpleProxyServlet: Got request:\n" + request);
		}
    
    protected void doBye(SipServletRequest request) throws 
				ServletException, IOException {
        
        System.out.println("SimpleProxyServlet: Got BYE request:\n" + request);
        super.doBye(request);
    }
    
    
    protected void doResponse(SipServletResponse response) 
        throws ServletException, IOException {
        
        System.out.println("SimpleProxyServlet: Got response:\n" + response);
			super.doResponse(response);
    }
    
    // SipErrorListener
    
    public void noAckReceived(SipErrorEvent ee) {
        System.out.println("SimpleProxyServlet: Error: noAckReceived.");
    }
    
    public void noPrackReceived(SipErrorEvent ee) {
			System.out.println("SimpleProxyServlet: Error: noPrackReceived.");
    }
    
}

SIP Methods

In SimpleProxyServlet, you override several methods to respond to the main SIP methods.

SipErrorListener Methods

Because SimpleProxyServlet implements the SipErrorListener interface, it must implement the following methods:

Deploying and Running SipProxy

Follow these instructions to deploy and run the example.

ProcedureDeploying and Running SipProxy in NetBeans IDE

  1. Click File->Open Project and navigate to the location where you downloaded and expanded the SimpeProxy example.

  2. Select SipProxy and click Open Project.

  3. Right-click on SipProxy in the Projects pane and click Run.

ProcedureTesting SipProxy with the SIPp Application

Before You Begin

Be sure you have installed the SIPp test application, as described in SIPp Test Application.

  1. In a terminal, enter the following command to start the SIPp server on port 5090:

    % sipp -sn uas -p 5090
  2. In a new terminal enter the following command to start the SIPp client on port 5080:

    % sipp -sn uac -rsa 127.0.0.1:5060 -p 5080 127.0.0.1:5090

    You should now see the messages from the client get returned by the server, with the SipProxy application acting as a proxy between them.

The Click-To-Dial Example

The Click-To-Dial example demonstrates how to integrate a SIP servlet with a web application by allowing users to place calls to other users by using an HTTP servlet. The example demonstrates how SIP registration and invitation works, and how to share data between SIP servlets and HTTP servlets.

Architecture of the Click-To-Dial Example

The Click-To-Dial application allows users to call each other after registering their information using a web application. The example consists of two SIP servlets (RegistrarServlet and CallSipServlet) and two HTTP servlets (LoginServlet and PlaceCallServlet). The user data is stored in a database using the Java Persistence API.

The following scenario shows the procedure for using the Click-To-Dial example:

  1. Users Alice and Bob login to the web application, using the LoginServlet HTTP servlet.

  2. Alice and Bob register their SIP soft-phone with the web application. Registration is handled by the RegistrarServlet SIP servlet, which stores registration data in a database using the Java Persistence API.

  3. Alice clicks on Bob's Call link from the web application to start a phone call to Bob. The PlaceCallServletHTTP servlet passes the data to CallSipServlet in order to initiate the connection.

  4. Alice's phone rings.

  5. When Alice picks up her phone, a call is placed to Bob's phone, and Bob's phone rings.

  6. When Bob picks up his phone, the connection is established, and Alice and Bob can have a conversation.

  7. When Alice or Bob hangs up, the connection is terminated, and they are able to receive calls again.

Click-To-Dial's SIP Servlets

The SIP functionality in Click-To-Dial is split into two separate SIP servlets, RegistrarServlet and CallSipServlet.

SIP Application Annotations in CllickToDial

A @SipApplication annotation is used in ClickToDial to define a set of SIP servlets used together to provide SIP functionality. The @SipApplication annotation is set at the package level by putting it in the package-info.java file in the clicktodial.sip package.


Example 2–1 Package-level @SipApplicaton Annotation in ClickToDial

@javax.servlet.sip.annotation.SipApplication(
    name="ClickToDial",
    mainServlet="RegistrarServlet")
package clicktodial.sip;

The @SipApplication annotation sets two elements: the name of the application, and the main servlet. The name element is required, and is set to the application name. The optional mainServlet element defines which SIP servlet will initially respond to SIP requests. In this case, the RegistrarServlet, which registers SIP clients so they can be later contacted for calls, is the main servlet for ClickToDial.

The RegistrarServlet

The RegistrarServlet allows users to register soft-phones with the application, and stores the user's data in a database using the Java Persistence API.

RegistrarServlet has three methods: doRegister, handleRegister, and handleUnregister.

The doRegister method responds to REGISTER messages and performs some checks on the incoming request, extracts the user name from the request, looks the user up in the database of users, and examines the EXPIRES header of the request to determine whether the request is a registration or unregistration request. If it is a registration request, the handleRegister private helper method is called. If it is an unregistration request, the handleUnregister private helper method is called. These methods will return a SIP response to send back to the client.


Example 2–2 The doResponse Method

@Override
protected void doRegister(SipServletRequest req) 
        	throws ServletException, IOException {
        logger.info("Received register request: " + req.getTo());
        
        int response = SipServletResponse.SC_SERVER_INTERNAL_ERROR;
        ModelFacade mf = (ModelFacade) getServletContext().getAttribute("Model");
        
        // Figure out the name the user is registering with.  This is the
        // user portion of the SIP URI, e.g. "Bob" in "sip:Bob@x.y.z:port"
        String username = null;
        if (req.getTo().getURI().isSipURI()) {
            username = ((SipURI) req.getTo().getURI()).getUser();
        }
        
        // get the Person object from the database
        Person p = mf.getPerson(username);
        if (p != null) {
            // the Expires header tells us if this is a registration or
            // unregistration attempt.  An expires value of 0 or no Expires
            // header means it is an unregistration.
            int expires = 0;
            String expStr = req.getHeader("Expires");
            if (expStr != null) {
                expires = Integer.parseInt(expStr);
            }
            
            if (expires == 0) {
                // unregister
                response = handleUnregister(req, p);
            } else {
                // register
                response = handleRegister(req, p);
            }
        } else {
            // no person found in the database
            response = SipServletResponse.SC_NOT_FOUND;
        }
    
        // send the response
        SipServletResponse resp = req.createResponse(response);
        resp.send();
    }

The handleRegister method extracts the user's SIP address from the request, stores it in the user database, and returns a SIP OK response. The user can now place and receive calls.


Example 2–3 The handleRegister Method

private int handleRegister(SipServletRequest req, Person p)
        throws ServletException {

        // Get the contact address from the request.  Prefer the
        // "Contact" address if given, otherwise use the "To" address
        Address addr = req.getTo();
        String contact = req.getHeader("Contact");
        if (contact != null) {
            addr = sf.createAddress(contact);
        }
        
        logger.info("Register address: " + addr);
        
        // store the contact address in the database
        p.setTelephone(addr.getURI().toString());
        
        ModelFacade mf = (ModelFacade) getServletContext().getAttribute("Model");
        mf.updatePerson(p);
        
        return SipServletResponse.SC_OK;
    }

The handleUnregister method removes the user's SIP address from the database by setting it to null, then sends a SIP OK response back. The user cannot place or receive calls after being unregistered.


Example 2–4 The handleUnregister Method

private int handleUnregister(SipServletRequest req, Person p) {
        // store the contact address in the database
        p.setTelephone(null);
        
        ModelFacade mf = (ModelFacade) getServletContext().getAttribute("Model");
        mf.updatePerson(p);
        
        return SipServletResponse.SC_OK;
    }

The CallSipServlet

The CallSipServlet SIP servlet connects registered SIP users to one another, allowing users to place calls to one another. There are 5 main SIP methods in CallSipServlet: doSuccessResponse, sendInviteToClient, sendAckToClient, sendAckToServer, and sent200OKToClient.

CallSipServlet is annotated at the class-level with a @SipServlet and @SipListener annotation.

@javax.servlet.sip.annotation.SipServlet
@SipListener
public class CallSipServlet extends SipServlet implements SipSessionListener {
    ...
}

The doSuccessResponse method connects a call between two registered users. When the first user Alice initiates a call to the second user Bob, first Alice's phone rings. If Alice answers her phone, a SIP OK message is sent. At that point, Bob's address is extracted from the request, a SIP INVITE message is sent to Bob's address by calling the sendInviteToClient private method, and Bob's phone rings. If Bob answers the phone, a SIP OK message is sent. The two SIP sessions, from Alice and Bob respectively, are linked, and a SIP ACK message is sent to both user's phones by calling the sendAckToClient and sendAckToServer private methods. Alice and Bob are now connected and can have a conversation. When the call is terminated, a BYE message is sent from the server, and the send200OKToClient private method is called.


Example 2–5 The doSuccessResponse Method

@Override
protected void doSuccessResponse(SipServletResponse resp)
        throws ServletException, IOException {
    logger.info("Received a response.\n" + resp);

    if (resp.getMethod().equals("INVITE")) {
        List<SipSession> sipSessions = getSipSessions(resp.getApplicationSession());
        if (sipSessions.size() == 1) {
            sipSessions.get(0).setAttribute("ACK", resp.createAck());
            sendInviteToClient(resp);
        } else { // 200 OK from Client
            sendAckToClient(resp);
            sendAckToServer(resp);
        }
    } else if (resp.getMethod().equals("BYE")) {
        send200OKToClient(resp);
    }
}


Example 2–6 The sendInviteToClient Method

private void sendInviteToClient(SipServletResponse serverResp)
        throws ServletException, IOException {
    SipServletRequest serverReq = serverResp.getRequest();
    B2buaHelper b2buaHelper = serverReq.getB2buaHelper();

    // Swap To & From headers.
    Map<String, List<String>> headerMap = new HashMap<String, List<String>>();
    List<String> from = new ArrayList<String>();
    from.add(serverResp.getHeader("From"));
    headerMap.put("To", from);
    List<String> to = new ArrayList<String>();
    to.add(serverResp.getHeader("To"));
    headerMap.put("From", to);

    SipServletRequest clientRequest = b2buaHelper
				.createRequest(serverReq, true, headerMap);
    clientRequest.setRequestURI(clientRequest.getAddressHeader("To").getURI());
    if (serverResp.getContent() != null) { // set sdp1
        clientRequest.setContent(serverResp.getContent(),
               serverResp.getContentType());
    }
    logger.info("Sending INVITE to client.\n" + clientRequest);
    clientRequest.send();
}


Example 2–7 The sendAckToClient Method

private void sendAckToClient(SipServletResponse clientResp) 
        throws ServletException, IOException {
    SipServletRequest ack = clientResp.createAck();
    logger.info("Sending ACK to client.\n" + ack);
    ack.send();
}


Example 2–8 The sendAckToServer Method

private void sendAckToServer(SipServletResponse clientResp) 
        throws ServletException, IOException {
    B2buaHelper b2buaHelper = clientResp.getRequest().getB2buaHelper();
    SipSession clientSession = clientResp.getSession();
    SipSession serverSession = b2buaHelper.getLinkedSession(clientSession);
    SipServletRequest ack = (SipServletRequest) serverSession.getAttribute("ACK");
    serverSession.removeAttribute("ACK");
    if (clientResp.getContent() != null) { // set sdp2
        ack.setContent(clientResp.getContent(), clientResp.getContentType());
    }
    logger.info("Sending ACK to server.\n" + ack);
    ack.send();
}


Example 2–9 The send200OKToClient Method

    protected void doBye(SipServletRequest request)
        throws ServletException, IOException 
    {
        logger.info("Got bye");
        
        SipSession session = request.getSession();
        
        // end the linked call 
        SipSession linkedSession = (SipSession) session.getAttribute("LinkedSession");
        if (linkedSession != null) {
            // create a BYE request to the linked session
            SipServletRequest bye = linkedSession.createRequest("BYE");
            
            logger.info("Sending bye to " + linkedSession.getRemoteParty());
            
            // send the BYE request
            bye.send();
        }
        
        // send an OK for the BYE
        SipServletResponse ok = request.createResponse(SipServletResponse.SC_OK);
        ok.send();
    }

There are three SIP session listener methods implemented in CallSipServlet, from the SipSessionListener interface: sessionCreated, sessionDestroyed, and sessionReadyToInvalidate. In CallSipServlet, the methods simply log the events.


Example 2–10 SipSessionListener Methods Implemented in CallSipServlet

public void sessionCreated(SipSessionEvent sse) {
    logger.info("Session created");
}

public void sessionDestroyed(SipSessionEvent sse) {
    logger.info("Session destroyed");
}
    
public void sessionReadyToInvalidate(SipSessionEvent sse) {
    logger.info("Session ready to be invalidated");
}

Running the Click-To-Dial Example

This section describes how to deploy and run the Click-To-Dial Example in NetBeans IDE.

ProcedureDeploying and Running Click-To-Dial in NetBeans IDE

  1. In NetBeans IDE, click Open Project and navigate to sip-tutorial/examples/ClickToDial.

  2. Right-click on the ClickToDial project and select Run.

    This will open a browser to http://localhost:8080/ClickToDial.

ProcedureRegistering Alice's SIP Phone

  1. In your web browser select Alice from the drop-down menu and click Login.

  2. In X-Lite right-click on the phone and select SIP Account Settings.

  3. Click Add.

  4. Enter Alice under Display Name, User Name, and Authorization User Name.

  5. Enter test.com under Domain.

  6. Check Register With Domain and Receive Incoming Calls.

  7. Under Send Outbound Via select Proxy and enter Communications Server IP address:5060. For example, 192.168.0.2:5060.

  8. Click Ok.

ProcedureRegistering Bob's SIP Phone

  1. On a different machine in your web browser go to http://Communications Server IP Address:8080/ClickToDial. For example, http://192.168.0.2:8080/ClickToDial.

  2. Select Bob from the drop-down menu and click Login.

  3. In the second machine's X-Lite right-click on the phone and select SIP Account Settings.

  4. Click Add.

  5. Enter Bob under Display Name, User Name, and Authorization User Name.

  6. Enter test.com under Domain.

  7. Check Register With Domain and Receive Incoming Calls.

  8. Under Send Outbound Via select Proxy and enter Communications Server IP address:5060. For example, 192.168.0.2:5060.

  9. Click Ok.

ProcedurePlacing a Call From Alice To Bob

  1. On Alice's machine, refresh the web browser to see that both Alice and Bob are registered.

  2. Click Call next to Bob's SIP address to place a call to Bob.

  3. In X-Lite click Answer to place the call to Bob.

    X-Lite will initiate a call to Bob's X-Lite instance using Communications Server as a proxy.

  4. On Bob's machine, click Answer to receive the call from Alice.

    Alice and Bob are now connected and may talk.