简体   繁体   English

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

[英]Google App Engine session changes between instances

A visual aid to help you understand the issue. 视觉辅助工具,可帮助您了解问题。

I am baffled because this issue has occurred 100% of the time on every application that I've tried to launch, and yet I can't find any information about this issue, and I can't find anybody else on the Internet who has had this issue. 我很困惑,因为在我尝试启动的每个应用程序中,此问题都已经100%发生了,但是我找不到有关此问题的任何信息,而且我在Internet上找不到其他人有这个问题。

I've been learning web development with Java using Google Cloud Platform for about a year now, and I have a problem that I've been stuck on for a couple months now. 我已经学习使用Google Cloud Platform使用Java进行Web开发大约一年了,但有一个问题,我已经坚持了几个月。 I can't seem to find ANY mention of this problem anywhere online. 我似乎找不到任何地方在线提到此问题。

I have a web application built on Java servlets, and it uses sessions to keep track of the logged-in user. 我有一个基于Java Servlet构建的Web应用程序,它使用会话来跟踪已登录的用户。 Everything in the application works fine, but when I deployed the app to App Engine, the user kept randomly getting logged in and out with subsequent server requests. 应用程序中的所有内容都可以正常运行,但是当我将应用程序部署到App Engine时,用户会继续随随后的服务器请求随机登录和注销。 I watched in the development window and saw that the session ID kept changing between two different values. 我在开发窗口中查看,发现会话ID在两个不同的值之间不断变化。

Because app engine uses multiple instances of an app for scaling with traffic and resource management, it may receive a request on one instance and send it back to the client from another instance. 由于应用程序引擎使用应用程序的多个实例进行流量和资源管理扩展,因此它可能会在一个实例上收到请求,然后将其从另一个实例发送回客户端。 This makes no noticeable difference on the client side, but I discovered that each instance had a different session ID for my client, causing different session attributes to get trapped in each instance. 这在客户端没有什么明显的区别,但是我发现每个实例的客户端都有不同的会话ID,从而导致每个实例都陷入了不同的会话属性。

Surely enough, when I reduced the app down to one instance, my sessions worked fine. 当然,当我将应用程序缩减为一个实例时,我的会话运行良好。 But that won't work in production, I need my app to be able to scale with traffic and utilize the app engine resources as they were intended. 但这在生产环境中行不通,我需要我的应用程序能够随流量扩展并按预期使用应用程序引擎资源。

Google CLAIMS that sessions on app engine are handled automatically using Memcache and Datastore, and that all you need to do to access the session is use request.getSession() , so I don't understand why this problem is occurring. Google要求使用Memcache和数据存储区自动处理app引擎上的会话,而访问该会话所需要做的只是使用request.getSession() ,所以我不明白为什么会发生此问题。 If I can't understand the problem, I will never find a solution :( 如果我不明白这个问题,我将永远找不到解决方案:(

[Edit]: The app uses a pre-existing Web Filter called DatastoreSessionFilter to keep track of the session variables using cookies and datastore, but it doesn't seem to be working properly, since my session variables are getting trapped in individual instances. [编辑]:该应用程序使用一个预先存在的Web过滤器DatastoreSessionFilter来跟踪使用cookie和数据存储区的会话变量,但由于我的会话变量被困在各个实例中,因此似乎无法正常工作。 Here is the DatastoreSessionFilter class: 这是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