Repository: wicket
Updated Branches:
  refs/heads/master 061c0e903 -> 76116aa46


WICKET-6194 PushHeaderItem handles client side cache

Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/76116aa4
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/76116aa4
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/76116aa4

Branch: refs/heads/master
Commit: 76116aa467590378ae6b5d5acc8a153246fc9ee4
Parents: 061c0e9
Author: Tobias Soloschenko <[email protected]>
Authored: Thu Aug 11 20:43:52 2016 +0200
Committer: Tobias Soloschenko <[email protected]>
Committed: Thu Aug 11 20:45:00 2016 +0200

----------------------------------------------------------------------
 .../http2/markup/head/PushHeaderItem.java       | 148 ++++++++++++++++++-
 .../src/docs/guide/http2push/http2push_1.gdoc   |  44 +++++-
 2 files changed, 183 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/76116aa4/wicket-experimental/wicket-http2/wicket-http2-core/src/main/java/org/apache/wicket/http2/markup/head/PushHeaderItem.java
----------------------------------------------------------------------
diff --git 
a/wicket-experimental/wicket-http2/wicket-http2-core/src/main/java/org/apache/wicket/http2/markup/head/PushHeaderItem.java
 
b/wicket-experimental/wicket-http2/wicket-http2-core/src/main/java/org/apache/wicket/http2/markup/head/PushHeaderItem.java
index 5e88a2f..2238c61 100644
--- 
a/wicket-experimental/wicket-http2/wicket-http2-core/src/main/java/org/apache/wicket/http2/markup/head/PushHeaderItem.java
+++ 
b/wicket-experimental/wicket-http2/wicket-http2-core/src/main/java/org/apache/wicket/http2/markup/head/PushHeaderItem.java
@@ -16,6 +16,11 @@
  */
 package org.apache.wicket.http2.markup.head;
 
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
@@ -27,19 +32,26 @@ import org.apache.wicket.Page;
 import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.http2.Http2Settings;
 import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.IRequestHandler;
 import org.apache.wicket.request.Request;
 import org.apache.wicket.request.Response;
 import org.apache.wicket.request.Url;
 import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebRequest;
+import org.apache.wicket.request.http.WebResponse;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.apache.wicket.util.collections.ConcurrentHashSet;
+import org.apache.wicket.util.time.Time;
 
 /**
  * A push header item to be used in the http/2 context and to reduce the 
latency of the web
- * application
+ * application. Follow these steps for your page:<br><br>
+ * - Override the setHeaders method and don't call super.setHeaders to disable 
caching<br>
+ * - Get the page request / response and store them as transient fields that 
are given into the PushHeaderItem<br>
+ * - Ensure a valid https connection (not self signed), because otherwise no 
caching information are accepted from Chrome or other browsers
  * 
  * @author Tobias Soloschenko
  *
@@ -49,6 +61,12 @@ public class PushHeaderItem extends HeaderItem
        private static final long serialVersionUID = 1L;
 
        /**
+        * The header date format for if-modified-since / last-modified
+        */
+       private static final SimpleDateFormat headerDateFormat = new 
SimpleDateFormat(
+               "EEE, dd MMM yyyy HH:mm:ss zzz");
+
+       /**
         * The http2 protocol string
         */
        public static final String HTTP2_PROTOCOL = "http/2";
