SIP Servlet 概述及范例

Posted on Jun 14, 2006

SIP Servlets 规范 (JSR116, Java Community Process), 提供了一系列的 Java API 和一个基于容器 (Container) /应用服务器 (Application Server) 的开发模型, 用于提高服务器端 SIP 应用的开发效率.

SIP Servlets 同样基于 Java Servlet 架构, 其 API 归属于 javax.servlet.sip 包, 和 javax.servlet.http 同样扩展自 javax.servlet. 不同的是, HTTP Servlets 通过 Servlet 架构实现了 HTTP 协议, 而 SIP Servlet 实现了 SIP 协议.

通过 SIP Servlets, 开发人员可以非常简单的开发出复杂的 SIP 应用程序, 就像 HTTP servlets 在 WEB 应用开发中起到的作用一样.

一组 SIP Servlets 连同资源和部署描述文件打包后部署并运行在一个容器或 SIP 应用服务器上的. 容器提供了例如会话状态管理, 事务管理, 重发, 网络连接, 消息调度, 线程管理, 资源管理, 应用程序管理等服务. 应用程序中只需包含高级的消息处理和业务逻辑, 开发人员不再需要在协议本身上投入过多精力, 这使 SIP 服务的开发成为一件轻而易举的事情.

下面对 HTTP serlvet 和 SIP servlet 做了一点比较:

HTTPSIP
Servlet classHttpServletSipServlet
SessionHttpSessionSipSession
Application packagewarsar
Deployment Descriptorweb.xmlsip.xml

SIP serlvets 继承自 javax.servlet.sip.SipServlet, 这个类同 HttpServlet 一样, 继承自 javax.servlet.GenericServlet. SipServlet 通过重写的 service(ServletRequest request, ServletResponse response) 方法来处理不同的SIP消息.

由于 SIP 协议是异步的, 所以 service 方法每次调用时, 其两个参数中只有一个是有效的, 而另一个是 null, 例如, 如果收到的 SIP 消息是一个 request, 那么第一个 request 参数有效, 而 response 参数为null, 反之亦然.

SipSerlvet 中 service 方法的默认实现是把 SIP 消息交给不同的子方法处理. 对请求来说, 是 doXXX() 方法, 对响应来说是 doXXXResponse() 方法. 例如, doInvite(SipServletRequest request) 方法处理 INVITE 请求, doSuccessResponse(SipServletResponse response) 处理所有的 2XX 响应. 通常, 通过重写 SipSerlvet 类的这些 doXXX(), doXXXResponse() 方法, 来实现 SIP 应用的业务逻辑.

创建响应:

doXXX(SipServletRequest request) 中, 可以通过调用 javax.servlet.sip.SipServletRequest类的 createResponse() 方法来创建一个响应, 随后, 调用这个响应的 send() 方法, 将其发送出去.

创建请求:

