Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Wilkins2014-12-05 16:47:25 +0000
committerGreg Wilkins2014-12-05 16:47:25 +0000
commit06b1efc18208d226668a38fb7d469d498bbaf196 (patch)
tree94041cf1c9bce31bb273f604053fe4fb2284e209
parent7b41e78f748e8404263929ad65fd25b1966631f3 (diff)
downloadorg.eclipse.jetty.project-06b1efc18208d226668a38fb7d469d498bbaf196.tar.gz
org.eclipse.jetty.project-06b1efc18208d226668a38fb7d469d498bbaf196.tar.xz
org.eclipse.jetty.project-06b1efc18208d226668a38fb7d469d498bbaf196.zip
Added alternate push API and example
-rw-r--r--jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java4
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java1
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java157
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Request.java104
-rw-r--r--jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java193
5 files changed, 457 insertions, 2 deletions
diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java
index 3ebe2e2b5a..51b5e1016c 100644
--- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java
+++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java
@@ -44,7 +44,7 @@ import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.servlets.PushCacheFilter;
+import org.eclipse.jetty.servlets.PushSessionCacheFilter;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -59,7 +59,7 @@ public class Http2Server
ServletContextHandler context = new ServletContextHandler(server, "/",ServletContextHandler.SESSIONS);
context.setResourceBase("/tmp");
- context.addFilter(PushCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST))
+ context.addFilter(PushSessionCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST))
.setInitParameter("ports","443,6443,8443");
context.addServlet(new ServletHolder(servlet), "/test/*");
context.addServlet(DefaultServlet.class, "/").setInitParameter("maxCacheSize","81920");
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
index 64d5bdb915..e7f721faa7 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
@@ -217,6 +217,7 @@ public class Dispatcher implements RequestDispatcher
}
}
+ @Deprecated
public void push(ServletRequest request)
{
Request baseRequest = Request.getBaseRequest(request);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java
new file mode 100644
index 0000000000..3025e83c5e
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java
@@ -0,0 +1,157 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Collection;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.util.URIUtil;
+
+
+/* ------------------------------------------------------------ */
+/**
+ *
+ */
+public class PushBuilder
+{
+ private final Request _request;
+ private final HttpFields _fields;
+ private String _method;
+ private String _queryString;
+ private String _sessionId;
+ private boolean _conditional;
+
+ public PushBuilder(Request request, HttpFields fields, String method, String queryString, String sessionId, boolean conditional)
+ {
+ super();
+ _request = request;
+ _fields = fields;
+ _method = method;
+ _queryString = queryString;
+ _sessionId = sessionId;
+ _conditional = conditional;
+ }
+
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ public void setMethod(String method)
+ {
+ _method = method;
+ }
+ public String getQueryString()
+ {
+ return _queryString;
+ }
+ public void setQueryString(String queryString)
+ {
+ _queryString = queryString;
+ }
+ public String getSessionId()
+ {
+ return _sessionId;
+ }
+ public void setSessionId(String sessionId)
+ {
+ _sessionId = sessionId;
+ }
+ public boolean isConditional()
+ {
+ return _conditional;
+ }
+ public void setConditional(boolean conditional)
+ {
+ _conditional = conditional;
+ }
+
+ public Collection<String> getHeaderNames()
+ {
+ return _fields.getFieldNamesCollection();
+ }
+
+ public String getHeader(String name)
+ {
+ return _fields.get(name);
+ }
+
+ public void setHeader(String name,String value)
+ {
+ _fields.put(name,value);
+ }
+
+ public void addHeader(String name,String value)
+ {
+ _fields.add(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Push a resource.
+ * Push a resource based on the current state of the PushBuilder. If {@link #isConditional()}
+ * is true and an etag or lastModified value is provided, then an appropriate conditional header
+ * will be generated. If an etag and lastModified value are provided only an If-None-Match header
+ * will be generated. If the builder has a session ID, then the pushed request
+ * will include the session ID either as a Cookie or as a URI parameter as appropriate.The builders
+ * query string is merged with any passed query string.
+ * @param uriInContext The URI within the current context of the resource to push.
+ * @param etag The etag for the resource or null if not available
+ * @param lastModified The last modified date of the resource or null if not available
+ * @throws IllegalArgumentException if the method set expects a request
+ * body (eg POST)
+ */
+ public void push(String uriInContext,String etag,String lastModified)
+ {
+ if (HttpMethod.POST.is(_method) || HttpMethod.PUT.is(_method))
+ throw new IllegalStateException("Bad Method "+_method);
+
+ String query=_queryString;
+ int q=uriInContext.indexOf('?');
+ if (q>=0)
+ {
+ query=uriInContext.substring(q+1)+'&'+query;
+ uriInContext=uriInContext.substring(0,q);
+ }
+
+ String path = URIUtil.addPaths(_request.getContextPath(),uriInContext);
+
+ String param=null;
+ if (_sessionId!=null && _request.isRequestedSessionIdFromURL())
+ param="jsessionid="+_sessionId;
+
+ if (_conditional)
+ {
+ if (etag!=null)
+ _fields.add(HttpHeader.IF_NONE_MATCH,etag);
+ else if (lastModified!=null)
+ _fields.add(HttpHeader.IF_MODIFIED_SINCE,lastModified);
+ }
+
+ HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),path,param,query,null);
+ MetaData.Request push = new MetaData.Request(_method,uri,_request.getHttpVersion(),_fields);
+ _request.getHttpChannel().getHttpTransport().push(push);
+
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index 04852f824a..a4f05a4c54 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -213,6 +213,110 @@ public class Request implements HttpServletRequest
{
return _input;
}
+
+ /* ------------------------------------------------------------ */
+ /** Get a PushBuilder associated with this request initialized as follows:<ul>
+ * <li>The method is initialized to "GET"</li>
+ * <li>The headers from this request are copied to the Builder, except for:<ul>
+ * <li>Conditional headers (eg. If-Modified-Since)
+ * <li>Range headers
+ * <li>Expect headers
+ * <li>Authorization headers
+ * <li>Referrer headers
+ * </ul></li>
+ * <li>If the request was Authenticated, an Authorization header will
+ * be set with a container generated token that will result in equivalent
+ * Authorization</li>
+ * <li>The query string from {@link #getQueryString()}
+ * <li>The {@link #getRequestedSessionId()} value, unless at the time
+ * of the call {@link #getSession(boolean)}
+ * has previously been called to create a new {@link HttpSession}, in
+ * which case the new session ID will be used as the PushBuilders
+ * requested session ID.</li>
+ * <li>The source of the requested session id will be the same as for
+ * this request</li>
+ * <li>The builders Referer header will be set to {@link #getRequestURL()}
+ * plus any {@link #getQueryString()} </li>
+ * <li>If {@link HttpServletResponse#addCookie(Cookie)} has been called
+ * on the associated response, then a corresponding Cookie header will be added
+ * to the PushBuilder, unless the {@link Cookie#getMaxAge()} is <=0, in which
+ * case the Cookie will be removed from the builder.</li>
+ * <li>If this request has has the conditional headers If-Modified-Since or
+ * If-None-Match then the {@link PushBuilder#isConditional()} header is set
+ * to true.
+ * </ul>
+ *
+ * <p>Each call to getPushBuilder() will return a new instance
+ * of a PushBuilder based off this Request. Any mutations to the
+ * returned PushBuilder are not reflected on future returns.
+ * @return A new PushBuilder or null if push is not supported
+ */
+ public PushBuilder getPushBuilder()
+ {
+ HttpFields fields = new HttpFields(getHttpFields().size()+5);
+ boolean conditional=false;
+ UserIdentity user_identity=null;
+ Authentication authentication=null;
+
+ for (HttpField field : getHttpFields())
+ {
+ HttpHeader header = field.getHeader();
+ if (header==null)
+ fields.add(field);
+ else
+ {
+ switch(header)
+ {
+ case IF_MATCH:
+ case IF_RANGE:
+ case IF_UNMODIFIED_SINCE:
+ case RANGE:
+ case EXPECT:
+ case REFERER:
+ case COOKIE:
+ continue;
+
+ case AUTHORIZATION:
+ user_identity=getUserIdentity();
+ authentication=_authentication;
+ continue;
+
+ case IF_NONE_MATCH:
+ case IF_MODIFIED_SINCE:
+ conditional=true;
+ continue;
+
+ default:
+ fields.add(field);
+ }
+ }
+ }
+
+ String id=null;
+ try
+ {
+ HttpSession session = getSession();
+ if (session!=null)
+ {
+ session.getLastAccessedTime(); // checks if session is valid
+ id=session.getId();
+ }
+ else
+ id=getRequestedSessionId();
+ }
+ catch(IllegalStateException e)
+ {
+ id=getRequestedSessionId();
+ }
+
+ PushBuilder builder = new PushBuilder(this,fields,getMethod(),getQueryString(),id,conditional);
+ builder.addHeader("referer",getRequestURL().toString());
+
+ // TODO process any set cookies
+ // TODO process any user_identity
+
+ return builder;
+ }
/* ------------------------------------------------------------ */
public void addEventListener(final EventListener listener)
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java
new file mode 100644
index 0000000000..1d2885cb26
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java
@@ -0,0 +1,193 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.PushBuilder;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class PushSessionCacheFilter implements Filter
+{
+ private static final String TARGET_ATTR="PushCacheFilter.target";
+ private static final Logger LOG = Log.getLogger(PushSessionCacheFilter.class);
+ private final ConcurrentMap<String, Target> _cache = new ConcurrentHashMap<>();
+
+ private long _associateDelay=5000L;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+ */
+ @Override
+ public void init(FilterConfig config) throws ServletException
+ {
+ if (config.getInitParameter("associateDelay")!=null)
+ _associateDelay=Long.valueOf(config.getInitParameter("associateDelay"));
+
+ config.getServletContext().addListener(new ServletRequestListener()
+ {
+ @Override
+ public void requestDestroyed(ServletRequestEvent sre)
+ {
+ Request request = Request.getBaseRequest(sre.getServletRequest());
+ Target target = (Target)request.getAttribute(TARGET_ATTR);
+ if (target==null)
+ return;
+
+ // Update conditional data
+ Response response = request.getResponse();
+ target._etag=response.getHttpFields().get(HttpHeader.ETAG);
+ target._lastModified=response.getHttpFields().get(HttpHeader.LAST_MODIFIED);
+
+ // Does this request have a referer?
+ String referer = request.getHttpFields().get(HttpHeader.REFERER);
+ if (referer!=null)
+ {
+ // Is the referer from this contexts?
+ HttpURI uri = new HttpURI(referer);
+ String path = uri.getPath();
+ if (request.getServerName().equals(uri.getHost()) && path.startsWith(request.getContextPath()))
+ {
+ String path_in_ctx = path.substring(request.getContextPath().length());
+ Target referer_target = _cache.get(path_in_ctx);
+ if (referer_target!=null)
+ {
+ String sessionId = request.getSession(true).getId();
+ Long last = referer_target._timestamp.get(sessionId);
+ if (last!=null && (System.currentTimeMillis()-last)<_associateDelay && !referer_target._associated.containsKey(path))
+ {
+ if (referer_target._associated.putIfAbsent(path,target)==null)
+ LOG.info("ASSOCIATE {}->{}",path_in_ctx,target._path);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void requestInitialized(ServletRequestEvent sre)
+ {
+ }
+
+ });
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
+ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+ {
+ Request baseRequest = Request.getBaseRequest(request);
+
+ // Iterating over fields is more efficient than multiple gets
+ HttpFields fields = baseRequest.getHttpFields();
+ String referer=fields.get(HttpHeader.REFERER);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} {} referer={}%n",baseRequest.getMethod(),baseRequest.getRequestURI(),referer);
+
+
+ HttpSession session = baseRequest.getSession(true);
+ String sessionId = session.getId();
+ String path = URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo());
+
+ // find the target for this resource
+ Target target = _cache.get(path);
+ if (target == null)
+ {
+ Target t=new Target(path);
+ target = _cache.putIfAbsent(path,t);
+ target = target==null?t:target;
+ }
+ target._timestamp.put(sessionId,System.currentTimeMillis());
+ request.setAttribute(TARGET_ATTR,target);
+
+ // push any associated resources
+ if (target._associated.size()>0)
+ {
+ PushBuilder builder = baseRequest.getPushBuilder();
+ if (!session.isNew())
+ builder.setConditional(true);
+ if (builder!=null)
+ {
+ for (Target associated : target._associated.values())
+ {
+ LOG.info("PUSH {}->{}",path,associated._path);
+ builder.push(associated._path,associated._etag,associated._lastModified);
+ }
+ }
+ }
+
+ chain.doFilter(request,response);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.Filter#destroy()
+ */
+ @Override
+ public void destroy()
+ {
+ }
+
+
+ public static class Target
+ {
+ final String _path;
+ final ConcurrentMap<String,Target> _associated = new ConcurrentHashMap<>();
+ final ConcurrentMap<String,Long> _timestamp = new ConcurrentHashMap<>();
+ volatile String _etag;
+ volatile String _lastModified;
+
+ public Target(String path)
+ {
+ _path=path;
+ }
+
+ }
+}

Back to the top