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

时间:2017-10-23 11:07:15

标签: java google-app-engine servlets cookies httpsession

A visual aid to help you understand the issue.

我很困惑,因为这个问题已经100%发生在我试图启动的每个应用程序上,但我找不到任何关于这个问题的信息,我找不到任何人在互联网上遇到此问题的其他人。

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

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

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

当然,当我将应用程序缩减为一个实例时,我的会话工作正常。但这在生产中不起作用,我需要我的应用程序能够扩展流量并按预期使用应用引擎资源。

Google CLAIMS使用Memcache和Datastore自动处理应用引擎上的会话,并且您访问会话所需要做的就是使用request.getSession(),因此我不明白为什么会出现此问题。如果我无法理解这个问题,我将永远找不到解决方案:(

[编辑]: 该应用程序使用一个名为DatastoreSessionFilter的预先存在的Web过滤器来跟踪使用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]
}

1 个答案:

答案 0 :(得分:0)

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" />