【发布时间】:2018-04-03 21:55:40
【问题描述】:
A visual aid to help you understand the issue.
我很困惑,因为我尝试启动的每个应用程序 100% 都会出现此问题,但我找不到有关此问题的任何信息,也找不到任何人互联网上遇到此问题的其他人。
我已经使用 Google Cloud Platform 学习使用 Java 进行 Web 开发大约一年了,但我有一个问题已经困扰了几个月。我似乎在网上的任何地方都找不到关于这个问题的任何提及。
我有一个基于 Java servlet 构建的 Web 应用程序,它使用会话来跟踪登录用户。应用程序中的一切工作正常,但是当我将应用程序部署到 App Engine 时,用户不断随机登录和退出后续服务器请求。我在开发窗口中观察,发现会话 ID 在两个不同的值之间不断变化。
因为应用引擎使用应用的多个实例来扩展流量和资源管理,它可能会在一个实例上接收请求,然后将其从另一个实例发送回客户端。这在客户端没有显着差异,但我发现每个实例对我的客户端都有不同的会话 ID,导致不同的会话属性被困在每个实例中。
果然,当我将应用程序减少到一个实例时,我的会话运行良好。但这在生产环境中行不通,我需要我的应用能够随流量扩展并按预期利用应用引擎资源。
Google 声称应用引擎上的会话是使用 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]
}
【问题讨论】:
-
你实现了什么样的登录机制?自定义实现?
-
您如何管理您的会话?预先存在的代码(如果是,是哪个),还是您自己编写的?
-
我建议您将此信息存储在某个数据库中。仅将该信息存储在 mem-cache 或 session-cache 中,如果不存在,您可以从数据库中检索。
-
感谢您的回复。我使用电子邮件和密码登录表单编写了一个自定义登录机制。它使用 JWT(Java Web 令牌)为用户会话创建令牌。至于会话管理
-
关于会话管理,我使用的是 Google 编写的预先存在的网络过滤器 DatastoreSessionFilter.java。我相信它应该是负责使用数据存储和内存缓存存储/检索会话变量的类,但它似乎不起作用。我将编辑我的问题以包含 DatastoreSessionFilter 类。
标签: java google-app-engine servlets cookies httpsession