【问题标题】:Pairing cometd session with HTTP session将 cometd 会话与 HTTP 会话配对
【发布时间】:2012-05-30 17:01:21
【问题描述】:

我们有一个使用 Jetty 8.1、dojo 和 cometd 的 Web 应用程序,它使用 (1) 用于同步操作的 JSON/HTTP REST API 和 (2) 用于从服务器。

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

Cookie: jsessionid=758E2FAD7C199D722DA8B5E243E0E27D

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

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

【问题讨论】:

    标签: jetty cometd


    【解决方案1】:

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

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

    如果执行 CometD 重新握手,则使用新的 ServerSession 更新映射器。

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

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

    其中一部分是在ServerSession 上注册RemoveListener,如下所示:

    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 有一种与传输无关的方式,可以通过BayeuxContext 实例与HttpSession 进行交互,您可以从BayeuxServer.getContext() 获得这种方式。 我建议你也看看它,看看它是否可以简化事情,特别是检索存储在HttpSession 中的令牌。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        猜你喜欢
        • 2020-04-13
        • 1970-01-01
        • 2013-12-01
        • 2012-07-21
        • 1970-01-01
        • 2016-07-10
        • 2019-01-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多