The SIP Servlet Tutorial

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 Application Server 1.5 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 Application Server 1.5 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 Application Server 1.5 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 Application Server 1.5 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.