繁体   English   中英

实例之间的Google App Engine会话更改

[英]Google App Engine session changes between instances

视觉辅助工具,可帮助您了解问题。

我很困惑,因为在我尝试启动的每个应用程序中,此问题都已经100%发生了,但是我找不到有关此问题的任何信息,而且我在Internet上找不到其他人有这个问题。

我已经学习使用Google Cloud Platform使用Java进行Web开发大约一年了,但有一个问题,我已经坚持了几个月。 我似乎找不到任何地方在线提到此问题。

我有一个基于Java Servlet构建的Web应用程序,它使用会话来跟踪已登录的用户。 应用程序中的所有内容都可以正常运行,但是当我将应用程序部署到App Engine时,用户会继续随随后的服务器请求随机登录和注销。 我在开发窗口中查看,发现会话ID在两个不同的值之间不断变化。

由于应用程序引擎使用应用程序的多个实例进行流量和资源管理扩展,因此它可能会在一个实例上收到请求,然后将其从另一个实例发送回客户端。 这在客户端没有什么明显的区别,但是我发现每个实例的客户端都有不同的会话ID,从而导致每个实例都陷入了不同的会话属性。

当然,当我将应用程序缩减为一个实例时,我的会话运行良好。 但这在生产环境中行不通,我需要我的应用程序能够随流量扩展并按预期使用应用程序引擎资源。

