Updates to the SPDY push implementation.

This commit is contained in:
Simone Bordet 2012-05-04 18:43:24 +02:00
parent 3ce07230d5
commit f75a9d83f1
6 changed files with 74 additions and 20 deletions

View File

@ -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));
}

View File

@ -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<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders);
public static class None implements PushStrategy
{
@Override
public Set<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders)
{
return Collections.emptySet();
}
}
}

View File

@ -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;
/**
* <p>A SPDY push strategy that auto-populates push metadata based on referrer URLs.</p>
* <p>A typical request for a main resource such as <tt>index.html</tt> is immediately
* followed by a number of requests for associated resources. Associated resource requests
* will have a <tt>Referer</tt> HTTP header that points to <tt>index.html</tt>, which we
* use to link the associated resource to the main resource.</p>
* <p>However, also following a hyperlink generates a HTTP request with a <tt>Referer</tt>
* HTTP header that points to <tt>index.html</tt>; therefore main resources and associated
* resources must be distinguishable.</p>
* <p>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.</p>
* <p>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.</p>
*
*
* 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<String> 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<String> pushResources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
Set<String> existing = resources.putIfAbsent(referrer, pushResources);

View File

@ -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<Runnable> tasks = new LinkedList<>();
private final BlockingQueue<DataInfo> 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<String> 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<Stream>()
{
@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

View File

@ -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);

View File

@ -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());
}
};
}