In “SIP Servlet Programming: Basic“, I showed you how to write a simple SIP Servlet application. In this blog, I will show you a more realistic application –a simple SIP Registrar and Proxy server .
SIP Registration and Proxy
SIP Registrar and Proxy servers play a central role in routing SIP calls among SIP phones.

As illustrated in the above diagram, a SIP phone registers to Registrar with its current address. Registration essentially establishes a binding between SIP phone’s logical address (e.g. sip:wchen@voxeo.com) to its physical address (e.g. sip:wchen@192.168.1.105). The bindings are stored in a Location Service. When john@voxeo.com calls wchen@voxeo.com, Proxy looks up the Location Service to find the physical address of wchen@voxeo.com, where Proxy forwards the call. Please note Location Service here is a logical concept and is not necessarily a separated software entity.
Application Overview
The application includes two SIP servlets, one for SIP Registrar and one for SIP Proxy. Here is the application package layout.
|-- WEB-INF
| |-- classes (where all the java classes are)
| | |-- com
| | |-- voxeo
| | |-- prism
| | |-- tutorial
| | |-- two
| | |-- ProxyServer.class
| | |-- Registrar.class
| | |-- Binding.class
| | |-- LocationService.class
| | |-- IllegalRegistrationException.class
| |-- lib (where all the library JARs are, if any)
| |-- sip.xml (SIP deployment descriptor XML)
| |-- web.xml (HTTP deployment descriptor XML)
| |-- xmpp.xml (optional XMPP deployment descriptor XML)
| |-- sipmethod-application.xml (optional Prism deployment descriptor XML)
|-- index.jsp (document files)
Implement a SIP Registrar
The overall SIP registration behavior is well defined in RFC 3261, section 10. Two of the key designs in implementing a SIP Registrar are the data model for the binding and processing logic for REGISTER message.
Data Model for Binding
A binding is created when a SIP phone registers itself to Registrar by sending a SIP REGISTER request. A binding essentially is entity with the following attributes.
| Address of Record |
Contact URI |
Call ID |
CSeq |
Expiration |
- Address of Record (AOR): the logical address of the SIP phone.
- Contact URI (Contact): the physical address of the SIP phone.
- Call ID: the Call-ID associated with the SIP REGISTER request that creates this binding.
- CSeq: the CSeq number associated with the SIP REGISTER request that creates this binding.
- Expiration: the expiration time for this binding.
AOR and Contact are the composite key for a binding entity.
In this blog, I used a two-level Map to store all the bindings in memory for simplicity. In real world, you might persist the bindings to a relational data store.
Map<URI, Map<URI, Binding>>
| | |
AOR Contact Binding
The first level Map allows look-up of all bindings given an AOR. The second level Map allows look-up a binding given a specific Contact. The Binding class encapsulates the binding entity. The AOR is not encapsulated inside the Binding class since it is already the key in the map.
Registration Processing
RFC 3261, section 10.3 describes 8 steps for processing a SIP REGISTER request. In this blog, I implemented most of the requirements in a Registrar Servlet, as illustrated in the following flow chart.

