本文是由两部分组成的系列文章的第 2 部分。在本系列文章的 第 1 部分中,您了解了本地和远程地从 Java 应用程序使用 Domino Objects 的一些基础知识。本文中您将了解 SSL 加密、servlet、连接池、单点登录、会话超时和回收。文中还包括关于故障检修的一个部分。本文假设您熟悉 Domino Java API,并已经阅读了第一篇文章。
本系列文章的前一篇文章讨论了在本地或远程运行 Java 应用程序。远程调用需要 HTTP 和 DIIOP 访问权。可以使用 SSL (Secure Sockets Layer) 对通过 DIIOP 端口的传输进行加密。有关如何设置 DIIOP 的说明,请参阅前一篇文章。客户机代码通过在 createSession 调用中指定新的第二个参数来表明加密要求。该参数是一个 String 数组,第一个元素将 -ORBEnableSSLSecurity 作为其值,例如:
1
2
3
4
5
|
String args[] = new String[1];
args[0] = "-ORBEnableSSLSecurity";
Session s = NotesFactory.createSession
("myhost.east.acme.com:63148", args,
"Jane Smith/East/Acme", "topS3cr3t");
|
仍然使用非 SSL 端口(上例中为 63148)来获得 IOR。实际服务请求是通过 DIIOP SSL 端口进行的,默认情况下,该端口为 63149。
运行代码之前,必须设置有一个从证书权威机构获得的通用受信任的根证书的服务器和客户机。最好将这个过程分为一系列的步骤来讲述。
创建密钥环(key ring)。打开 Domino 服务器中的 Server Certificate Admin (certsrv.nsf) 数据库,使用其表单创建和填充密钥环。有关的详细信息,请参阅 Administering the Domino System, Volume 2 或 Domino Administrator Help。为了进行测试,可以使用 CertAdminCreateKeyringWithSelfCert 表单创建具有自我认证证书的密钥环。
将密钥环移至服务器。密钥环包含密钥环文件(KYR 文件)和存储文件(STH 文件)。在访问 Server Certificate Admin 数据库的计算机上生成这些文件。将这两个密钥环文件移至或复制到包含 Domino 服务器的计算机上。将它们放在服务器的数据目录中。例如,如果使用默认名称创建具有自我认证证书的密钥环,并将文件复制到服务器的数据文件安装在 C:\Lotus\Domino\Data 中的计算机上,那么服务器文件将为:
C:\Lotus\Domino\Data\selfcert.kyr
C:\Lotus\Domino\Data\selfcert.sth.
将 TrustedCerts.class 复制到客户机中并将其放在类路径中。一旦密钥环文件位于服务器中,启动或重新启动 DIIOP 任务会在 Domino 数据目录中生成名为 TrustedCerts.class 的文件。将该文件分布到任何计算机中,您将从这台计算机使用 CORBA 通过 SSL 访问服务器,并将包含该文件的目录放在类路径中。例如,如果将文件复制到客户机的 C:\Lotus\TrustedCerts.class 中,那么设置类路径将如下所示:
set classpath := %classpath%;c:\lotus
为服务器启用 SSL。在服务器的 Domino Directory 的 Server 文档中,转至 Ports 选项卡,然后转至 Internet Ports 选项卡。在 SSL 设置下,指定 SSL 密钥文件名(例如,selfcert.kyr)。再转至 DIIOP 选项卡。确保 SSL 端口号正确 —— 默认端口号为 63149。启用 SSL 端口。并根据需要设置 Name & password 和 Anonymous 身份验证。
Domino 服务器 HTTP 任务通过加载 servlet 引擎和 JVM 来支持 servlet。在 Domino Directory 的 Server 文档中,转至 Internet Protocols 选项卡、Domino Web Engine 选项卡和 Java Servlets 选项卡进行设置。Domino Designer Help 文档“Running servlets in Domino”提供了一般情况的详细说明。下面是 Server 文档上面部分的示例:
默认情况下,Domino 在 domino\servlet 下的数据目录中查找 servlet 可执行代码。例如,如果名为 domino\servlet 的文件中包含可执行 servlet,那么可以按如下所示将其存储在服务器上:
c:\lotus\domino\data\domino\servlet\MyServlet.class
从浏览器(默认情况下),通过在 URL 中指定 /servlet 和类文件的名称来运行 servlet。例如:
http://myhost.east.acme.com/servlet/MyServlet
如果正在访问 Domino Objects,还有考虑其他两点。首先,对于远程访问,服务器的 Notes.ini 文件必须为变量 JavaUserClasses 指定 NCSO 归档文件。该变量是由 HTTP 任务加载的 JVM 的类路径。典型规范应该为:
JavaUserClasses=c:\lotus\domino\data\domino\java\NCSO.jar
其次,在本地访问 Domino Objects 的代码必须使用 NotesThread。因为其他方法无法在 servlet 中使用,所以在使用任何 Domino Objects 之前先调用 NotesThread.sinitThread,然后调用 NotesThread.stermThread。下列代码示范了在本地访问 Domino Objects 的简单 servlet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import lotus.domino.*;
public class MyServlet extends HttpServlet
{
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
try
{
NotesThread.sinitThread();
Session s1 = NotesFactory.createSession();
String name = s1.getUserName();
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<
HTML
><
B
>Information from servlet:
B
><
BR
>");
out.println("<
BR
>Name = " + name);
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
NotesThread.stermThread();
}
}
}
|
Java 类的本地访问仅需要 HTTP。必须运行 HTTP 任务,这样浏览器才可以访问服务器,从而调用 servlet。无需使用 DIIOP 任务。对于远程访问,必须同时运行 DIIOP 任务。编码将相对简单,因为不使用 NotesThread。极有可能您将不使用远程类从同一台计算机访问 Domino。首选是本地类。然而,可以从不同计算机上运行的其他 servlet 管理器(例如,WebSphere)来使用远程类。下面是一个模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import lotus.domino.*;
public class MyServlet extends HttpServlet
{
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
try
{
Session s1 = NotesFactory.createSession("myserver.east.acme.com");
String name = s1.getUserName();
s1.recycle();
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<
HTML
><
B
>Information from servlet:
B
><
BR
>");
out.println("<
BR
>Name = " + name);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
|
连接池允许使用同一 ORB (Object Reference Broker) 创建多个会话,因为所有会话共享一个 TCP/IP 连接,所以这将减少网络资源。在 servlet 应用程序和服务器到服务器的应用程序中,连接池特别有用。可以使用 NotesFactory createORB 方法之一生成 ORB。然后使用带有 ORB 参数的 createSession 方法创建会话。
这里存在的危险是过度加载网络连接。花费很长时间的某一会话上的操作会妨碍共享 ORB 的其他会话上的操作。创建的会话数不要超过连接可以处理的数目,当完成一个会话时要对其进行回收。
下列代码是一个简单示例,该示例使用了一个 ORB,以及多达 10 个的 Domino 会话。第一次运行 servlet 时以及每第十次运行 servlet 之后,servlet 都会创建新的 ORB。示例使用了下一节中将介绍的 SSO。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import lotus.domino.*;
public class ConnPool3 extends HttpServlet
{
Session s = null;
Static org.omg.CORBA.ORB orb = null;
Static int count = 0;
Static Object sem = new Object;
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
try
{
synchronized (sem)
{
if ((orb == null) || ((count++ % 10) == 0))
orb = NotesFactory.createORB();
}
s = NotesFactory.createSession("myhost1.east.acme.com:63148",
orb,request);
String name = s.getUserName();
s.recycle();
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<
HTML
><
B
>Information from servlet:
B
><
BR
>");
out.println("<
BR
>Name = " + name);
out.println("<
BR
>Count = " + count);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
|
单点登录允许通过一台服务器上的一次登录,来访问多个 Domino 和 WebSphere 服务器。访问的服务器必须像 Administering the Domino System, Volume 2 或 Domino Administrator 帮助中说明的那样设置了 SSO。您还可以参阅红皮书 Domino and WebSphere Together Second Edition。
下列签名在通过 Domino 或 WebSphere 服务器的预先身份验证之后创建了一个 SSO 会话。
下列代码说明了基本知识。它使用名称和密码访问服务器,获得 SSO 令牌,然后使用该令牌访问同一 SSO 域中的其他服务器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import lotus.domino.*;
public class sso1 implements Runnable
{
public static void main(String argv[])
{
sso1 t = new sso1();
Thread nt = new Thread((Runnable)t);
nt.start();
}
public void run()
{
String token = null;
try
{
String host1 = "myhost1.east.acme.com:63148";
String name = "Jane Smith/East/Acme";
String pw = "topS3cr3t";
Session s = NotesFactory.createSession(host1, name, pw);
token = s.getSessionToken();
s.recycle();
}
catch(Exception e)
{
e.printStackTrace();
}
try
{
String host2 = "myhost2.east.acme.com:63148";
Session s = NotesFactory.createSession(host2, token);
System.out.println(s.getUserName());
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
|
接下来的代码是一个 servlet 示例,从 HTTP cookie 列表中取得 SSO 令牌。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import lotus.domino.*;
public class sso13 extends HttpServlet
{
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
Cookie[] cookies = null;
String sessionToken = null;
try
{
cookies = request.getCookies();
}
catch (Exception e)
{
e.printStackTrace();
}
if (cookies != null)
{
for (int i = 0; i < cookies.length; i++)
{
if (cookies[i].getName().equals("LtpaToken"))
{
sessionToken = cookies[i].getValue();
}
}
}
if (sessionToken != null)
{
try
{
NotesThread.sinitThread();
Session session = NotesFactory.createSession(null, sessionToken);
response.setContentType("text/plain");
ServletOutputStream out = response.getOutputStream();
out.println("UserName: " + session.getUserName());
}
catch (NotesException e)
{
e.printStackTrace();
}
finally
{
NotesThread.stermThread();
}
}
}
}
|
对于远程会话,服务器都会有最大空闲时间的限制。如果一个会话的空闲时间超过这个限制,服务器将中止此会话并释放资源。在 Server 文档里,可以在 Internet Protocols -〉DIIOP 标签的 Idle session timeout 域里设置这个值,默认值是 60 分钟。
在客户端代码里,使用 Session.isValid() 方法来判断一个远程会话是否可用。如果远程会话已经超时,isValid() 方法将返回 false 值。下面的例子将创建一个远程会话,然后重新访问此会话。在条件语句中将判断这个会话是否仍然可用,如果不可用,程序将重新创建一个会话。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
import lotus.domino.*;
public class isvalidTest implements Runnable
{
String host=null, user="", pwd="";
Session s = null;
public static void main(String argv[])
{
if(argv.length <
1
)
{
System.out.println("Need to supply Domino server name");
return;
}
isvalidTest
t
=
new
isvalidTest(argv);
Thread
nt
=
new
Thread((Runnable)t);
nt.start();
}
public isvalidTest(String argv[])
{
host
=
argv
[0];
if(argv.length >= 2) user = argv[1];
if(argv.length >= 3) pwd = argv[2];
}
public void run()
{
try
{
s = NotesFactory.createSession(host, user, pwd);
String p = s.getPlatform();
System.out.println("Platform = " + p);
}
catch(Exception e)
{
e.printStackTrace();
}
// Later the same day
try
{
if (!s.isValid())
s = NotesFactory.createSession(host, user, pwd);
String n = s.getUserName();
System.out.println("Username = " + n);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
|
isValid() 方法将激活会话,而该会话将重置超时时钟。
Java 不认识重量级后端 Domino Objects,仅认识表示这些对象的轻量级 Java 对象。对于本地类,Domino Objects 在运行 Java 程序的过程中使用了内存,如果不回收这些内存,就不会发生垃圾收集,直到程序退出。内存使用在下列情况下可能存在一些问题:
所有 Domino Objects 都有回收方法。回收方法将终止当前对象及其所有子对象,并释放它们的内存。在内存使用可能存在问题的时候,强烈建议对内存进行回收。例如,可以将下列语句(其中会话是 Session 对象)放在 servlet 中 doGet 代码的后面。这样将在每次调用 servlet 后回收所有使用的 Domino Objects。
session.recycle();
可以将下列语句放在处理文档(其中 doc 是 Document 对象)的循环(在任何应用程序中)的后面。这样将在每次调用时回收 Document 对象及其所有子对象(如 Item)。
doc.recycle();
对于远程 Domino Objects,回收没有这样重要。那时内存使用是在 DIIOP 过程中,由线程执行垃圾收集。如果失去 TCP/IP 连接,整个会话都将被自动回收。
回收也有下列签名:
recycle(java.util.Vector objects)
其中 vector 包含 Domino Objects。该签名有效地对回收请求进行批处理,提高远程调用的效率。
在进行回收时,请遵守下列指导原则:
下一节将介绍一般问题的故障检修方案。
确保代码可以访问必需的类。假设 Notes 和 Domino 软件安装在 c:\lotus\domino 中,那么类路径和路径必须按如下所示方式进行设置:
对于本地类来说,PATH 是重要的,因为 Notes.jar 需要使用 Notes/Domino 二进制文件和 Notes.ini。
如果从没有 Domino 软件的计算机远程访问 Domino,必须将 NCSO 归档文件复制到该计算机中。类路径应该反映归档文件在该计算机中的位置。
对于 SSL,类路径必须指定包含 TrustedCerts.class 的文件夹。
要远程使用 Domino Objects,必须运行远程计算机,必须运行 Domino 服务器,客户机计算机必须使用正确的主机名称或 IP 地址。
在 Windows 中,如果有权访问服务器计算机,可以通过下列 DOS 命令获取诸如主机名称、DNS 服务器和 IP 地址之类的信息:
> ipconfig/all
在客户机计算机中,必须能够访问服务器计算机。可以尝试 ping 命令:
> ping hostname
或者
> ping ipaddress
如果无法 ping 远程计算机,那么是网络有问题。可能是物理网络连接、网络设置或输入的名称存在问题。必须解决这些问题,才能继续进行后面的操作。
如果可以 ping 远程计算机,尝试 telnet 命令来查看远程计算机监听的端口是否正确。对于 HTTP,默认端口是 80,对于 DIIOP,默认端口为 63148。
> telnet host port
如果能够 ping 计算机,但不能 telnet 端口,那么可能是发生了下列情况之一:
如果通过 Web 服务器端口访问 IOR,应该能够使用下列 URL 从浏览器访问 Domino Server:
http://hostname
应该能够使用下列 URL 查看 IOR 文件:
http://hostname:port/diiop_ior.txt
如果有权访问 Domino Server,可以使用下列控制台命令检查 DIIOP 任务:
> tell diiop show config
下面是一些典型输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
> tell diiop show config
Dump of Domino IIOP (DIIOP) Configuration Settings
Full Server Name: CN=Buffalo/O=Zoo
Common Server Name: Buffalo/Zoo
Refresh Interval: 3 minutes
Host Full Name: Buffalo.myCompany.com
Host Short Name: Buffalo
Host Address: 9.99.99.999
Public Host Name/Address: 9.99.99.999
TCP Port: 63148 Enabled
SSL Port: 63149 Enabled
Initial Net Timeout: 120 seconds
Session Timeout: 60 minutes
Client Session Timeout: 62 minutes
IOR File: d:\dom60x\data\domino\html\diiop_ior.txt
SSL Key File: d:\dom60x\data\mykeyfile.kyr
Java Key File: d:\dom60x\data\domino\java\TrustedCerts.class
Allow Ambiguous Names: False
Web Name Authentic: False
User Lookup View: ($Users)
Allow Database Browsing: True
TCP Name/Password Allowed: True
TCP Anonymous Allowed: True
SSL Name/Password Allowed: True
SSL Anonymous Allowed: True
Multi-Server Session Authentication: Enabled
Multi-Server Session Configuration: mySSOConfig
Internet Sites: Disabled
Single Server Cookies: Disabled
|
下面是一些错误消息和可能的解释。
HTTP JVM java.lang.ClassNotFoundException and HTTP JVM java.lang.NoClassDefFoundError
这些消息显示在服务器控制台上。它们指明客户机正在尝试使用远程类,但未在服务器中的 Notes.ini 中指定该 JavaUserClasses。
NotesException: Could not get IOR from Domino Server: java.net.ConnectException: Connection refused
该错误消息指出存在下列情况之一:
NotesException: Could not get IOR from Domino Server: java.net.ConnectException: Operation timed out
在条错误消息指出存在网络配置错误。
NotesException: Could not get IOR from Domino Server: java.net.UnknownHostException
这条错误消息指出存在下列情况之一:
NotesException: Could not open Notes session: org.omg.CORBA.COMM_FAILURE: java.net.ConnectException: Connection refused
这条错误消息指出您未在服务器上运行 DIIOP 任务。
NotesException: Invalid user name/password
这条错误消息指出代码尝试使用未知名称或无效密码访问 Domino Directory。
NotesException: Server access denied
这条错误消息指出进行了 Anonymous 访问,但未被允许。
NotesException: User username is not a server
这条错误消息指出尝试通过客户机上的 Domino Directory 来运行会话。
NotesException: Wrong Password
这条错误消息指出在尝试通过 Notes ID 访问时,代码提供了错误密码。如果代码不发送密码,访问最初为 Anonymous。如果需要身份验证(例如,试图打开数据库),会出现 Lotus Notes 框来要求您输入密码。该框会一直显示,直到用户提供了正确的密码或单击 Cancel 为止。
Reference to createSession is ambigous
在编译过程中会出现这种错误消息。表明使用了 null 作为 createSession 参数。在大多数情况下,必须使用“(String)null”。
UnsatisfiedLinkError: NCreateSession
该错误消息指明本地会话未使用 NotesThread。提供 NotesThread.sinitThread 和 stermThread,或者使用 NotesThread。
NotesFactory createSession 方法可以创建本地或远程会话对象。可以使用 Notes ID 或 Domino Directory 进行本地会话调用。远程会话使用 Domino Directory。
对于遵守下列格式的 NotesFactory 签名:
hostname = hostname | hostname:port | ipaddress | ipaddress:port
1
2
3
4
5
6
7
|
createSession()
createSession((String)null)
createSession("")
createSession((String)null, (String)null, password)
createSession("", (String)null, password)
createSessionWithFullAccess() // useful on server only
createSessionWithFullAccess(password) // useful on server only
|
1
2
3
4
|
createSession((String)null, "", "") // Anonymous
createSession("", "", "") // Anonymous
createSession((String)null, username, password)
createSession("", username, password)
|
1
2
3
4
5
6
7
|
createSession(hostname) // Anonymous
createSession(hostname, username, password)
createSession(hostname, -ORBEnableSSLSecurity, username, password)
createSessionWithIOR(ior) // Anonymous
createSessionWithIOR(ior, username, password)
createSessionWithIOR(ior, -ORBEnableSSLSecurity, username, password)
getIOR(hostname)
|
下列 Domino 服务器的 Notes.ini 变量会影响 DIIOP 任务。
本系列的文章综合讲述了使用 Java 访问 Domino Objects。在本系列文章的第 2 部分中,我们讲述了 SSL 加密、servlet、连接池、单点登录、会话超时和回收。同时还包含了介绍故障检修的一个小节。结合介绍本地和远程调用以及访问控制的第一篇文章,现在,对 Java 应用程序编码,您应该是胸有成竹。
Robert Perron 是位于马萨诸塞州 Westford 的 Lotus 的文档架构师。自从二十世纪九十年代初开始,他就一直为 Lotus Notes 和 Domino 开发文档,主要致力可编程功能的开发。他曾为 LotusScript 和 Java Notes 类开发文档,并与别人合著了 60 Minute Guide to LotusScript 3 - Programming for Notes 4 一书。他还 LDD Today 撰写过多篇文章。去年,他为 The View 撰写了“A Comprehensive Tour of Programming Enhancements in Notes/Domino 6”一文。