6

我们有一个使用 Jetty 8.1、dojo 和 cometd 的 Web 应用程序,它使用 (1) 用于同步操作的 JSON/HTTP REST API 和 (2) 用于从服务器接收大量事件的 Cometd API 在浏览器和 Web 容器之间进行交互。

我们不完全清楚的是如何优雅地管理这两个不同 API 的身份验证会话,特别是因为我们的 Cometd 将尽可能使用 websocket 而不是常规 HTTP。该应用程序使用标准 Jetty LDAP 模块使用基于表单的身份验证。因此,从 HTTP 的角度来看,容器为浏览器提供了一个标准的 jsessionid,如下所示:

Cookie: jsessionid=758E2FAD7C199D722DA8B5E243E0E27D

根据 Simone Bordet 在此处的帖子,似乎推荐的解决方案是在我们正在做的 Cometd 握手期间传递此令牌。

我们遇到的问题是有两个根本不同的会话——HTTP 会话和 Bayeux cometd 会话。由于潜在的内存泄漏和安全问题等原因,我们希望它们一致地终止或“配对”。如果用户的 HTTP 会话终止,我们希望相应的 Bayeux 会话也终止,反之亦然。有推荐的方法吗?

4

2 回答 2

12

HTTP 会话和 CometD 会话具有不同的生命周期:例如,如果临时连接失败,则 CometD 会话将失败,服务器会要求客户端重新握手,从而创建不同的 CometD 会话(代表相同的用户,但使用不同的 CometD clientId)。在相同的情况下,HttpSession将保持不变。

考虑到这一点,您需要在应用程序级别维护用户名、通讯者HttpSession和通讯者之间的映射ServerSession。我们称之为映射HttpCometDMapper。每次新用户登录时,您都要注册其名称(或用户的另一个唯一标识符)、HttpSession和当前ServerSession. 可能您将需要一个两步过程,首先将用户名和 . 链接起来HttpSession,然后将相同的用户名与ServerSession.

如果执行 CometD 重新握手,则使用新的ServerSession.

HttpSessionListener您可以通过向 注册 an来链接这两个会话,HttpSession这样当它被销毁时,您可以ServerSession从映射器中检索当前的 CometD 并调用ServerSession.disconnect()它。

反之亦然有点棘手,因为 CometD 没有像HttpSessionhas 那样的非活动超时概念。它必须使用您自己的逻辑在应用程序中实现。

这样做的一部分是在 上注册 a RemoveListenerServerSession如下所示:

serverSession.addListener(new ServerSession.RemoveListener() 
{
    public void removed(ServerSession session, boolean timeout);
    {
        if (!timeout)
        {
            // Explicitly disconnected, invalidate the HttpSession
            httpCometDMapper.invalidate(session);
        }
    }
});

此侦听器监视与客户端(和服务器)的显式断开连接 - 谨防重入。

稍微困难的是为非显式断开实现相同的机制。在这种情况下,timeout参数将为真,但可能由于临时网络故障而发生(与客户端永久消失相反),并且同一用户可能已经与新的ServerSession.

我认为在这种情况下,应用程序超时可以解决问题:当您看到ServerSession因超时而被删除时,您会注意到该用户并启动应用程序超时。如果同一个用户重新握手,取消应用超时;否则用户真的走了,应用程序超时到期,你也使之无效HttpSession

以上只是想法和建议;实际的实现很大程度上取决于应用程序的细节(这就是 CometD 不提供开箱即用的原因)。

关键点是映射器、HttpSessionListenerRemoveListener,以及了解这些组件的生命周期。一旦你做到了这一点,你就可以编写正确的代码来为你的应用程序做正确的事情。

最后,请注意 CometD 具有与HttpSession通过BayeuxContext实例交互的与传输无关的方式,您可以从BayeuxServer.getContext(). 我建议你也看看它,看看它是否可以简化事情,尤其是检索存储在HttpSession.

于 2012-05-30T17:56:40.670 回答
0

如果我们在临时连接失败后创建一个 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);
    }

}
于 2015-11-12T06:11:23.703 回答