Here is the corresponding Java code in Registrar servlet.
@Override
protected synchronized void doRegister(SipServletRequest req) throws ServletException, IOException {
// TODO 1: request URI format validation
// TODO 2: domain validation
// TODO 3: Require validation
// TODO 4: Multiple contact headers support
// get the Address-of-Record (AOR) for this REGISTER request.
URI aor = req.getTo().getURI();
// get the Contact for this REGISTER request, assuming there is at most one.
Address contact = req.getAddressHeader("Contact");
// get the bindings map for the AOR, if no bindings map, initialize an empty one.
Map<URI, Binding> bindings = _registrations.get(aor);
if (bindings == null) {
bindings = new HashMap<URI, Binding>();
_registrations.put(aor, bindings);
}
try {
// is there a Contact in this Request? If not, this is a fetch per RFC 3261 10.2.3
if (contact != null) {
// calculating the Expires for this Contact
int expires = contact.getExpires();
if (expires < 0) {
expires = req.getExpires();
}
if (expires < 0) {
expires = DEFAULT_EXPIRES;
}
// is this Contact a wildcard Contact? i.e."*".
if (!contact.isWildcard()) {
String callID = req.getCallId();
int seq = Integer.parseInt(new StringTokenizer(req.getHeader("CSeq").trim()).nextToken());
URI key = contact.getURI();
// is there a binding existed for this URI already?
Binding current = bindings.get(key);
if (current != null) {
// Now we have to compare the Call ID and CSeq of the new Contact and the existed Contact
// per RFC 3261 10.3, step 7
if (!current.getCallID().equals(callID) || current.getSeq() < seq) {
if (expires != 0) {
current.update(callID, seq, expires);
}
else {
bindings.remove(key);
}
}
else {
throw new IllegalRegistrationException("Registration is out of order.");
}
}
else if (expires > 0) {
// since no existing binding, let's add the new binding
bindings.put(key, new Binding(contact, callID, seq, expires));
}
}
else if (expires == 0) {
// Wildcard Contact can only removes the bindings for an AOR, per RFC 3261 10.3, step 6
bindings.clear();
}
else {
throw new IllegalRegistrationException("In violation of RFC 3261 10.2.2.");
}
}
// valid response must enumerate all the current bindings, per RFC 3261 10.3, step 8
SipServletResponse resp = req.createResponse(SipServletResponse.SC_OK);
for (Binding b : bindings.values()) {
Address addr = _factory.createAddress(b.getContact());
addr.setExpires(b.getExpires());
resp.addAddressHeader("Contact", addr, false);
}
resp.send();
}
catch (IllegalRegistrationException e) {
req.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR, e.getMessage()).send();
}
finally {
// REGISTER session should be invalidated since REGISTER request doesn't establish a SIP dialog.
req.getApplicationSession().invalidate();
}
}
Please note, at the end of doRegister() method, the session is invalidated right away. This is because a REGISTER request does not establish a SIP dialog, per RFC 3261, section 10.2.
Implement a SIP Proxy
The overall SIP proxy behavior is well defined in RFC 3261, section 16. In SIP Servlet, only stateful Proxy is supported, which is most often used. Most of the proxy logic is already built within a SIP Servlet container. So writing a SIP proxy is really simple.
The following is how the ProxyServer servlet handles SIP INVITE messages.
@Override
protected void doInvite(SipServletRequest req) throws ServletException, IOException {
// application only has to worry about forwarding the initial request.
// the rest is taken care by the container.
if (req.isInitial()) {
URI callee = req.getTo().getURI();
Proxy proxy = req.getProxy();
//let's see if the Location Service has bindings for this callee.
List<URI> targets = _locator.getContacts(callee);
if (targets.size() > 0) {
// The Proxy Server should stay on the signaling path
proxy.setRecordRoute(true);
// Let the container handles all the 3xx redirect automatically
proxy.setRecurse(true);
// Try all the registered phones for this callee sequentially
proxy.setParallel(false);
//Yes, let's proxy the call to the registered phones.
proxy.proxyTo(targets);
}
else {
// Oops, no phone is registered right now. Let's do our best.
proxy.proxyTo(callee);
}
}
Servlet Selection
Since there are two servlets in this application, how does Voxeo Prism know Registar servlet will handle SIP REGISTER messages and Proxy servlet will handle SIP INVITE messages?
The trick is to specify the matching rules in the SIP deployment descriptor.
<servlet-selection>
<servlet-mapping>
<servlet-name>Registrar</servlet-name>
<pattern>
<equal ignore-case="false">
<var>request.method</var>
<value>REGISTER</value>
</equal>
</pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Proxy</servlet-name>
<pattern>
<and>
<not>
<equal ignore-case="false">
<var>request.to.uri.user</var>
<value>prism</value>
</equal>
</not>
<equal ignore-case="false">
<var>request.method</var>
<value>INVITE</value>
</equal>
</and>
</pattern>
</servlet-mapping>
</servlet-selection>
The above rules basically tell Voxe Prism that Registar servlet will take any SIP REGISTER requests, and Proxy servlet will take any SIP INVITE requests if the request URI’s user portion is not “prism”. The last condition is to avoid conflicts with the pre-installed Voxeo Prism demo application.
Please note that the rules are associated with servlet names, instead of servlet class name. The association between servlet names and servlet class names are defined via @SipServlet annotations in Registar servlet and Proxy servlet respectively.
Running the Application
First step is to download and install Voxeo Prism. Then download the application and copy the WAR file to your <where_prism_is_installed>/apps directory.
Now you can configure two phones to register Voxeo Prism. Here I show you how to configure two X-Lite softphones to test the application.
Please note that Proxy address is pointing to the where Voxeo Prsim is, e.g. 192.168.1.111.
Once both phones are registered, you can view the current bindings by going to http://192.168.1.111:8080/Tutorial.2/, as shown below.

Now the two phones can call each other by entering the name. E.g. calling john from my phone will look like the following.

Summary
In this blog and the tutorial application, I showed you how to implement a SIP Registrar and Proxy using SIP Servlet APIs.
Please note that the code is written mainly for simplicity and readability, instead of performance and scalability.
As an exercise, you can try to enhance it by completing the TODO items I listed in Registrar Servlet.
Enjoy!