Jetty 将Comed会话与HTTP会话配对
我们有一个使用Jetty 8.1、dojo和cometd的web应用程序,它使用(1)用于同步操作的JSON/HTTP REST API和(2)用于从服务器接收大量事件的cometd API在浏览器和web容器之间进行交互 我们不完全清楚的是如何优雅地管理这两个不同API的身份验证会话,特别是因为cometd for us将尽可能使用websocket而不是常规HTTP。应用程序使用标准Jetty LDAP模块使用基于表单的身份验证。因此,从HTTP的角度来看,容器为浏览器提供了一个标准的jsessionid,如下所示:Jetty 将Comed会话与HTTP会话配对,jetty,cometd,Jetty,Cometd,我们有一个使用Jetty 8.1、dojo和cometd的web应用程序,它使用(1)用于同步操作的JSON/HTTP REST API和(2)用于从服务器接收大量事件的cometd API在浏览器和web容器之间进行交互 我们不完全清楚的是如何优雅地管理这两个不同API的身份验证会话,特别是因为cometd for us将尽可能使用websocket而不是常规HTTP。应用程序使用标准Jetty LDAP模块使用基于表单的身份验证。因此,从HTTP的角度来看,容器为浏览器提供了一个标准的jse
Cookie: jsessionid=758E2FAD7C199D722DA8B5E243E0E27D
根据Simone Bordet的帖子,似乎推荐的解决方案是在Comet握手期间传递此令牌,这就是我们正在做的
我们面临的问题是有两个根本不同的会话——HTTP会话和Bayeux Comed会话。出于潜在内存泄漏和安全问题等原因,我们希望它们一致终止或“配对”。如果用户的HTTP会话终止,我们希望相应的Bayeux会话也终止,反之亦然。有推荐的方法吗?HTTP会话和Comed会话具有不同的生命周期:例如,如果出现临时连接故障,Comed会话将失败,服务器将要求客户端重新握手,从而创建不同的Comed会话(代表相同的用户,但使用不同的Comed
clientId
)。在相同的情况下,HttpSession
将保持不变
考虑到这一点,您需要在应用程序级别维护用户名、对应的HttpSession
和对应的ServerSession
之间的映射。
让我们将此映射称为httpcometedmapper
。
每次新用户登录时,您都会注册其名称(或用户的另一个唯一标识符)、HttpSession
和当前的ServerSession
。
您可能需要一个两步流程,首先链接用户名和HttpSession
,然后将相同的用户名链接到ServerSession
如果执行了Comed重新握手,则使用新的ServerSession
更新映射器
您可以通过将HttpSessionListener
注册到HttpSession
来链接这两个会话,这样当它被销毁时,您就可以从映射器中检索当前ComedServerSession
,并在其上调用ServerSession.disconnect()
viceversa有点棘手,因为CometD没有像HttpSession那样的非活动超时概念。它必须用自己的逻辑在应用程序中实现
这样做的一部分是在服务器会话
上注册RemoveListener
,如下所示:
serverSession.addListener(new ServerSession.RemoveListener()
{
public void removed(ServerSession session, boolean timeout);
{
if (!timeout)
{
// Explicitly disconnected, invalidate the HttpSession
httpCometDMapper.invalidate(session);
}
}
});
此侦听器监视与客户端(和服务器)的显式断开连接,注意重入
更难的是为非显式断开连接实现相同的机制。在这种情况下,timeout
参数将为true,但可能是由于临时网络故障而发生的(而不是客户端永久消失),并且同一用户可能已经与新的服务器会话
重新握手
我认为在这种情况下,应用程序超时可以解决问题:当您看到由于超时而删除的ServerSession
时,您会注意到该用户并启动一个应用程序超时。如果同一用户重新握手,则取消应用程序超时;否则用户真的不在了,应用程序超时将过期,并且您会使应用程序无效HttpSession
也是
以上只是一些想法和建议;实际的实现在很大程度上取决于应用程序的细节(这就是为什么Comet不提供开箱即用的功能)
关键点是映射器、HttpSessionListener
和removellistener
,以及了解这些组件的生命周期。
一旦管理好了,您就可以编写正确的代码,为您的应用程序做正确的事情
最后,请注意CometD有一种与传输无关的方式,可以通过实例与HttpSession
交互,您可以从BayeuxServer.getContext()
获得该实例。
我建议您也考虑一下,看看它是否可以简化事情,特别是在检索存储在
HttpSession
中的令牌时,如果在临时连接失败后创建BayeuxClient,是否会遇到任何问题
您可以尝试使用下面的代码
try {
log.info("Running streaming client example....");
makeConnect();
} catch (Exception e) {
handleException("Error while setup the salesforce connection.", e);
}
}
private void makeConnect() {
try{
client = makeClient();
client.getChannel(Channel.META_HANDSHAKE).addListener
(new ClientSessionChannel.MessageListener() {
public void onMessage(ClientSessionChannel channel, Message message) {
log.info("[CHANNEL:META_HANDSHAKE]: " + message);
boolean success = message.isSuccessful();
if (!success) {
String error = (String) message.get("error");
if (error != null) {
log.error("Error during HANDSHAKE: " + error);
}
Exception exception = (Exception) message.get("exception");
if (exception != null) {
handleException("Exception during HANDSHAKE: ", exception);
}
}
}
});
client.getChannel(Channel.META_CONNECT).addListener(
new ClientSessionChannel.MessageListener() {
public void onMessage(ClientSessionChannel channel, Message message) {
log.info("[CHANNEL:META_CONNECT]: " + message);
boolean success = message.isSuccessful();
if (!success) {
client.disconnect();
makeConnect();
String error = (String) message.get("error");
if (error != null) {
//log.error("Error during CONNECT: " + error);
}
}
}
});
client.getChannel(Channel.META_SUBSCRIBE).addListener(
new ClientSessionChannel.MessageListener() {
public void onMessage(ClientSessionChannel channel, Message message) {
log.info("[CHANNEL:META_SUBSCRIBE]: " + message);
boolean success = message.isSuccessful();
if (!success) {
String error = (String) message.get("error");
if (error != null) {
makeConnect();
log.error("Error during SUBSCRIBE: " + error);
}
}
}
});
client.handshake();
log.info("Waiting for handshake");
boolean handshaken = client.waitFor(waitTime, BayeuxClient.State.CONNECTED);
if (!handshaken) {
log.error("Failed to handshake: " + client);
}
log.info("Subscribing for channel: " + channel);
client.getChannel(channel).subscribe(new MessageListener() {
public void onMessage(ClientSessionChannel channel, Message message) {
injectSalesforceMessage(message);
}
});
log.info("Waiting for streamed data from your organization ...");
}catch (Exception e) {
handleException("Error while setup the salesforce connection.", e);
}
}