From: Marc Englund Date: Tue, 1 Sep 2009 11:51:58 +0000 (+0000) Subject: Appengine fixes; This version uses GAE sessions, but attempts to keep synchronization... X-Git-Tag: 6.7.0.beta1~2550 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=ee33e544f9b165607f409602e8f6aa0b1f89515a;p=vaadin-framework.git Appengine fixes; This version uses GAE sessions, but attempts to keep synchronization using memcache. For #2835 svn changeset:8614/svn branch:6.1 --- diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index e1a96e7413..652179b7dc 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -350,6 +350,15 @@ application com.vaadin.tests.book.ChatApplication + + + + GAESyncTest + com.vaadin.terminal.gwt.server.GAEApplicationServlet + + application + com.vaadin.tests.appengine.GAESyncTest + @@ -503,6 +512,11 @@ ChatServlet /chat/* + + + + GAESyncTest + /gaesynctest/* diff --git a/build/build.xml b/build/build.xml index 2436527241..99c8eba8ed 100644 --- a/build/build.xml +++ b/build/build.xml @@ -239,6 +239,7 @@ + @@ -854,7 +855,7 @@ - + diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 69174587a6..31ee4dad81 100755 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -292,14 +292,15 @@ public class ApplicationConnection { return (activeRequests > 0); } - private void makeUidlRequest(String requestData, boolean repaintAll, - boolean forceSync, boolean analyzeLayouts) { + private void makeUidlRequest(final String requestData, + final boolean repaintAll, final boolean forceSync, + final boolean analyzeLayouts) { startRequest(); // Security: double cookie submission pattern - requestData = uidl_security_key + VAR_BURST_SEPARATOR + requestData; + final String rd = uidl_security_key + VAR_BURST_SEPARATOR + requestData; - console.log("Making UIDL Request with params: " + requestData); + console.log("Making UIDL Request with params: " + rd); String uri = getAppUri() + "UIDL" + configuration.getPathInfo(); if (repaintAll) { // collect some client side data that will be sent to server on @@ -331,13 +332,14 @@ public class ApplicationConnection { } if (!forceSync) { + boolean success = false; final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri); // TODO enable timeout // rb.setTimeoutMillis(timeoutMillis); rb.setHeader("Content-Type", "text/plain;charset=utf-8"); try { - rb.sendRequest(requestData, new RequestCallback() { + rb.sendRequest(rd, new RequestCallback() { public void onError(Request request, Throwable exception) { showCommunicationError(exception.getMessage()); endRequest(); @@ -358,6 +360,21 @@ public class ApplicationConnection { showCommunicationError("Invalid status code 0 (server down?)"); return; // TODO could add more cases + case 503: + // We'll assume msec instead of the usual seconds + int delay = Integer.parseInt(response + .getHeader("Retry-After")); + console.log("503, retrying in " + delay + "msec"); + (new Timer() { + @Override + public void run() { + activeRequests--; + makeUidlRequest(requestData, repaintAll, + forceSync, analyzeLayouts); + } + }).schedule(delay); + return; + } if ("init".equals(uidl_security_key)) { // Read security key @@ -415,7 +432,7 @@ public class ApplicationConnection { syncSendForce(((HTTPRequestImpl) GWT.create(HTTPRequestImpl.class)) .createXmlHTTPRequest(), uri + "&" + PARAM_UNLOADBURST - + "=1", requestData); + + "=1", rd); } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 0640477e5c..af1847a7b7 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -475,12 +475,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet { .endTransaction(application, request); } - // Work-around for GAE session problem. Explicitly touch session so - // it is re-serialized. - HttpSession session = request.getSession(false); - if (session != null) { - session.setAttribute("sessionUpdated", new Date().getTime()); - } } } @@ -879,7 +873,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet { * @return true if an DownloadStream was sent to the client, false otherwise * @throws IOException */ - private boolean handleURI(CommunicationManager applicationManager, + protected boolean handleURI(CommunicationManager applicationManager, Window window, HttpServletRequest request, HttpServletResponse response) throws IOException { // Handles the URI @@ -1085,11 +1079,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet { } } - private enum RequestType { + enum RequestType { FILE_UPLOAD, UIDL, OTHER; } - private RequestType getRequestType(HttpServletRequest request) { + protected RequestType getRequestType(HttpServletRequest request) { if (isFileUploadRequest(request)) { return RequestType.FILE_UPLOAD; } else if (isUIDLRequest(request)) { @@ -1812,8 +1806,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet { String path = getRequestPathInfo(request); // Main window as the URI is empty - if (!(path == null || path.length() == 0 || path.equals("/") || path - .startsWith("/APP/"))) { + if (!(path == null || path.length() == 0 || path.equals("/"))) { + if (path.startsWith("/APP/")) { + // Use main window for application resources + return application.getMainWindow(); + } String windowName = null; if (path.charAt(0) == '/') { path = path.substring(1); diff --git a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java new file mode 100644 index 0000000000..13d01cfb49 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java @@ -0,0 +1,96 @@ +package com.vaadin.terminal.gwt.server; + +import java.io.IOException; +import java.util.Date; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.google.appengine.api.memcache.Expiration; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; +import com.google.apphosting.api.DeadlineExceededException; +import com.vaadin.ui.Window; + +public class GAEApplicationServlet extends ApplicationServlet { + + private static final long serialVersionUID = 2179597952818898526L; + + private static final String MUTEX_BASE = "vaadin.gae.mutex."; + // Note: currently interpreting Retry-After as ms, not sec + private static final int RETRY_AFTER_MILLISECONDS = 100; + private static final int KEEP_MUTEX_MILLISECONDS = 100; + + @Override + protected void service(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + + boolean locked = false; + MemcacheService memcache = null; + String mutex = null; + try { + RequestType requestType = getRequestType(request); + if (requestType == RequestType.UIDL) { + memcache = MemcacheServiceFactory.getMemcacheService(); + mutex = MUTEX_BASE + request.getSession().getId(); + // try to get lock + locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40), + MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); + if (!locked) { + // could not obtain lock, tell client to retry + request.setAttribute("noSerialize", new Object()); + response + .setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + // Note: currently interpreting Retry-After as ms, not sec + response.setHeader("Retry-After", "" + + RETRY_AFTER_MILLISECONDS); + return; + } + + } + + super.service(request, response); + + if (request.getAttribute("noSerialize") == null) { + // Explicitly touch session so it is re-serialized. + HttpSession session = request.getSession(false); + if (session != null) { + session + .setAttribute("sessionUpdated", new Date() + .getTime()); + } + } + + } catch (DeadlineExceededException e) { + System.err.println("DeadlineExceeded for " + + request.getSession().getId()); + // TODO i18n? + criticalNotification( + request, + response, + "Deadline Exceeded", + "I'm sorry, but the operation took too long to complete. We'll try reloading to see where we're at, please take note of any unsaved data...", + "", null); + } finally { + // "Next, please!" + if (locked) { + memcache.delete(mutex, KEEP_MUTEX_MILLISECONDS); + } + + } + } + + protected boolean handleURI(CommunicationManager applicationManager, + Window window, HttpServletRequest request, + HttpServletResponse response) throws IOException { + + if (super.handleURI(applicationManager, window, request, response)) { + request.setAttribute("noSerialize", new Object()); + return true; + } + return false; + } + +} diff --git a/src/com/vaadin/tests/appengine/GAESyncTest.java b/src/com/vaadin/tests/appengine/GAESyncTest.java new file mode 100644 index 0000000000..0f5c85b239 --- /dev/null +++ b/src/com/vaadin/tests/appengine/GAESyncTest.java @@ -0,0 +1,152 @@ +package com.vaadin.tests.appengine; + +import com.google.apphosting.api.DeadlineExceededException; +import com.vaadin.Application; +import com.vaadin.data.Property; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.terminal.ClassResource; +import com.vaadin.terminal.DownloadStream; +import com.vaadin.ui.Button; +import com.vaadin.ui.Embedded; +import com.vaadin.ui.GridLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; +import com.vaadin.ui.Window; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Window.Notification; + +public class GAESyncTest extends Application { + + /** + * + */ + private static final long serialVersionUID = -3724319151122707926l; + + @Override + public void init() { + setMainWindow(new IntrWindow(this)); + + } + + @Override + public void terminalError(com.vaadin.terminal.Terminal.ErrorEvent event) { + Throwable t = event.getThrowable(); + // Was this caused by a GAE timeout? + while (t != null) { + if (t instanceof DeadlineExceededException) { + getMainWindow().showNotification("Bugger!", + "Deadline Exceeded", Notification.TYPE_ERROR_MESSAGE); + return; + } + t = t.getCause(); + } + + super.terminalError(event); + + } + + private class IntrWindow extends Window { + private int n = 0; + private static final long serialVersionUID = -6521351715072191625l; + TextField tf; + Label l; + Application app; + GridLayout gl; + + private IntrWindow(Application app) { + + this.app = app; + tf = new TextField("Echo thingie"); + tf.setImmediate(true); + tf.addListener(new Property.ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + IntrWindow.this.showNotification((String) event + .getProperty().getValue()); + + } + + }); + addComponent(tf); + + l = new Label("" + n); + addComponent(l); + + { + Button b = new Button("Slow", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + try { + Thread.sleep((40000)); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + }); + addComponent(b); + } + + { + Button b = new Button("Add", new Button.ClickListener() { + + public void buttonClick(ClickEvent event) { + if (getWindow() == getApplication().getMainWindow()) { + getWindow().showNotification("main"); + try { + Thread.sleep((5000)); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + addImage(); + } + + }); + addComponent(b); + } + + gl = new GridLayout(30, 50); + addComponent(gl); + + } + + private void addImage() { + ClassResource res = new ClassResource("img1.png", app) { + + private static final long serialVersionUID = 1L; + + @Override + public DownloadStream getStream() { + try { + Thread.sleep((long) (Math.random() * 5000)); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return super.getStream(); + } + + }; + res.setCacheTime(0); + Embedded emb = new Embedded("" + n, res); + emb.setWidth("30px"); + emb.setHeight("5px"); + gl.addComponent(emb); + l.setValue("" + n++); + } + + } + + @Override + public Window getWindow(String name) { + Window w = super.getWindow(name); + if (w == null) { + w = new IntrWindow(this); + addWindow(w); + } + return w; + + } + +} diff --git a/src/com/vaadin/tests/appengine/img1.png b/src/com/vaadin/tests/appengine/img1.png new file mode 100644 index 0000000000..d249324e0c Binary files /dev/null and b/src/com/vaadin/tests/appengine/img1.png differ