@@ -62,6 +80,47 @@ public class PushHeaderItem extends HeaderItem
         * The URLs of resources to be pushed to the client
         */
        private Set<String> urls = new ConcurrentHashSet<String>(new 
TreeSet<String>());
+       /**
+        * The web response of the page to apply the caching information to
+        */
+       private WebResponse pageWebResponse;
+
+       /**
+        * The web request of the page to get the caching information from
+        */
+       private WebRequest pageWebRequest;
+
+       /**
+        * The page to get the modification time of
+        */
+       private Page page;
+
+       /**
+        * Creates a push header item based on the given page and the 
corresponding page request / page
+        * response. To get the request and response
+        * 
+        * 
+        * @param page
+        *            the page this header item is applied to
+        * @param pageRequest
+        *            the page request this header item is applied to
+        * @param pageResponse
+        *            the page response this header item is applied to
+        */
+       public PushHeaderItem(Page page, Request pageRequest, Response 
pageResponse)
+       {
+               if (page == null || !(page instanceof WebPage) || pageResponse 
== null ||
+                       !(pageResponse instanceof WebResponse))
+               {
+                       throw new WicketRuntimeException(
+                               "Please hand over the web page, the web 
response and the web request to the push header item like \"new 
PushHeaderItem(this, yourWebPageReponse, yourWebPageRequest)\" - " +
+                                       "The webPageResponse / webPageRequest 
can be obtained via \"getRequestCycle().getResponse()\" and placed into the 
page as field " +
+                                       "\"private transient Response 
webPageResponse;\" / \"private transient Request webPageRequest;\"");
+               }
+               this.pageWebResponse = (WebResponse)pageResponse;
+               this.pageWebRequest = (WebRequest)pageRequest;
+               this.page = page;
+       }
 
        /**
         * Uses the URLs that has already been pushed to the client to ensure 
not to push them again
@@ -78,23 +137,102 @@ public class PushHeaderItem extends HeaderItem
        }
 
        /**
+        * Gets the time the page of this header item has been modified
+        * 
+        * @return the time the page of this header item has been modified
+        */
+       private Time getPageModificationTime()
+       {
+               URL resource = 
page.getClass().getResource(page.getClass().getSimpleName() + ".html");
+               if (resource == null)
+               {
+                       throw new WicketRuntimeException(
+                               "The markup to the page couldn't be found: " + 
page.getClass().getName());
+               }
+               try
+               {
+                       return Time.valueOf(new 
Date(resource.openConnection().getLastModified()));
+               }
+               catch (IOException e)
+               {
+                       throw new WicketRuntimeException(
+                               "The time couln't be determined of the markup 
file of the page: " +
+                                       page.getClass().getName(),
+                               e);
+               }
+       }
+
+       /**
+        * Applies the cache header item to the response
+        */
+       private void applyPageCacheHeader()
+       {
+               Time pageModificationTime = getPageModificationTime();
+               // check modification of page html
+               pageWebResponse.setLastModifiedTime(pageModificationTime);
+               pageWebResponse.setDateHeader("Expires", pageModificationTime);
+               pageWebResponse.setHeader("Cache-Control",
+                       "max-age=31536000, public, must-revalidate, 
proxy-revalidate");
+               pageWebResponse.setHeader("Pragma", "public");
+       }
+
+       /**
         * Pushes the previously created URLs to the client
         */
        @Override
        public void render(Response response)
        {
+               // applies the caching header to the actual page request
+               applyPageCacheHeader();
+               
                HttpServletRequest request = 
getContainerRequest(RequestCycle.get().getRequest());
                // Check if the protocol is http/2 or http/2.0 to only push the 
resources in this case
                if (isHttp2(request))
                {
-                       // Receives the vendor specific push builder
-                       Http2Settings http2Settings = 
Http2Settings.Holder.get(Application.get());
-                       PushBuilder pushBuilder = 
http2Settings.getPushBuilder();
-                       pushBuilder.push(request, urls.toArray(new 
String[urls.size()]));
+                       try
+                       {
+                               Time pageModificationTime = 
getPageModificationTime();
+                               String ifModifiedSinceHeader = 
pageWebRequest.getHeader("If-Modified-Since");
+
+                               if (ifModifiedSinceHeader != null)
+                               {
+                                       Time ifModifiedSinceFromRequestTime = 
Time
+                                               
.valueOf(headerDateFormat.parse(ifModifiedSinceHeader));
+                                       // If the client modification time is 
before the page modification time -
+                                       // don't push
+                                       if 
(ifModifiedSinceFromRequestTime.before(pageModificationTime))
+                                       {
+                                               push(request);
+                                       }
+                               }
+                               else
+                               {
+
+                               }
+                       }
+                       catch (ParseException e)
+                       {
+                               // If the If-Modified-Since time can't be 
parsed - the push handling is going to be
+                               // skipped
+                       }
                }
        }
 
        /**
+        * Pushed all URLs of this header item to the client
+        * 
+        * @param request
+        *            the request to push the URLs to
+        */
+       private void push(HttpServletRequest request)
+       {
+               // Receives the vendor specific push builder
+               Http2Settings http2Settings = 
Http2Settings.Holder.get(Application.get());
+               PushBuilder pushBuilder = http2Settings.getPushBuilder();
+               pushBuilder.push(request, urls.toArray(new 
String[urls.size()]));
+       }
+
+       /**
         * Creates a URL and pushes the resource to the client - this is only 
supported if http2 is
         * enabled
         * 

http://git-wip-us.apache.org/repos/asf/wicket/blob/76116aa4/wicket-user-guide/src/docs/guide/http2push/http2push_1.gdoc
----------------------------------------------------------------------
diff --git a/wicket-user-guide/src/docs/guide/http2push/http2push_1.gdoc 
b/wicket-user-guide/src/docs/guide/http2push/http2push_1.gdoc
index d6888fd..59d53fe 100644
--- a/wicket-user-guide/src/docs/guide/http2push/http2push_1.gdoc
+++ b/wicket-user-guide/src/docs/guide/http2push/http2push_1.gdoc
@@ -21,12 +21,48 @@ For the setup you need to follow those steps:
 </dependency>
 {code}
 
-3. Add the following PushHeaderItem to the renderHead method in your component.
+3. Use the PushHeader Item like in this example page:
 Example:
 {code}
-TestResourceReference instance = TestResourceReference.getInstance();
-response.render(CssHeaderItem.forReference(instance));
-response.render(new PushHeaderItem().push(Arrays.asList(new 
PushItem(instance))));
+public class HTTP2Page extends WebPage
+{
+       private static final long serialVersionUID = 1L;
+
+       private transient Response webPageResponse;
+
+       private transient Request webPageRequest;
+
+       public HTTP2Page()
+       {
+               webPageResponse = getRequestCycle().getResponse();
+               webPageRequest = getRequestCycle().getRequest();
+               add(new Label("label", "Label"));
+       }
+
+       @Override
+       public void renderHead(IHeaderResponse response)
+       {
+               super.renderHead(response);
+               TestResourceReference instance = 
TestResourceReference.getInstance();
+               response.render(CssHeaderItem.forReference(instance));
+               response.render(new PushHeaderItem(this, webPageResponse, 
webPageRequest)
+                   .push(Arrays.asList(new PushItem(instance))));
+       }
+
+       @Override
+       protected void setHeaders(WebResponse response)
+       {
+               // NOOP just disable caching
+       }
+}
 {code}
 
 Basically the resource is pushed before the actual response of the component 
is send to the client (browser) and because of this the client does not need to 
send an additional request.
+
+The PushHeaderItem behaves like explained in the following steps:
+* When a browser requests the page with an initial commit everything is going 
to be pushed with (200)
+* When a browser requests the page a second time resources are not pushed 
(304) not modified, because of the actual ResourceReferences headers
+* When a browser requests the page a second time and the markup of the page 
has changed everything is going to be pushed again (200)
+* When a browser requests the page a second time and resource references has 
been changed but not the page markup, all changed resource references are 
shipped via separate requests
+
+Note: Chrome does not set cache headers if the https connection is not secure 
(self signed) / valid - so ensure that a valid https connection is available 
with your server.
\ No newline at end of file

Reply via email to