在一个 SIP Servlet 中创建一个请求有两种方式, SipSession 类的 createRequest() 方法用于在已经建立的一个会话中创建请求, 创建的请求属于这个已经存在的 SipSession. 另一种方法是 javax.servlet.sip.SipFactory 类的 createRequest() 方法. SipFactory 是 SIP Servlet API 中提供的一个工厂接口, 用于创建 Request, Address, URI, Applicaiton session 对象, 它的具体实现是由容器提供的, 在 SIP Servlet 中, 只需要通过 ServletContext 类的 getAttribute("javax.servlet.sip.SipFactory”) 就可以获得一个 SipFactory 实例.

下面是一个具体的例子:

这个例子是一简单的 “Echo” 应用, 它接收 Windows Messenger 发送的 Instant Message 消息, 再将收到的消息发送回 Windows Messenger.

package blog.sample.sipservlet;
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.*;
import javax.servlet.sip.*;

/**
* EchoServlet provides a simple example of a SIP servlet.
* EchoServlet echoes instant messages sent by Windows Messenger.
*/
public class EchoServlet extends SipServlet {

	/**
	* _address keeps the mapping between sign-in name and actual contact address.
	*/
	protected HashMap _addresses = new HashMap();

	/**
	* Invoked for SIP INVITE requests, which are sent by Windows Messenger
	* to establish a chat session.
	*/
	protected void doInvite(SipServletRequest req) throws IOException, ServletException {
		// we accept invitation for a new session by returning 200 OK response
		req.createResponse(SipServletResponse.SC_OK).send();
	}

	/**
	* Invoked for SIP REGISTER requests, which are sent by Windows Messenger
	* for sign-in and sign-off.
	*/
	protected void doRegister(SipServletRequest req) throws IOException, ServletException {
		String aor = req.getFrom().getURI().toString().toLowerCase();
		synchronized (_addresses) {
			// the non-zero value of Expires header indicates a sign-in.
			if (req.getExpires() != 0) {
				// keep the name/address mapping
				_addresses.put(aor, req.getAddressHeader("Contact").getURI());
			}
			// the zero value of Expires header indicates a sign-off.
			else {
				// remove the name/address mapping
				_addresses.remove(aor);
			}
		}
		// we accept the sign-in or sign-off by returning 200 OK response
		req.createResponse(SipServletResponse.SC_OK).send();
	}

	/**
	* Invoked for SIP MESSAGE requests, which are sent by Windows Messenger
	* for instant messages.
	*/
	protected void doMessage(SipServletRequest req) throws IOException, ServletException {
		SipURI uri = null;
		synchronized (_addresses) {
			// get the previous registered address for the sender
			uri = (SipURI) _addresses.get(req.getFrom().getURI().toString().toLowerCase());
		}
		if (uri == null) {
			// reject the message if it is not from a registered user
			req.createResponse(SipServletResponse.SC_FORBIDDEN).send();
			return;
		}
		// we accept the instant message by returning 200 OK response.
		req.createResponse(SipServletResponse.SC_OK).send();
		// create an echo SIP MESSAGE request with the same content
		SipServletRequest echo = req.getSession().createRequest("MESSAGE");
		String charset = req.getCharacterEncoding();
		if (charset != null) {
			echo.setCharacterEncoding(charset);
		}
		echo.setRequestURI(uri);
		echo.setContent(req.getContent(), req.getContentType());
		// send the echo MESSAGE request back to Windows Messenger
		echo.send();
	}

	/**
	* Invoked for SIP 2xx class responses.
	*/
	protected void doSuccessResponse(SipServletResponse resp) throws IOException, ServletException {
		// print out when the echo message was accepted.
		if (resp.getMethod().equalsIgnoreCase("MESSAGE")) {
			System.out.println("\"" + resp.getRequest().getContent() + "\" was accepted: " + resp.getStatus());
		}
	}

	/**
	* Invoked for SIP 4xx-6xx class responses
	*/
	protected void doErrorResponse(SipServletResponse resp) throws IOException, ServletException {
		// print out when the echo message was rejected/
		if (resp.getMethod().equalsIgnoreCase("MESSAGE")) {
			System.out.println("\"" + resp.getRequest().getContent() + "\" was rejected: " + resp.getStatus());
		}
	}

	/**
	* Invoked for SIP BYE requests, which are sent by Windows Messenger
	* to terminate a  chat session/
	*/
	protected void doBye(SipServletRequest req) throws IOException, ServletException {
		// accept session termination by returning 200 OK response.
		req.createResponse(SipServletResponse.SC_OK).send();
	}
}

编译 EchoServlet 需要 servlet.jar 和 sipservlet.jar 这两个 jar 文件在 classpath 中. sipservlet.jar 可以在 JSR116 的发布包中找到.

EchoServlet的呼叫流程

和 HTTP Servlet 应用一样, SIP Servlet 应用同样需要一个部署描述文件 sip.xml. 下面是一个 sip.xml 的例子:

<sip-app>
  <display-name>SIP Servlet Sample</display-name>
  <servlet>
    <servlet-name>echo</servlet-name>
    <servlet-class>com.micromethod.sample.EchoServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>echo</servlet-name>
    <pattern>
      <or>
        <equal><var>request.method</var><value>REGISTER</value></equal>
        <equal><var>request.method</var><value>INVITE</value></equal>
      </or>
    </pattern>
  </servlet-mapping>
</sip-app>

明显和 web.xml 不同的就是用 and, or, not, equal, exists, contains, subdomain-of 这一系列标签代替了 url-pattern. 通过这些标签的组合嵌套, 形成了一个条件表达式, 表明相应的 servlet 可以处理哪些 SIP 消息.

上面的 sip.xml 就表明了 EchoServlet 可以处理 REGISTER 和 INVITE 请求. 注意的是, 请求的类型指的是创建会话的请求, 会话后续请求不再做这种条件判断, 例如 INVITE 创建会话后, 会话内的 ACK, CANCEL, MESSAGE, BYE 等消息会直接被判定为由接收 INVITE 请求的 servlet 处理.

为了方便部署, 需要将 EchoServlet 和 sip.xml 放入一个部署包中. 和 HTTP Servlet 应用类似, SIP Servlet 应用使用 SIP application archive (SAR). SAR 文件其实就是 JAR 文件, 它的内部目录结构和 WAR 文件相同:

.../WEB-INF
   |-- classes
   | |-- blog
   |   |-- sample
   |     |-- sipservlet
   |       |-- EchoServlet.class
   |-- sip.xml

在上级目录执行 jar cv0f echo.sar 创建 sar 文件.

最后需要做的就是部署 echo.sar, 目前提供 SIP 应用服务器免费下载的的似乎只有 BEA 和 Micromethod, BEA 的 Weblogic SIP Server 是基于它的 Web Server 实现的, 和 J2EE 结合比较紧密, 但相应配置运行较为复杂, Micromethod 的 SIPMethod Application Server 相比之下是一个轻量级的产品, 配置部署运行都比较简洁, 同时还提供一个方便开发 SIP 应用的 Application Creation Environment 和一些基于 SIP Servlet 技术的完整 SIP 基础应用, 如 Proxy Server, Presence Server 等, 因此, 比较适合学习和开发使用. 可以从 http://www.micromethod.comhttp://cn.micromethod.com 下载.

本文中使用的就是 Micromethod 的 SIPMethod Application Server, 下载安装后, 将 echo.sar 拷贝到 SIPMethod AS 的 sipapps 目录, 最好把 sipapps 目录下其它的 sar 文件都删除, 以免 EchoServlet 的 servlet-mapping 和其它应用中的 servlet 冲突. 接下来, 执行 SIPMethod 的 bin 目录下的 startup.bat 运行服务器, 在服务器启动过程中, echo.sar 就会被解开.

通常情况下, 运行 SIPMethod AS 甚至不需要额外配置, 使用它的默认配置就可以运行, 高级的配置方式可以参考它的 user-guide. 常见的问题是端口冲突, SIPMethod AS 默认需要占用 TCP 5060, TCP 8080, TCP 47492, UDP 5060 这四个端口.

当 SIPMethod Application Server 启动完成后, 就可以用 Windows Messenger 看效果了.

Windows Messenger 的需要 5.0 或更高, 低于这个版本的 WM 没有 SIP 功能.

运行 Windows Messenger, 依照下面的步骤进行配置:

  1. 打开 “Options” 对话框 Tools->Options;
  2. 点击 “Options” 对话框中的 “Account” 标签;
  3. 选中 “My contacts include users of a SIP Communications Service” 复选框;
  4. 在 “Sign-in name” 编辑框中输入一个 SIP URI, 例如 kyle@micromethod.com;
  5. 点击 “Advanced…” 按钮打开 “SIP Communications Service Connection Configuration” 对话框;
  6. 选中 “Configure settings” 单选按钮;
  7. 在 “Server name or IP address” 编辑框中输入运行 SIPMethod AS 的机器IP地址, 例如 192.168.0.101, 或 192.168.0.101:5060, 5060 是 SIPMethod 默认的 SIP 监听端口;
  8. 选择使用的传输协议, 通常是 TCP 或 UDP, TLS 需要服务器进行额外配置.

配置完成后, 就可以登陆 Windows Messenger 了, 使用的登陆名就是刚才输入的 SIP URI, 例如 kyle@micromethod.com.

确保 SIPMethod Applicaiton Server 是运行的.

接下来选择 “Actions->Send an Instant Message…” 菜单项, 打开 “Send an Instant Message” 对话框, 选中对话框的 Other 标签, 在编辑框中输入 echo@micromethod.com, 点击 OK. (实际上在这个输入 echo 或是其它任何字符都可以, 因为我们在servlet-mapping 中并没有对 request.to 或 request.uri 进行任何限制).

在打开的 Instant message 窗口中, 就可以和 EchoServlet 对话了.