From f75a9d83f1d05d6f155ca9f677fb496115d4296c Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 4 May 2012 18:43:24 +0200 Subject: [PATCH] Updates to the SPDY push implementation. --- .../spdy/http/HTTPSPDYServerConnector.java | 3 +- .../eclipse/jetty/spdy/http/PushStrategy.java | 10 ++++ .../jetty/spdy/http/ReferrerPushStrategy.java | 46 ++++++++++++++++--- .../http/ServerHTTPSPDYAsyncConnection.java | 25 ++++++---- .../ServerHTTPSPDYAsyncConnectionFactory.java | 8 ++-- .../jetty/spdy/http/AbstractHTTPSPDYTest.java | 2 +- 6 files changed, 74 insertions(+), 20 deletions(-) diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java index b8359cde9c0..63544a0356b 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; public class HTTPSPDYServerConnector extends SPDYServerConnector { private final AsyncConnectionFactory defaultConnectionFactory; + private final PushStrategy pushStrategy = new PushStrategy.None(); public HTTPSPDYServerConnector() { @@ -47,7 +48,7 @@ public class HTTPSPDYServerConnector extends SPDYServerConnector { super.doStart(); // Override the "spdy/2" protocol by handling HTTP over SPDY - putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this)); + putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, pushStrategy)); // Add the "http/1.1" protocol for browsers that do not support NPN putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(this)); } diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java index 14032f7b62d..780a5b22d1d 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.spdy.http; +import java.util.Collections; import java.util.Set; import org.eclipse.jetty.spdy.api.Headers; @@ -27,4 +28,13 @@ import org.eclipse.jetty.spdy.api.Stream; public interface PushStrategy { public Set apply(Stream stream, Headers requestHeaders, Headers responseHeaders); + + public static class None implements PushStrategy + { + @Override + public Set apply(Stream stream, Headers requestHeaders, Headers responseHeaders) + { + return Collections.emptySet(); + } + } } diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java index 1f6fb95667c..ef6cef2a3be 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.eclipse.jetty.spdy.http; import java.util.ArrayList; @@ -13,8 +29,24 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** + *

A SPDY push strategy that auto-populates push metadata based on referrer URLs.

+ *

A typical request for a main resource such as index.html is immediately + * followed by a number of requests for associated resources. Associated resource requests + * will have a Referer HTTP header that points to index.html, which we + * use to link the associated resource to the main resource.

+ *

However, also following a hyperlink generates a HTTP request with a Referer + * HTTP header that points to index.html; therefore main resources and associated + * resources must be distinguishable.

+ *

This class distinguishes associated resources by their URL path suffix. + * CSS stylesheets, images and JavaScript files have recognizable URL path suffixes that + * are classified as associated resources.

+ *

Note however, that CSS stylesheets may refer to images, and the CSS image request + * will have the CSS stylesheet as referrer, so there is some degree of recursion that + * needs to be handled.

+ * + * * TODO: this class is kind-of leaking since the resources map is always adding entries - * TODO: although these entries will be limited by the application pages + * TODO: although these entries will be limited by the number of application pages. * TODO: however, there is no ConcurrentLinkedHashMap yet in JDK (there is in Guava though) * TODO: so we cannot use the built-in LRU features of LinkedHashMap */ @@ -31,17 +63,17 @@ public class ReferrerPushStrategy implements PushStrategy String url = requestHeaders.get("url").value(); if (!hasQueryString(url)) { - if (isMainResource(url)) + if (isMainResource(url, responseHeaders)) { return pushResources(url); } - else if (isPushResource(url)) + else if (isPushResource(url, responseHeaders)) { String referrer = requestHeaders.get("referer").value(); Set pushResources = resources.get(referrer); if (pushResources == null || !pushResources.contains(url)) { - buildPushResources(url, referrer); + buildMetadata(url, referrer); } else { @@ -57,13 +89,13 @@ public class ReferrerPushStrategy implements PushStrategy return url.contains("?"); } - private boolean isMainResource(String url) + private boolean isMainResource(String url, Headers responseHeaders) { // TODO return false; } - private boolean isPushResource(String url) + private boolean isPushResource(String url, Headers responseHeaders) { // TODO return false; @@ -77,7 +109,7 @@ public class ReferrerPushStrategy implements PushStrategy return Collections.unmodifiableSet(pushResources); } - private void buildPushResources(String url, String referrer) + private void buildMetadata(String url, String referrer) { Set pushResources = Collections.newSetFromMap(new ConcurrentHashMap()); Set existing = resources.putIfAbsent(referrer, pushResources); diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java index a0699377067..dd05ab6fb42 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java @@ -48,6 +48,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.spdy.SPDYAsyncConnection; import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.Handler; import org.eclipse.jetty.spdy.api.Headers; import org.eclipse.jetty.spdy.api.ReplyInfo; import org.eclipse.jetty.spdy.api.Stream; @@ -64,6 +65,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem private final Queue tasks = new LinkedList<>(); private final BlockingQueue dataInfos = new LinkedBlockingQueue<>(); private final SPDYAsyncConnection connection; + private final PushStrategy pushStrategy; private final Stream stream; private Headers headers; // No need for volatile, guarded by state private DataInfo dataInfo; // No need for volatile, guarded by state @@ -71,10 +73,11 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem private volatile State state = State.INITIAL; private boolean dispatched; // Guarded by synchronization on tasks - public ServerHTTPSPDYAsyncConnection(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, Stream stream) + public ServerHTTPSPDYAsyncConnection(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream) { super(connector, endPoint, server); this.connection = connection; + this.pushStrategy = pushStrategy; this.stream = stream; getParser().setPersistent(true); } @@ -380,17 +383,23 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem Set pushResources = pushStrategy.apply(stream, this.headers, replyInfo.getHeaders()); for (String url : pushResources) { - Headers pushHeaders = new Headers(); + final Headers pushHeaders = new Headers(); pushHeaders.put("method", "GET"); pushHeaders.put("url", url); pushHeaders.put("version", "HTTP/1.1"); Headers.Header acceptEncoding = headers.get("accept-encoding"); if (acceptEncoding != null) pushHeaders.put(acceptEncoding); - Stream pushStream = stream.syn(new SynInfo(pushHeaders, true)); - Synchronous connection = new Synchronous(getConnector(), getEndPoint(), getServer(), , pushStream); - connection.beginRequest(pushHeaders); - connection.endRequest(); + stream.syn(new SynInfo(pushHeaders, false), getMaxIdleTime(), TimeUnit.MILLISECONDS, new Handler.Adapter() + { + @Override + public void completed(Stream pushStream) + { + Synchronous pushConnection = new Synchronous(getConnector(), getEndPoint(), getServer(), connection, pushStrategy, pushStream); + pushConnection.beginRequest(pushHeaders); + pushConnection.endRequest(); + } + }); } } } @@ -709,9 +718,9 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem private static class Synchronous extends ServerHTTPSPDYAsyncConnection { - private Synchronous(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, Stream stream) + private Synchronous(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream) { - super(connector, endPoint, server, connection, stream); + super(connector, endPoint, server, connection, pushStrategy, stream); } @Override diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java index 93fff9c8467..160ee0ffd50 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java @@ -42,11 +42,13 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect private static final Logger logger = Log.getLogger(ServerHTTPSPDYAsyncConnectionFactory.class); private final Connector connector; + private final PushStrategy pushStrategy; - public ServerHTTPSPDYAsyncConnectionFactory(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler, Connector connector) + public ServerHTTPSPDYAsyncConnectionFactory(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler, Connector connector, PushStrategy pushStrategy) { super(version, bufferPool, threadPool, scheduler); this.connector = connector; + this.pushStrategy = pushStrategy; } @Override @@ -77,8 +79,8 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect HTTPSPDYAsyncEndPoint asyncEndPoint = new HTTPSPDYAsyncEndPoint(endPoint, stream); ServerHTTPSPDYAsyncConnection connection = new ServerHTTPSPDYAsyncConnection(connector, - asyncEndPoint, connector.getServer(), - (SPDYAsyncConnection)endPoint.getConnection(), stream); + asyncEndPoint, connector.getServer(), (SPDYAsyncConnection)endPoint.getConnection(), + pushStrategy, stream); asyncEndPoint.setConnection(connection); stream.setAttribute(CONNECTION_ATTRIBUTE, connection); diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java index 3839ff64856..84dd3271a5b 100644 --- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java @@ -72,7 +72,7 @@ public abstract class AbstractHTTPSPDYTest @Override protected AsyncConnectionFactory getDefaultAsyncConnectionFactory() { - return new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this); + return new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, new PushStrategy.None()); } }; }