Google要求使用Memcache和数据存储区自动处理app引擎上的会话,而访问该会话所需要做的只是使用request.getSession() ,所以我不明白为什么会发生此问题。 如果我不明白这个问题,我将永远找不到解决方案:(

[编辑]:该应用程序使用一个预先存在的Web过滤器DatastoreSessionFilter来跟踪使用cookie和数据存储区的会话变量,但由于我的会话变量被困在各个实例中,因此似乎无法正常工作。 这是DatastoreSessionFilter类:

@WebFilter(filterName = "DatastoreSessionFilter", 
    urlPatterns = { "", 
    "/LoginEmail", 
    "/Logout", 
    "/ResetPassword",
    "/SignupEmail", 
    "/VerifyEmail", 

    "/About", 
    "/BulkUpload", 
    "/DeleteAlbum", 
    "/DeletePost",
    "/EditAlbum", 
    "/EditPost",
    "/Events", 
    "/EventsScroll", 
    "/Home", 
    "/HomeScroll", 
    "/Info",
    "/ListAlbums",
    "/ListAlbumsScroll",
    "/NewAlbum",
    "/NewPost",
    "/Support",
    "/Videos",
    "/VideosScroll",
    "/ViewAlbum",
    "/ViewAlbumScroll",
    "/ViewPost",
    "/ViewProfile",
  })

public class DatastoreSessionFilter implements Filter {

  private static Datastore datastore;
  private static KeyFactory keyFactory;
  private static final DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSSS");

  @Override
  public void init(FilterConfig config) throws ServletException {
    // initialize local copy of datastore session variables

    datastore = DatastoreOptions.getDefaultInstance().getService();
    keyFactory = datastore.newKeyFactory().setKind("SessionVariable");
    // Delete all sessions unmodified for over two days
    DateTime dt = DateTime.now(DateTimeZone.UTC);
    Query<Entity> query = Query.newEntityQueryBuilder().setKind("SessionVariable")
        .setFilter(PropertyFilter.le("lastModified", dt.minusDays(2).toString(dtf))).build();
    QueryResults<Entity> resultList = datastore.run(query);
    while (resultList.hasNext()) {
      Entity stateEntity = resultList.next();
      datastore.delete(stateEntity.getKey());
    }
  }
  // [END init]

  @Override
  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)
      throws IOException, ServletException {

    HttpServletRequest req = (HttpServletRequest) servletReq;
    HttpServletResponse resp = (HttpServletResponse) servletResp;

    // Check if the session cookie is there, if not there, make a session cookie using a unique
    // identifier.
    String sessionId = getCookieValue(req, "bookshelfSessionId");

    if (sessionId.equals("")) {
      String sessionNum = new BigInteger(130, new SecureRandom()).toString(32);
      Cookie session = new Cookie("bookshelfSessionId", sessionNum);
      session.setPath("/");
      resp.addCookie(session);
    }

    Map<String, String> datastoreMap = loadSessionVariables(req); // session variables for request

    chain.doFilter(servletReq, servletResp); // Allow the servlet to process request and response

    HttpSession session = req.getSession(); // Create session map
    Map<String, String> sessionMap = new HashMap<>();
    Enumeration<String> attrNames = session.getAttributeNames();

    while (attrNames.hasMoreElements()) {
      String attrName = attrNames.nextElement();
      String sessName = session.getAttribute(attrName).toString();
      sessionMap.put(attrName, sessName);
      // DEFAULT: sessionMap.put(attrName, (String) session.getAttribute(attrName));
    }

    // Create a diff between the new session variables and the existing session variables
    // to minimize datastore access
    MapDifference<String, String> diff = Maps.difference(sessionMap, datastoreMap);
    Map<String, String> setMap = diff.entriesOnlyOnLeft();
    Map<String, String> deleteMap = diff.entriesOnlyOnRight();

    // Apply the diff
    setSessionVariables(sessionId, setMap);
    deleteSessionVariables(sessionId, FluentIterable.from(deleteMap.keySet()).toArray(String.class));
  }

  @SuppressWarnings("unused")
  private String mapToString(Map<String, String> map) {
    StringBuffer names = new StringBuffer();

    for (String name : map.keySet()) {
      names.append(name + " ");
    }

    return names.toString();
  }

  @Override
  public void destroy() {
  }

  protected String getCookieValue(HttpServletRequest req, String cookieName) {
    Cookie[] cookies = req.getCookies();

    if (cookies != null) {
      for (Cookie cookie : cookies) {
        if (cookie.getName().equals(cookieName)) {
          return cookie.getValue();
        }
      }
    }

    return "";
  }

  // [START deleteSessionVariables]
  /**
   * Delete a value stored in the project's datastore.
   * @param sessionId Request from which the session is extracted.
   */
  protected void deleteSessionVariables(String sessionId, String... varNames) {
    if (sessionId.equals("")) {
      return;
    }

    Key key = keyFactory.newKey(sessionId);
    Transaction transaction = datastore.newTransaction();

    try {
      Entity stateEntity = transaction.get(key);

      if (stateEntity != null) {
        Entity.Builder builder = Entity.newBuilder(stateEntity);
        StringBuilder delNames = new StringBuilder();

        for (String varName : varNames) {
          delNames.append(varName + " ");
          builder = builder.remove(varName);
        }

        datastore.update(builder.build());
      }

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

  }
  // [END deleteSessionVariables]

  protected void deleteSessionWithValue(String varName, String varValue) {
    Transaction transaction = datastore.newTransaction();

    try {
      Query<Entity> query = Query.newEntityQueryBuilder().setKind("SessionVariable")
          .setFilter(PropertyFilter.eq(varName, varValue)).build();

      QueryResults<Entity> resultList = transaction.run(query);

      while (resultList.hasNext()) {
        Entity stateEntity = resultList.next();
        transaction.delete(stateEntity.getKey());
      }

      transaction.commit();

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

  }

  // [START setSessionVariables]
  /**
   * Stores the state value in each key-value pair in the project's datastore.
   * @param sessionId Request from which to extract session.
   * @param varName the name of the desired session variable
   * @param varValue the value of the desired session variable
   */
  protected void setSessionVariables(String sessionId, Map<String, String> setMap) {

    if (sessionId.equals("")) {
      return;
    }

    Key key = keyFactory.newKey(sessionId);
    Transaction transaction = datastore.newTransaction();
    DateTime dt = DateTime.now(DateTimeZone.UTC);
    dt.toString(dtf);

    try {
      Entity stateEntity = transaction.get(key);
      Entity.Builder seBuilder;

      if (stateEntity == null) {
        seBuilder = Entity.newBuilder(key);
      } else {
        seBuilder = Entity.newBuilder(stateEntity);
      }

      for (String varName : setMap.keySet()) {
        seBuilder.set(varName, setMap.get(varName));
      }

      transaction.put(seBuilder.set("lastModified", dt.toString(dtf)).build());
      transaction.commit();

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

  }
  // [END setSessionVariables]

  // [START loadSessionVariables]
  /**
   * Take an HttpServletRequest, and copy all of the current session variables over to it
   * @param req Request from which to extract session.
   * @return a map of strings containing all the session variables loaded or an empty map.
   */
  protected Map<String, String> loadSessionVariables(HttpServletRequest req) throws ServletException {
    Map<String, String> datastoreMap = new HashMap<>();
    String sessionId = getCookieValue(req, "bookshelfSessionId");

    if (sessionId.equals("")) {
      return datastoreMap;
    }

    Key key = keyFactory.newKey(sessionId);
    Transaction transaction = datastore.newTransaction();

    try {
      Entity stateEntity = transaction.get(key);
      StringBuilder logNames = new StringBuilder();

      if (stateEntity != null) {

        for (String varName : stateEntity.getNames()) {
          req.getSession().setAttribute(varName, stateEntity.getString(varName));
          datastoreMap.put(varName, stateEntity.getString(varName));
          logNames.append(varName + " ");
        }

      }

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

    return datastoreMap;

  }
  // [END loadSessionVariables]
}
According to Google App engine Documentation you need to define below configuration in appengine-web.xml 

 App Engine includes an implementation of sessions, using the servlet session interface. The implementation stores session data in the App Engine datastore for persistence, and also uses memcache for speed. As with most other servlet containers, the session attributes that are set with `session.setAttribute()` during the request are persisted at the end of the request. 


  <sessions-enabled>true</sessions-enabled>
  <async-session-persistence enabled="true" />

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM