Restored server push functionality. (#8760)
* Restored server push functionality. * Moved Request.isPushSupported() to ConnectionMetaData. * Removed HttpStream.isPushSupported(). * Implemented ee10 PushBuilder. * Moved PushCacheFilterTest from core to ee10. * Duplicated PushCacheFilterTest to ee9. Signed-off-by: Simone Bordet <simone.bordet@gmail.com> Co-authored-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
parent
9cb6cc62d5
commit
e7f6f6729a
|
@ -84,7 +84,7 @@ public class HttpRequest implements Request
|
||||||
private List<HttpCookie> cookies;
|
private List<HttpCookie> cookies;
|
||||||
private Map<String, Object> attributes;
|
private Map<String, Object> attributes;
|
||||||
private List<RequestListener> requestListeners;
|
private List<RequestListener> requestListeners;
|
||||||
private BiFunction<Request, Request, Response.CompleteListener> pushListener;
|
private BiFunction<Request, Request, Response.CompleteListener> pushHandler;
|
||||||
private Supplier<HttpFields> trailers;
|
private Supplier<HttpFields> trailers;
|
||||||
private String upgradeProtocol;
|
private String upgradeProtocol;
|
||||||
private Object tag;
|
private Object tag;
|
||||||
|
@ -607,6 +607,13 @@ public class HttpRequest implements Request
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Request onPush(BiFunction<Request, Request, Response.CompleteListener> pushHandler)
|
||||||
|
{
|
||||||
|
this.pushHandler = pushHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Request onComplete(final Response.CompleteListener listener)
|
public Request onComplete(final Response.CompleteListener listener)
|
||||||
{
|
{
|
||||||
|
@ -621,26 +628,6 @@ public class HttpRequest implements Request
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Sets a listener for pushed resources.</p>
|
|
||||||
* <p>When resources are pushed from the server, the given {@code listener}
|
|
||||||
* is invoked for every pushed resource.
|
|
||||||
* The parameters to the {@code BiFunction} are this request and the
|
|
||||||
* synthesized request for the pushed resource.
|
|
||||||
* The {@code BiFunction} should return a {@code CompleteListener} that
|
|
||||||
* may also implement other listener interfaces to be notified of various
|
|
||||||
* response events, or {@code null} to signal that the pushed resource
|
|
||||||
* should be canceled.</p>
|
|
||||||
*
|
|
||||||
* @param listener a listener for pushed resource events
|
|
||||||
* @return this request object
|
|
||||||
*/
|
|
||||||
public Request pushListener(BiFunction<Request, Request, Response.CompleteListener> listener)
|
|
||||||
{
|
|
||||||
this.pushListener = listener;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Request trailersSupplier(Supplier<HttpFields> trailers)
|
public Request trailersSupplier(Supplier<HttpFields> trailers)
|
||||||
{
|
{
|
||||||
|
@ -800,9 +787,9 @@ public class HttpRequest implements Request
|
||||||
return responseListeners;
|
return responseListeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiFunction<Request, Request, Response.CompleteListener> getPushListener()
|
public BiFunction<Request, Request, Response.CompleteListener> getPushHandler()
|
||||||
{
|
{
|
||||||
return pushListener;
|
return pushHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ -420,6 +421,22 @@ public interface Request
|
||||||
*/
|
*/
|
||||||
Request onResponseFailure(Response.FailureListener listener);
|
Request onResponseFailure(Response.FailureListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets a handler for pushed resources.</p>
|
||||||
|
* <p>When resources are pushed from the server, the given {@code pushHandler}
|
||||||
|
* is invoked for every pushed resource.
|
||||||
|
* The parameters to the {@code BiFunction} are this request and the
|
||||||
|
* synthesized request for the pushed resource.
|
||||||
|
* The {@code BiFunction} should return a {@code CompleteListener} that
|
||||||
|
* may also implement other listener interfaces to be notified of various
|
||||||
|
* response events, or {@code null} to signal that the pushed resource
|
||||||
|
* should be canceled.</p>
|
||||||
|
*
|
||||||
|
* @param pushHandler a handler for pushed resource events
|
||||||
|
* @return this request object
|
||||||
|
*/
|
||||||
|
Request onPush(BiFunction<Request, Request, Response.CompleteListener> pushHandler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param listener a listener for complete event
|
* @param listener a listener for complete event
|
||||||
* @return this request object
|
* @return this request object
|
||||||
|
|
|
@ -292,18 +292,6 @@ public class HttpStreamOverFCGI implements HttpStream
|
||||||
return _generator.generateResponseContent(_id, buffer, last, _aborted, callback);
|
return _generator.generateResponseContent(_id, buffer, last, _aborted, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushSupported()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCommitted()
|
public boolean isCommitted()
|
||||||
{
|
{
|
||||||
|
|
|
@ -168,7 +168,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
||||||
HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString());
|
HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString());
|
||||||
// TODO: copy PUSH_PROMISE headers into pushRequest.
|
// TODO: copy PUSH_PROMISE headers into pushRequest.
|
||||||
|
|
||||||
BiFunction<Request, Request, Response.CompleteListener> pushListener = request.getPushListener();
|
BiFunction<Request, Request, Response.CompleteListener> pushListener = request.getPushHandler();
|
||||||
if (pushListener != null)
|
if (pushListener != null)
|
||||||
{
|
{
|
||||||
Response.CompleteListener listener = pushListener.apply(request, pushRequest);
|
Response.CompleteListener listener = pushListener.apply(request, pushRequest);
|
||||||
|
|
|
@ -389,6 +389,12 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
||||||
return getEndPoint() instanceof SslConnection.DecryptedEndPoint;
|
return getEndPoint() instanceof SslConnection.DecryptedEndPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPushSupported()
|
||||||
|
{
|
||||||
|
return getSession().isPushEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocketAddress getRemoteSocketAddress()
|
public SocketAddress getRemoteSocketAddress()
|
||||||
{
|
{
|
||||||
|
|
|
@ -431,37 +431,31 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPushSupported()
|
public void push(MetaData.Request resource)
|
||||||
{
|
|
||||||
return _stream.getSession().isPushEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
{
|
||||||
if (!_stream.getSession().isPushEnabled())
|
if (!_stream.getSession().isPushEnabled())
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("HTTP/2 push disabled for {}", request);
|
LOG.debug("HTTP/2 push disabled for {}", resource);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("HTTP/2 push {}", request);
|
LOG.debug("HTTP/2 push {}", resource);
|
||||||
|
|
||||||
_stream.push(new PushPromiseFrame(_stream.getId(), request), new Promise<>()
|
_stream.push(new PushPromiseFrame(_stream.getId(), resource), new Promise<>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void succeeded(Stream pushStream)
|
public void succeeded(Stream pushStream)
|
||||||
{
|
{
|
||||||
_connection.push((HTTP2Stream)pushStream, request);
|
_connection.push((HTTP2Stream)pushStream, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void failed(Throwable x)
|
public void failed(Throwable x)
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Could not HTTP/2 push {}", request, x);
|
LOG.debug("Could not HTTP/2 push {}", resource, x);
|
||||||
}
|
}
|
||||||
}, null); // TODO: handle reset from the client ?
|
}, null); // TODO: handle reset from the client ?
|
||||||
}
|
}
|
||||||
|
@ -470,6 +464,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_requestMetaData = request;
|
||||||
Runnable task = _httpChannel.onRequest(request);
|
Runnable task = _httpChannel.onRequest(request);
|
||||||
_httpChannel.getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE);
|
_httpChannel.getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE);
|
||||||
|
|
||||||
|
|
|
@ -1,981 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
|
||||||
//
|
|
||||||
// This program and the accompanying materials are made available under the
|
|
||||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
||||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
|
||||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
|
||||||
// ========================================================================
|
|
||||||
//
|
|
||||||
|
|
||||||
package org.eclipse.jetty.http2.tests;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Disabled("move to ee9 or provide a PushCacheHandler")
|
|
||||||
public class PushCacheFilterTest extends AbstractTest
|
|
||||||
{
|
|
||||||
@Test
|
|
||||||
public void test()
|
|
||||||
{
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private String contextPath = "/push";
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// protected void customizeContext(ServletContextHandler context)
|
|
||||||
// {
|
|
||||||
// context.setContextPath(contextPath);
|
|
||||||
// context.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// protected MetaData.Request newRequest(String method, String path, HttpFields fields)
|
|
||||||
// {
|
|
||||||
// return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField("localhost:" + connector.getLocalPort()), contextPath + servletPath + path, HttpVersion.HTTP_2, fields, -1);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private String newURI(String pathInfo)
|
|
||||||
// {
|
|
||||||
// return "http://localhost:" + connector.getLocalPort() + contextPath + servletPath + pathInfo;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testPush() throws Exception
|
|
||||||
// {
|
|
||||||
// final String primaryResource = "/primary.html";
|
|
||||||
// final String secondaryResource = "/secondary.png";
|
|
||||||
// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
|
||||||
// {
|
|
||||||
// String requestURI = req.getRequestURI();
|
|
||||||
// ServletOutputStream output = resp.getOutputStream();
|
|
||||||
// if (requestURI.endsWith(primaryResource))
|
|
||||||
// output.print("<html><head></head><body>PRIMARY</body></html>");
|
|
||||||
// else if (requestURI.endsWith(secondaryResource))
|
|
||||||
// output.write(secondaryData);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Request for the primary and secondary resource to build the cache.
|
|
||||||
// final String referrerURI = newURI(primaryResource);
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build()
|
|
||||||
// .put(HttpHeader.REFERER, referrerURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// // Request again the primary resource, we should get the secondary resource pushed.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(2);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(2);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
|
||||||
// if (response.getStatus() == HttpStatus.OK_200)
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
|
||||||
// if (response.getStatus() == HttpStatus.OK_200)
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testPushReferrerNoPath() throws Exception
|
|
||||||
// {
|
|
||||||
// final String primaryResource = "/primary.html";
|
|
||||||
// final String secondaryResource = "/secondary.png";
|
|
||||||
// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
|
||||||
// {
|
|
||||||
// String requestURI = req.getRequestURI();
|
|
||||||
// ServletOutputStream output = resp.getOutputStream();
|
|
||||||
// if (requestURI.endsWith(primaryResource))
|
|
||||||
// output.print("<html><head></head><body>PRIMARY</body></html>");
|
|
||||||
// else if (requestURI.endsWith(secondaryResource))
|
|
||||||
// output.write(secondaryData);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Request for the primary and secondary resource to build the cache.
|
|
||||||
// // The referrerURI does not point to the primary resource, so there will be no
|
|
||||||
// // resource association with the primary resource and therefore won't be pushed.
|
|
||||||
// final String referrerURI = "http://localhost:" + connector.getLocalPort();
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build()
|
|
||||||
// .put(HttpHeader.REFERER, referrerURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// // Request again the primary resource, we should not get the secondary resource pushed.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testPushIsReset() throws Exception
|
|
||||||
// {
|
|
||||||
// final String primaryResource = "/primary.html";
|
|
||||||
// final String secondaryResource = "/secondary.png";
|
|
||||||
// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
|
||||||
// {
|
|
||||||
// String requestURI = req.getRequestURI();
|
|
||||||
// ServletOutputStream output = resp.getOutputStream();
|
|
||||||
// if (requestURI.endsWith(primaryResource))
|
|
||||||
// output.print("<html><head></head><body>PRIMARY</body></html>");
|
|
||||||
// else if (requestURI.endsWith(secondaryResource))
|
|
||||||
// output.write(secondaryData);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Request for the primary and secondary resource to build the cache.
|
|
||||||
// final String primaryURI = newURI(primaryResource);
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build()
|
|
||||||
// .put(HttpHeader.REFERER, primaryURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// // Request again the primary resource, we should get the secondary resource pushed.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// // Reset the stream as soon as we see the push.
|
|
||||||
// ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code);
|
|
||||||
// stream.reset(resetFrame, Callback.NOOP);
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// // We should not receive pushed data that we reset.
|
|
||||||
// assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// // Make sure the session is sane by requesting the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build();
|
|
||||||
// secondaryFields.put(HttpHeader.REFERER, primaryURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// final CountDownLatch secondaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// secondaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testPushWithoutPrimaryResponseContent() throws Exception
|
|
||||||
// {
|
|
||||||
// final String primaryResource = "/primary.html";
|
|
||||||
// final String secondaryResource = "/secondary.png";
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
// {
|
|
||||||
// String requestURI = request.getRequestURI();
|
|
||||||
// final ServletOutputStream output = response.getOutputStream();
|
|
||||||
// if (requestURI.endsWith(secondaryResource))
|
|
||||||
// output.write("SECONDARY".getBytes(StandardCharsets.UTF_8));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Request for the primary and secondary resource to build the cache.
|
|
||||||
// final String primaryURI = newURI(primaryResource);
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build();
|
|
||||||
// secondaryFields.put(HttpHeader.REFERER, primaryURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// Thread.sleep(1000);
|
|
||||||
//
|
|
||||||
// // Request again the primary resource, we should get the secondary resource pushed.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testRecursivePush() throws Exception
|
|
||||||
// {
|
|
||||||
// final String primaryResource = "/primary.html";
|
|
||||||
// final String secondaryResource1 = "/secondary1.css";
|
|
||||||
// final String secondaryResource2 = "/secondary2.js";
|
|
||||||
// final String tertiaryResource = "/tertiary.png";
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
// {
|
|
||||||
// String requestURI = request.getRequestURI();
|
|
||||||
// final ServletOutputStream output = response.getOutputStream();
|
|
||||||
// if (requestURI.endsWith(primaryResource))
|
|
||||||
// output.print("<html><head></head><body>PRIMARY</body></html>");
|
|
||||||
// else if (requestURI.endsWith(secondaryResource1))
|
|
||||||
// output.print("body { background-image: url(\"" + tertiaryResource + "\"); }");
|
|
||||||
// else if (requestURI.endsWith(secondaryResource2))
|
|
||||||
// output.print("(function() { window.alert('HTTP/2'); })()");
|
|
||||||
// if (requestURI.endsWith(tertiaryResource))
|
|
||||||
// output.write("TERTIARY".getBytes(StandardCharsets.UTF_8));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Request for the primary, secondary and tertiary resource to build the cache.
|
|
||||||
// final String primaryURI = newURI(primaryResource);
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(2);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resources.
|
|
||||||
// String secondaryURI1 = newURI(secondaryResource1);
|
|
||||||
// HttpFields.Mutable secondaryFields1 = HttpFields.build()
|
|
||||||
// .put(HttpHeader.REFERER, primaryURI);
|
|
||||||
// MetaData.Request secondaryRequest1 = newRequest("GET", secondaryResource1, secondaryFields1);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the tertiary resource.
|
|
||||||
// HttpFields.Mutable tertiaryFields = HttpFields.build()
|
|
||||||
// .put(HttpHeader.REFERER, secondaryURI1);
|
|
||||||
// MetaData.Request tertiaryRequest = newRequest("GET", tertiaryResource, tertiaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(tertiaryRequest, null, true), new Promise.Adapter<>(), new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// HttpFields.Mutable secondaryFields2 = HttpFields.build()
|
|
||||||
// .put(HttpHeader.REFERER, primaryURI);
|
|
||||||
// MetaData.Request secondaryRequest2 = newRequest("GET", secondaryResource2, secondaryFields2);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// Thread.sleep(1000);
|
|
||||||
//
|
|
||||||
// // Request again the primary resource, we should get the secondary and tertiary resources pushed.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch primaryPushesLatch = new CountDownLatch(3);
|
|
||||||
// final CountDownLatch recursiveLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// // The stream id of the PUSH_PROMISE must
|
|
||||||
// // always be a client stream and therefore odd.
|
|
||||||
// assertEquals(1, frame.getStreamId() & 1);
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryPushesLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// recursiveLatch.countDown();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// assertTrue(primaryPushesLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// assertFalse(recursiveLatch.await(1, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// // Make sure that explicitly requesting a secondary resource, we get the tertiary pushed.
|
|
||||||
// CountDownLatch secondaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// CountDownLatch secondaryPushLatch = new CountDownLatch(1);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource1, HttpFields.EMPTY);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// secondaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// secondaryPushLatch.countDown();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// assertTrue(secondaryPushLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testSelfPush() throws Exception
|
|
||||||
// {
|
|
||||||
// // The test case is that of a login page, for example.
|
|
||||||
// // When the user sends the credentials to the login page,
|
|
||||||
// // the login may fail and redirect to the same login page,
|
|
||||||
// // perhaps with different query parameters.
|
|
||||||
// // In this case a request for the login page will push
|
|
||||||
// // the login page itself, which will generate the pushed
|
|
||||||
// // request for the login page, which will push the login
|
|
||||||
// // page itself, etc. which is not the desired behavior.
|
|
||||||
//
|
|
||||||
// final String primaryResource = "/login.html";
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
// {
|
|
||||||
// ServletOutputStream output = response.getOutputStream();
|
|
||||||
// String credentials = request.getParameter("credentials");
|
|
||||||
// if (credentials == null)
|
|
||||||
// {
|
|
||||||
// output.print("<html><head></head><body>LOGIN</body></html>");
|
|
||||||
// }
|
|
||||||
// else if ("secret".equals(credentials))
|
|
||||||
// {
|
|
||||||
// output.print("<html><head></head><body>OK</body></html>");
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// response.setStatus(HttpStatus.TEMPORARY_REDIRECT_307);
|
|
||||||
// response.getHeaders().put(HttpHeader.LOCATION, primaryResource);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// final String primaryURI = newURI(primaryResource);
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Login with the wrong credentials, causing a redirect to self.
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource + "?credentials=wrong", primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
|
||||||
// if (response.getStatus() == HttpStatus.TEMPORARY_REDIRECT_307)
|
|
||||||
// {
|
|
||||||
// // Follow the redirect.
|
|
||||||
// String location = response.getFields().get(HttpHeader.LOCATION);
|
|
||||||
// HttpFields.Mutable redirectFields = HttpFields.build();
|
|
||||||
// redirectFields.put(HttpHeader.REFERER, primaryURI);
|
|
||||||
// MetaData.Request redirectRequest = newRequest("GET", location, redirectFields);
|
|
||||||
// session.newStream(new HeadersFrame(redirectRequest, null, true), new Promise.Adapter<>(), new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// Thread.sleep(1000);
|
|
||||||
//
|
|
||||||
// // Login with the right credentials, there must be no push.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource + "?credentials=secret", primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testPushWithQueryParameters() throws Exception
|
|
||||||
// {
|
|
||||||
// String name = "foo";
|
|
||||||
// String value = "bar";
|
|
||||||
// final String primaryResource = "/primary.html?" + name + "=" + value;
|
|
||||||
// final String secondaryResource = "/secondary.html?" + name + "=" + value;
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
// {
|
|
||||||
// String requestURI = request.getRequestURI();
|
|
||||||
// if (requestURI.endsWith(primaryResource))
|
|
||||||
// {
|
|
||||||
// response.setStatus(HttpStatus.OK_200);
|
|
||||||
// }
|
|
||||||
// else if (requestURI.endsWith(secondaryResource))
|
|
||||||
// {
|
|
||||||
// String param = request.getParameter(name);
|
|
||||||
// if (param == null)
|
|
||||||
// response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
|
|
||||||
// else
|
|
||||||
// response.setStatus(HttpStatus.OK_200);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Request for the primary and secondary resource to build the cache.
|
|
||||||
// final String primaryURI = newURI(primaryResource);
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build();
|
|
||||||
// secondaryFields.put(HttpHeader.REFERER, primaryURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// Thread.sleep(1000);
|
|
||||||
//
|
|
||||||
// // Request again the primary resource, we should get the secondary resource pushed.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// MetaData metaData = frame.getMetaData();
|
|
||||||
// assertTrue(metaData instanceof MetaData.Request);
|
|
||||||
// MetaData.Request pushedRequest = (MetaData.Request)metaData;
|
|
||||||
// assertEquals(contextPath + servletPath + secondaryResource, pushedRequest.getURI().getPathQuery());
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
|
||||||
// if (response.getStatus() == HttpStatus.OK_200)
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onHeaders(Stream stream, HeadersFrame frame)
|
|
||||||
// {
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testPOSTRequestIsNotPushed() throws Exception
|
|
||||||
// {
|
|
||||||
// final String primaryResource = "/primary.html";
|
|
||||||
// final String secondaryResource = "/secondary.png";
|
|
||||||
// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
|
||||||
// {
|
|
||||||
// String requestURI = req.getRequestURI();
|
|
||||||
// ServletOutputStream output = resp.getOutputStream();
|
|
||||||
// if (requestURI.endsWith(primaryResource))
|
|
||||||
// output.print("<html><head></head><body>PRIMARY</body></html>");
|
|
||||||
// else if (requestURI.endsWith(secondaryResource))
|
|
||||||
// output.write(secondaryData);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener() {});
|
|
||||||
//
|
|
||||||
// // Request for the primary and secondary resource to build the cache.
|
|
||||||
// final String referrerURI = newURI(primaryResource);
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build();
|
|
||||||
// secondaryFields.put(HttpHeader.REFERER, referrerURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// // Request again the primary resource with POST, we should not get the secondary resource pushed.
|
|
||||||
// primaryRequest = newRequest("POST", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// return new Adapter()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testPushDisabled() throws Exception
|
|
||||||
// {
|
|
||||||
// final String primaryResource = "/primary.html";
|
|
||||||
// final String secondaryResource = "/secondary.png";
|
|
||||||
// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
|
||||||
// start(new HttpServlet()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
|
||||||
// {
|
|
||||||
// String requestURI = req.getRequestURI();
|
|
||||||
// ServletOutputStream output = resp.getOutputStream();
|
|
||||||
// if (requestURI.endsWith(primaryResource))
|
|
||||||
// output.print("<html><head></head><body>PRIMARY</body></html>");
|
|
||||||
// else if (requestURI.endsWith(secondaryResource))
|
|
||||||
// output.write(secondaryData);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// final Session session = newClient(new Session.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public Map<Integer, Integer> onPreface(Session session)
|
|
||||||
// {
|
|
||||||
// Map<Integer, Integer> settings = new HashMap<>();
|
|
||||||
// settings.put(SettingsFrame.ENABLE_PUSH, 0);
|
|
||||||
// return settings;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // Request for the primary and secondary resource to build the cache.
|
|
||||||
// final String referrerURI = newURI(primaryResource);
|
|
||||||
// HttpFields.Mutable primaryFields = HttpFields.build();
|
|
||||||
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch warmupLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// {
|
|
||||||
// // Request for the secondary resource.
|
|
||||||
// HttpFields.Mutable secondaryFields = HttpFields.build();
|
|
||||||
// secondaryFields.put(HttpHeader.REFERER, referrerURI);
|
|
||||||
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
|
||||||
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// warmupLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
//
|
|
||||||
// // Request again the primary resource, we should not get the secondary resource pushed.
|
|
||||||
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
|
||||||
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
|
||||||
// final CountDownLatch pushLatch = new CountDownLatch(1);
|
|
||||||
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
|
||||||
// {
|
|
||||||
// pushLatch.countDown();
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onData(Stream stream, DataFrame frame, Callback callback)
|
|
||||||
// {
|
|
||||||
// callback.succeeded();
|
|
||||||
// if (frame.isEndStream())
|
|
||||||
// primaryResponseLatch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
|
||||||
// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -14,25 +14,30 @@
|
||||||
package org.eclipse.jetty.http2.tests;
|
package org.eclipse.jetty.http2.tests;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpRequest;
|
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.HttpURI;
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
import org.eclipse.jetty.http.MetaData;
|
||||||
|
import org.eclipse.jetty.http2.api.Session;
|
||||||
import org.eclipse.jetty.http2.api.Stream;
|
import org.eclipse.jetty.http2.api.Stream;
|
||||||
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||||
|
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||||
|
import org.eclipse.jetty.io.Content;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
|
@ -42,6 +47,7 @@ import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class PushedResourcesTest extends AbstractTest
|
public class PushedResourcesTest extends AbstractTest
|
||||||
|
@ -80,9 +86,8 @@ public class PushedResourcesTest extends AbstractTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort());
|
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
|
||||||
ContentResponse response = request
|
.onPush((mainRequest, pushedRequest) -> null)
|
||||||
.pushListener((mainRequest, pushedRequest) -> null)
|
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
|
@ -130,9 +135,8 @@ public class PushedResourcesTest extends AbstractTest
|
||||||
|
|
||||||
CountDownLatch latch1 = new CountDownLatch(1);
|
CountDownLatch latch1 = new CountDownLatch(1);
|
||||||
CountDownLatch latch2 = new CountDownLatch(1);
|
CountDownLatch latch2 = new CountDownLatch(1);
|
||||||
HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort());
|
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
|
||||||
ContentResponse response = request
|
.onPush((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||||
.pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(Result result)
|
public void onComplete(Result result)
|
||||||
|
@ -191,9 +195,8 @@ public class PushedResourcesTest extends AbstractTest
|
||||||
});
|
});
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort());
|
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
|
||||||
ContentResponse response = request
|
.onPush((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||||
.pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(Result result)
|
public void onComplete(Result result)
|
||||||
|
@ -211,4 +214,118 @@ public class PushedResourcesTest extends AbstractTest
|
||||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPushDisabled() throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
String secondaryData = "SECONDARY";
|
||||||
|
start(new Handler.Processor()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void process(Request request, Response response, Callback callback)
|
||||||
|
{
|
||||||
|
String requestURI = Request.getPathInContext(request);
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
{
|
||||||
|
assertFalse(request.getConnectionMetaData().isPushSupported());
|
||||||
|
Content.Sink.write(response, true, "<html><head></head><body>PRIMARY</body></html>", callback);
|
||||||
|
}
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
{
|
||||||
|
Content.Sink.write(response, true, secondaryData, callback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Session session = newClientSession(new Session.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Integer> onPreface(Session session)
|
||||||
|
{
|
||||||
|
Map<Integer, Integer> settings = new HashMap<>();
|
||||||
|
settings.put(SettingsFrame.ENABLE_PUSH, 0);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request for the primary and secondary resource to build the cache.
|
||||||
|
HttpFields.Mutable primaryFields = HttpFields.build();
|
||||||
|
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
||||||
|
String referrerURI = primaryRequest.getURIString();
|
||||||
|
CountDownLatch warmupLatch = new CountDownLatch(1);
|
||||||
|
session.newStream(new HeadersFrame(primaryRequest, null, true), new Stream.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable(Stream stream)
|
||||||
|
{
|
||||||
|
Stream.Data data = stream.readData();
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
stream.demand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.release();
|
||||||
|
if (data.frame().isEndStream())
|
||||||
|
{
|
||||||
|
// Request for the secondary resource.
|
||||||
|
HttpFields.Mutable secondaryFields = HttpFields.build();
|
||||||
|
secondaryFields.put(HttpHeader.REFERER, referrerURI);
|
||||||
|
MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
||||||
|
session.newStream(new HeadersFrame(secondaryRequest, null, true), new Stream.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable(Stream stream)
|
||||||
|
{
|
||||||
|
Stream.Data data = stream.readData();
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
stream.demand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.release();
|
||||||
|
if (data.frame().isEndStream())
|
||||||
|
warmupLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Request again the primary resource, we should not get the secondary resource pushed.
|
||||||
|
primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
||||||
|
CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable(Stream stream)
|
||||||
|
{
|
||||||
|
Stream.Data data = stream.readData();
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
stream.demand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.release();
|
||||||
|
if (data.frame().isEndStream())
|
||||||
|
primaryResponseLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -456,18 +456,6 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
||||||
return stream.trailer(frame);
|
return stream.trailer(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushSupported()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCommitted()
|
public boolean isCommitted()
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,6 +45,14 @@ public interface ConnectionMetaData extends Attributes
|
||||||
|
|
||||||
boolean isSecure();
|
boolean isSecure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the functionality of pushing resources is supported
|
||||||
|
*/
|
||||||
|
default boolean isPushSupported()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The address of the remote end of this connection. By default, this is the first hop of the underlying
|
* @return The address of the remote end of this connection. By default, this is the first hop of the underlying
|
||||||
* network connection, but it may be wrapped to represent a more remote end point.
|
* network connection, but it may be wrapped to represent a more remote end point.
|
||||||
|
@ -137,6 +145,12 @@ public interface ConnectionMetaData extends Attributes
|
||||||
return getWrapped().isSecure();
|
return getWrapped().isSecure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPushSupported()
|
||||||
|
{
|
||||||
|
return getWrapped().isPushSupported();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocketAddress getRemoteSocketAddress()
|
public SocketAddress getRemoteSocketAddress()
|
||||||
{
|
{
|
||||||
|
|
|
@ -85,9 +85,17 @@ public interface HttpStream extends Callback
|
||||||
*/
|
*/
|
||||||
void send(MetaData.Request request, MetaData.Response response, boolean last, ByteBuffer content, Callback callback);
|
void send(MetaData.Request request, MetaData.Response response, boolean last, ByteBuffer content, Callback callback);
|
||||||
|
|
||||||
boolean isPushSupported();
|
/**
|
||||||
|
* <p>Pushes the given {@code resource} to the client.</p>
|
||||||
void push(MetaData.Request request);
|
*
|
||||||
|
* @param resource the resource to push
|
||||||
|
* @throws UnsupportedOperationException if the push functionality is not supported
|
||||||
|
* @see ConnectionMetaData#isPushSupported()
|
||||||
|
*/
|
||||||
|
default void push(MetaData.Request resource)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
boolean isCommitted();
|
boolean isCommitted();
|
||||||
|
|
||||||
|
@ -170,15 +178,9 @@ public interface HttpStream extends Callback
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isPushSupported()
|
public void push(MetaData.Request resource)
|
||||||
{
|
{
|
||||||
return getWrapped().isPushSupported();
|
getWrapped().push(resource);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
getWrapped().push(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -203,14 +203,18 @@ public interface Request extends Attributes, Content.Source
|
||||||
@Override
|
@Override
|
||||||
Content.Chunk read();
|
Content.Chunk read();
|
||||||
|
|
||||||
// TODO should this be on the connectionMetaData?
|
/**
|
||||||
default boolean isPushSupported()
|
* <p>Pushes the given {@code resource} to the client.</p>
|
||||||
|
*
|
||||||
|
* @param resource the resource to push
|
||||||
|
* @throws UnsupportedOperationException if the push functionality is not supported
|
||||||
|
* @see ConnectionMetaData#isPushSupported()
|
||||||
|
*/
|
||||||
|
default void push(MetaData.Request resource)
|
||||||
{
|
{
|
||||||
return false; // TODO
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
void push(MetaData.Request request); // TODO
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Adds a listener for asynchronous errors.</p>
|
* <p>Adds a listener for asynchronous errors.</p>
|
||||||
* <p>The listener is a predicate function that should return {@code true} to indicate
|
* <p>The listener is a predicate function that should return {@code true} to indicate
|
||||||
|
@ -582,15 +586,9 @@ public interface Request extends Attributes, Content.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPushSupported()
|
public void push(MetaData.Request resource)
|
||||||
{
|
{
|
||||||
return getWrapped().isPushSupported();
|
getWrapped().push(resource);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
getWrapped().push(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -999,15 +999,9 @@ public class HttpChannelState implements HttpChannel, Components
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPushSupported()
|
public void push(MetaData.Request resource)
|
||||||
{
|
{
|
||||||
return true;
|
getHttpStream().push(resource);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
getHttpStream().push(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1441,18 +1441,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
||||||
_sendCallback.iterate();
|
_sendCallback.iterate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushSupported()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCommitted()
|
public boolean isCommitted()
|
||||||
{
|
{
|
||||||
|
|
|
@ -931,10 +931,10 @@ public class HttpChannelTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void push(MetaData.Request request)
|
public void push(MetaData.Request resource)
|
||||||
{
|
{
|
||||||
history.add("push");
|
history.add("push");
|
||||||
super.push(request);
|
super.push(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -204,18 +204,6 @@ public class MockHttpStream implements HttpStream
|
||||||
callback.succeeded();
|
callback.succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushSupported()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCommitted()
|
public boolean isCommitted()
|
||||||
{
|
{
|
||||||
|
|
|
@ -153,17 +153,6 @@ public class TestableRequest implements Request
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushSupported()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(org.eclipse.jetty.http.MetaData.Request request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunnelSupport getTunnelSupport()
|
public TunnelSupport getTunnelSupport()
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.ee10.servlet;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.PushBuilder;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
|
import org.eclipse.jetty.http.MetaData;
|
||||||
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
|
|
||||||
|
class PushBuilderImpl implements PushBuilder
|
||||||
|
{
|
||||||
|
private final ServletContextRequest _request;
|
||||||
|
private final HttpFields.Mutable _headers;
|
||||||
|
private String _method;
|
||||||
|
private String _query;
|
||||||
|
private String _sessionId;
|
||||||
|
private String _path;
|
||||||
|
|
||||||
|
public PushBuilderImpl(ServletContextRequest request, HttpFields.Mutable headers, String sessionId)
|
||||||
|
{
|
||||||
|
_request = request;
|
||||||
|
_headers = headers;
|
||||||
|
_method = request.getMethod();
|
||||||
|
_query = request.getHttpURI().getQuery();
|
||||||
|
_sessionId = sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PushBuilder method(String method)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = HttpMethod.fromString(method);
|
||||||
|
if (httpMethod == null || !httpMethod.isSafe())
|
||||||
|
throw new IllegalArgumentException("method not allowed for push: " + method);
|
||||||
|
_method = httpMethod.asString();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PushBuilder queryString(String queryString)
|
||||||
|
{
|
||||||
|
_query = queryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PushBuilder sessionId(String sessionId)
|
||||||
|
{
|
||||||
|
_sessionId = sessionId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PushBuilder setHeader(String name, String value)
|
||||||
|
{
|
||||||
|
_headers.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PushBuilder addHeader(String name, String value)
|
||||||
|
{
|
||||||
|
_headers.add(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PushBuilder removeHeader(String name)
|
||||||
|
{
|
||||||
|
_headers.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PushBuilder path(String path)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void push()
|
||||||
|
{
|
||||||
|
String pushPath = getPath();
|
||||||
|
if (pushPath == null || pushPath.isBlank())
|
||||||
|
throw new IllegalArgumentException("invalid push path: " + pushPath);
|
||||||
|
|
||||||
|
String query = getQueryString();
|
||||||
|
String pushQuery = query;
|
||||||
|
int q = pushPath.indexOf('?');
|
||||||
|
if (q > 0)
|
||||||
|
{
|
||||||
|
pushQuery = pushPath.substring(q + 1);
|
||||||
|
if (query != null)
|
||||||
|
pushQuery += "&" + query;
|
||||||
|
pushPath = pushPath.substring(0, q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pushPath.startsWith("/"))
|
||||||
|
pushPath = URIUtil.addPaths(_request.getContext().getContextPath(), pushPath);
|
||||||
|
|
||||||
|
String pushParam = null;
|
||||||
|
if (_sessionId != null)
|
||||||
|
{
|
||||||
|
if (_request.getServletApiRequest().isRequestedSessionIdFromURL())
|
||||||
|
pushParam = "jsessionid=" + _sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURI pushURI = HttpURI.build(_request.getHttpURI(), pushPath, pushParam, pushQuery).normalize();
|
||||||
|
MetaData.Request push = new MetaData.Request(_method, pushURI, _request.getConnectionMetaData().getHttpVersion(), _headers);
|
||||||
|
_request.push(push);
|
||||||
|
|
||||||
|
_path = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMethod()
|
||||||
|
{
|
||||||
|
return _method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQueryString()
|
||||||
|
{
|
||||||
|
return _query;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSessionId()
|
||||||
|
{
|
||||||
|
return _sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getHeaderNames()
|
||||||
|
{
|
||||||
|
return _headers.getFieldNamesCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(String name)
|
||||||
|
{
|
||||||
|
return _headers.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath()
|
||||||
|
{
|
||||||
|
return _path;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import java.security.Principal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -811,8 +812,75 @@ public class ServletContextRequest extends ContextRequest implements Runnable
|
||||||
@Override
|
@Override
|
||||||
public PushBuilder newPushBuilder()
|
public PushBuilder newPushBuilder()
|
||||||
{
|
{
|
||||||
// TODO NYI
|
if (!getConnectionMetaData().isPushSupported())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
HttpFields.Mutable pushHeaders = HttpFields.build(ServletContextRequest.this.getHeaders(), EnumSet.of(
|
||||||
|
HttpHeader.IF_MATCH,
|
||||||
|
HttpHeader.IF_RANGE,
|
||||||
|
HttpHeader.IF_UNMODIFIED_SINCE,
|
||||||
|
HttpHeader.RANGE,
|
||||||
|
HttpHeader.EXPECT,
|
||||||
|
HttpHeader.IF_NONE_MATCH,
|
||||||
|
HttpHeader.IF_MODIFIED_SINCE)
|
||||||
|
);
|
||||||
|
|
||||||
|
String referrer = getRequestURL().toString();
|
||||||
|
String query = getQueryString();
|
||||||
|
if (query != null)
|
||||||
|
referrer += "?" + query;
|
||||||
|
pushHeaders.put(HttpHeader.REFERER, referrer);
|
||||||
|
|
||||||
|
// Any Set-Cookie in the response should be present in the push.
|
||||||
|
HttpFields.Mutable responseHeaders = getResponse().getHeaders();
|
||||||
|
List<String> setCookies = new ArrayList<>(responseHeaders.getValuesList(HttpHeader.SET_COOKIE));
|
||||||
|
setCookies.addAll(responseHeaders.getValuesList(HttpHeader.SET_COOKIE2));
|
||||||
|
String cookies = pushHeaders.get(HttpHeader.COOKIE);
|
||||||
|
if (!setCookies.isEmpty())
|
||||||
|
{
|
||||||
|
StringBuilder pushCookies = new StringBuilder();
|
||||||
|
if (cookies != null)
|
||||||
|
pushCookies.append(cookies);
|
||||||
|
for (String setCookie : setCookies)
|
||||||
|
{
|
||||||
|
Map<String, String> cookieFields = HttpCookie.extractBasics(setCookie);
|
||||||
|
String cookieName = cookieFields.get("name");
|
||||||
|
String cookieValue = cookieFields.get("value");
|
||||||
|
String cookieMaxAge = cookieFields.get("max-age");
|
||||||
|
long maxAge = cookieMaxAge != null ? Long.parseLong(cookieMaxAge) : -1;
|
||||||
|
if (maxAge > 0)
|
||||||
|
{
|
||||||
|
if (pushCookies.length() > 0)
|
||||||
|
pushCookies.append("; ");
|
||||||
|
pushCookies.append(cookieName).append("=").append(cookieValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pushHeaders.put(HttpHeader.COOKIE, pushCookies.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
String sessionId;
|
||||||
|
HttpSession httpSession = getSession(false);
|
||||||
|
if (httpSession != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check that the session is valid;
|
||||||
|
httpSession.getLastAccessedTime();
|
||||||
|
sessionId = httpSession.getId();
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
if (LOG.isTraceEnabled())
|
||||||
|
LOG.trace("invalid HTTP session", x);
|
||||||
|
sessionId = getRequestedSessionId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sessionId = getRequestedSessionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PushBuilderImpl(ServletContextRequest.this, pushHeaders, sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -91,6 +91,11 @@
|
||||||
<artifactId>jetty-ee10-servlet</artifactId>
|
<artifactId>jetty-ee10-servlet</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||||
|
<artifactId>jetty-ee10-servlets</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-client</artifactId>
|
<artifactId>jetty-client</artifactId>
|
||||||
|
|
|
@ -85,6 +85,13 @@ public class AbstractTest
|
||||||
return transports;
|
return transports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Collection<Transport> transportsWithPushSupport()
|
||||||
|
{
|
||||||
|
Collection<Transport> transports = transports();
|
||||||
|
transports.retainAll(List.of(Transport.H2C, Transport.H2));
|
||||||
|
return transports;
|
||||||
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,515 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.ee10.test.client.transport;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import jakarta.servlet.DispatcherType;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServlet;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
|
import org.eclipse.jetty.ee10.servlets.PushCacheFilter;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class PushCacheFilterTest extends AbstractTest
|
||||||
|
{
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPush(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(2);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushReferrerNoPath(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
// The referrerURI does not point to the primary resource, so there will be no
|
||||||
|
// resource association with the primary resource and therefore won't be pushed.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should not get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushIsReset(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
// Cancel the push.
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Make sure the connection is sane.
|
||||||
|
HttpDestination destination = (HttpDestination)client.getDestinations().get(0);
|
||||||
|
assertFalse(destination.getConnectionPool().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushWithoutPrimaryResponseContent(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write("SECONDARY".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(2);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testRecursivePush(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource1 = "/secondary1.css";
|
||||||
|
String secondaryResource2 = "/secondary2.js";
|
||||||
|
String tertiaryResource = "/tertiary.png";
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource1))
|
||||||
|
output.print("body { background-image: url(\"" + tertiaryResource + "\"); }");
|
||||||
|
else if (requestURI.endsWith(secondaryResource2))
|
||||||
|
output.print("(function() { window.alert('HTTP/2'); })()");
|
||||||
|
if (requestURI.endsWith(tertiaryResource))
|
||||||
|
output.write("TERTIARY".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary, secondary and tertiary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource1)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource2)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(tertiaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(secondaryResource1).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary and tertiary resources pushed.
|
||||||
|
CountDownLatch primaryPushLatch = new CountDownLatch(3);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) -> new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
primaryPushLatch.countDown();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(primaryPushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Make sure that explicitly requesting a secondary resource, we get the tertiary pushed.
|
||||||
|
CountDownLatch secondaryPushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) -> new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
secondaryPushLatch.countDown();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(secondaryPushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testSelfPush(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
// The test case is that of a login page, for example.
|
||||||
|
// When the user sends the credentials to the login page,
|
||||||
|
// the login may fail and redirect to the same login page,
|
||||||
|
// perhaps with different query parameters.
|
||||||
|
// In this case a request for the login page will push
|
||||||
|
// the login page itself, which will generate the pushed
|
||||||
|
// request for the login page, which will push the login
|
||||||
|
// page itself, etc. which is not the desired behavior.
|
||||||
|
|
||||||
|
String primaryResource = "/login.html";
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
String credentials = request.getParameter("credentials");
|
||||||
|
if (credentials == null)
|
||||||
|
{
|
||||||
|
output.print("<html><head></head><body>LOGIN</body></html>");
|
||||||
|
}
|
||||||
|
else if ("secret".equals(credentials))
|
||||||
|
{
|
||||||
|
output.print("<html><head></head><body>OK</body></html>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.setStatus(HttpStatus.TEMPORARY_REDIRECT_307);
|
||||||
|
response.setHeader(HttpHeader.LOCATION.asString(), primaryResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Login with the wrong credentials, causing a redirect to self.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource + "?credentials=wrong")
|
||||||
|
.followRedirects(false)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.TEMPORARY_REDIRECT_307, response.getStatus());
|
||||||
|
String location = response.getHeaders().get(HttpHeader.LOCATION);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(location)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Login with the right credentials, there must be no push.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource + "?credentials=secret")
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushWithQueryParameters(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String name = "foo";
|
||||||
|
String value = "bar";
|
||||||
|
String query = name + "=" + value;
|
||||||
|
String primaryResource = "/primary.html?" + query;
|
||||||
|
String secondaryResource = "/secondary.html?" + query;
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
{
|
||||||
|
response.setStatus(HttpStatus.OK_200);
|
||||||
|
}
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
{
|
||||||
|
String param = request.getParameter(name);
|
||||||
|
if (param == null)
|
||||||
|
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
|
||||||
|
else
|
||||||
|
response.setStatus(HttpStatus.OK_200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
assertEquals(query, pushed.getURI().getQuery());
|
||||||
|
return new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPOSTRequestIsNotPushed(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resource to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource with POST, we should not get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.method(HttpMethod.POST)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,9 +29,6 @@ import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class PushBuilderImpl implements PushBuilder
|
public class PushBuilderImpl implements PushBuilder
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(PushBuilderImpl.class);
|
private static final Logger LOG = LoggerFactory.getLogger(PushBuilderImpl.class);
|
||||||
|
|
|
@ -243,7 +243,7 @@ public class Request implements HttpServletRequest
|
||||||
|
|
||||||
public boolean isPushSupported()
|
public boolean isPushSupported()
|
||||||
{
|
{
|
||||||
return !isPush() && getCoreRequest().isPushSupported();
|
return !isPush() && getCoreRequest().getConnectionMetaData().isPushSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final EnumSet<HttpHeader> NOT_PUSHED_HEADERS = EnumSet.of(
|
private static final EnumSet<HttpHeader> NOT_PUSHED_HEADERS = EnumSet.of(
|
||||||
|
@ -274,7 +274,7 @@ public class Request implements HttpServletRequest
|
||||||
String id;
|
String id;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpSession session = getSession();
|
HttpSession session = getSession(false);
|
||||||
if (session != null)
|
if (session != null)
|
||||||
{
|
{
|
||||||
session.getLastAccessedTime(); // checks if session is valid
|
session.getLastAccessedTime(); // checks if session is valid
|
||||||
|
@ -343,8 +343,12 @@ public class Request implements HttpServletRequest
|
||||||
fields.add(new HttpField(HttpHeader.COOKIE, buff.toString()));
|
fields.add(new HttpField(HttpHeader.COOKIE, buff.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
PushBuilder builder = new PushBuilderImpl(this, fields, getMethod(), getQueryString(), id);
|
String query = getQueryString();
|
||||||
builder.addHeader("referer", getRequestURL().toString());
|
PushBuilder builder = new PushBuilderImpl(this, fields, getMethod(), query, id);
|
||||||
|
String referrer = getRequestURL().toString();
|
||||||
|
if (query != null)
|
||||||
|
referrer += "?" + query;
|
||||||
|
builder.addHeader("referer", referrer);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,6 @@ import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.HttpTester;
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
import org.eclipse.jetty.http.HttpURI;
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.http.UriCompliance;
|
import org.eclipse.jetty.http.UriCompliance;
|
||||||
import org.eclipse.jetty.http.pathmap.MatchedPath;
|
import org.eclipse.jetty.http.pathmap.MatchedPath;
|
||||||
|
@ -2350,17 +2349,6 @@ public class RequestTest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushSupported()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addErrorListener(Predicate<Throwable> onError)
|
public boolean addErrorListener(Predicate<Throwable> onError)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2352,17 +2352,6 @@ public class ResponseTest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPushSupported()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void push(MetaData.Request request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addErrorListener(Predicate<Throwable> onError)
|
public boolean addErrorListener(Predicate<Throwable> onError)
|
||||||
{
|
{
|
||||||
|
|
|
@ -91,6 +91,11 @@
|
||||||
<artifactId>jetty-ee9-servlet</artifactId>
|
<artifactId>jetty-ee9-servlet</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||||
|
<artifactId>jetty-ee9-servlets</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-client</artifactId>
|
<artifactId>jetty-client</artifactId>
|
||||||
|
|
|
@ -85,6 +85,13 @@ public class AbstractTest
|
||||||
return transports;
|
return transports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Collection<Transport> transportsWithPushSupport()
|
||||||
|
{
|
||||||
|
Collection<Transport> transports = transports();
|
||||||
|
transports.retainAll(List.of(Transport.H2C, Transport.H2));
|
||||||
|
return transports;
|
||||||
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,515 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.ee9.test.client.transport;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import jakarta.servlet.DispatcherType;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServlet;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
|
import org.eclipse.jetty.ee9.servlets.PushCacheFilter;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class PushCacheFilterTest extends AbstractTest
|
||||||
|
{
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPush(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(2);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushReferrerNoPath(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
// The referrerURI does not point to the primary resource, so there will be no
|
||||||
|
// resource association with the primary resource and therefore won't be pushed.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should not get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushIsReset(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
// Cancel the push.
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Make sure the connection is sane.
|
||||||
|
HttpDestination destination = (HttpDestination)client.getDestinations().get(0);
|
||||||
|
assertFalse(destination.getConnectionPool().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushWithoutPrimaryResponseContent(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write("SECONDARY".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(2);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testRecursivePush(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource1 = "/secondary1.css";
|
||||||
|
String secondaryResource2 = "/secondary2.js";
|
||||||
|
String tertiaryResource = "/tertiary.png";
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource1))
|
||||||
|
output.print("body { background-image: url(\"" + tertiaryResource + "\"); }");
|
||||||
|
else if (requestURI.endsWith(secondaryResource2))
|
||||||
|
output.print("(function() { window.alert('HTTP/2'); })()");
|
||||||
|
if (requestURI.endsWith(tertiaryResource))
|
||||||
|
output.write("TERTIARY".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary, secondary and tertiary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource1)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource2)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(tertiaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(secondaryResource1).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary and tertiary resources pushed.
|
||||||
|
CountDownLatch primaryPushLatch = new CountDownLatch(3);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) -> new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
primaryPushLatch.countDown();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(primaryPushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Make sure that explicitly requesting a secondary resource, we get the tertiary pushed.
|
||||||
|
CountDownLatch secondaryPushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) -> new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
secondaryPushLatch.countDown();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(secondaryPushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testSelfPush(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
// The test case is that of a login page, for example.
|
||||||
|
// When the user sends the credentials to the login page,
|
||||||
|
// the login may fail and redirect to the same login page,
|
||||||
|
// perhaps with different query parameters.
|
||||||
|
// In this case a request for the login page will push
|
||||||
|
// the login page itself, which will generate the pushed
|
||||||
|
// request for the login page, which will push the login
|
||||||
|
// page itself, etc. which is not the desired behavior.
|
||||||
|
|
||||||
|
String primaryResource = "/login.html";
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
String credentials = request.getParameter("credentials");
|
||||||
|
if (credentials == null)
|
||||||
|
{
|
||||||
|
output.print("<html><head></head><body>LOGIN</body></html>");
|
||||||
|
}
|
||||||
|
else if ("secret".equals(credentials))
|
||||||
|
{
|
||||||
|
output.print("<html><head></head><body>OK</body></html>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.setStatus(HttpStatus.TEMPORARY_REDIRECT_307);
|
||||||
|
response.setHeader(HttpHeader.LOCATION.asString(), primaryResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Login with the wrong credentials, causing a redirect to self.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource + "?credentials=wrong")
|
||||||
|
.followRedirects(false)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.TEMPORARY_REDIRECT_307, response.getStatus());
|
||||||
|
String location = response.getHeaders().get(HttpHeader.LOCATION);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(location)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Login with the right credentials, there must be no push.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource + "?credentials=secret")
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPushWithQueryParameters(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String name = "foo";
|
||||||
|
String value = "bar";
|
||||||
|
String query = name + "=" + value;
|
||||||
|
String primaryResource = "/primary.html?" + query;
|
||||||
|
String secondaryResource = "/secondary.html?" + query;
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
{
|
||||||
|
response.setStatus(HttpStatus.OK_200);
|
||||||
|
}
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
{
|
||||||
|
String param = request.getParameter(name);
|
||||||
|
if (param == null)
|
||||||
|
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
|
||||||
|
else
|
||||||
|
response.setStatus(HttpStatus.OK_200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resources to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource, we should get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
assertEquals(query, pushed.getURI().getQuery());
|
||||||
|
return new BufferingResponseListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transportsWithPushSupport")
|
||||||
|
public void testPOSTRequestIsNotPushed(Transport transport) throws Exception
|
||||||
|
{
|
||||||
|
String primaryResource = "/primary.html";
|
||||||
|
String secondaryResource = "/secondary.png";
|
||||||
|
byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8);
|
||||||
|
start(transport, new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||||
|
{
|
||||||
|
String requestURI = req.getRequestURI();
|
||||||
|
ServletOutputStream output = resp.getOutputStream();
|
||||||
|
if (requestURI.endsWith(primaryResource))
|
||||||
|
output.print("<html><head></head><body>PRIMARY</body></html>");
|
||||||
|
else if (requestURI.endsWith(secondaryResource))
|
||||||
|
output.write(secondaryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
servletContextHandler.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
|
||||||
|
// Request for the primary and secondary resource to build the cache.
|
||||||
|
URI uri = newURI(transport);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.path(primaryResource)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.path(secondaryResource)
|
||||||
|
.headers(headers -> headers.put(HttpHeader.REFERER, uri.resolve(primaryResource).toString()))
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Request again the primary resource with POST, we should not get the secondary resource pushed.
|
||||||
|
CountDownLatch pushLatch = new CountDownLatch(1);
|
||||||
|
response = client.newRequest(uri)
|
||||||
|
.method(HttpMethod.POST)
|
||||||
|
.path(primaryResource)
|
||||||
|
.onPush((request, pushed) ->
|
||||||
|
{
|
||||||
|
pushLatch.countDown();
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue