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 Map<String, Object> attributes;
|
||||
private List<RequestListener> requestListeners;
|
||||
private BiFunction<Request, Request, Response.CompleteListener> pushListener;
|
||||
private BiFunction<Request, Request, Response.CompleteListener> pushHandler;
|
||||
private Supplier<HttpFields> trailers;
|
||||
private String upgradeProtocol;
|
||||
private Object tag;
|
||||
|
@ -607,6 +607,13 @@ public class HttpRequest implements Request
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request onPush(BiFunction<Request, Request, Response.CompleteListener> pushHandler)
|
||||
{
|
||||
this.pushHandler = pushHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request onComplete(final Response.CompleteListener listener)
|
||||
{
|
||||
|
@ -621,26 +628,6 @@ public class HttpRequest implements Request
|
|||
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
|
||||
public Request trailersSupplier(Supplier<HttpFields> trailers)
|
||||
{
|
||||
|
@ -800,9 +787,9 @@ public class HttpRequest implements Request
|
|||
return responseListeners;
|
||||
}
|
||||
|
||||
public BiFunction<Request, Request, Response.CompleteListener> getPushListener()
|
||||
public BiFunction<Request, Request, Response.CompleteListener> getPushHandler()
|
||||
{
|
||||
return pushListener;
|
||||
return pushHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
@ -420,6 +421,22 @@ public interface Request
|
|||
*/
|
||||
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
|
||||
* @return this request object
|
||||
|
|
|
@ -292,18 +292,6 @@ public class HttpStreamOverFCGI implements HttpStream
|
|||
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
|
||||
public boolean isCommitted()
|
||||
{
|
||||
|
|
|
@ -168,7 +168,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString());
|
||||
// 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)
|
||||
{
|
||||
Response.CompleteListener listener = pushListener.apply(request, pushRequest);
|
||||
|
|
|
@ -389,6 +389,12 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
return getEndPoint() instanceof SslConnection.DecryptedEndPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return getSession().isPushEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getRemoteSocketAddress()
|
||||
{
|
||||
|
|
|
@ -431,37 +431,31 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return _stream.getSession().isPushEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
public void push(MetaData.Request resource)
|
||||
{
|
||||
if (!_stream.getSession().isPushEnabled())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("HTTP/2 push disabled for {}", request);
|
||||
LOG.debug("HTTP/2 push disabled for {}", resource);
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
public void succeeded(Stream pushStream)
|
||||
{
|
||||
_connection.push((HTTP2Stream)pushStream, request);
|
||||
_connection.push((HTTP2Stream)pushStream, resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
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 ?
|
||||
}
|
||||
|
@ -470,6 +464,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
{
|
||||
try
|
||||
{
|
||||
_requestMetaData = request;
|
||||
Runnable task = _httpChannel.onRequest(request);
|
||||
_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;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
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.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
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.server.ServerSessionListener;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
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.Request;
|
||||
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.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class PushedResourcesTest extends AbstractTest
|
||||
|
@ -80,9 +86,8 @@ public class PushedResourcesTest extends AbstractTest
|
|||
}
|
||||
});
|
||||
|
||||
HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort());
|
||||
ContentResponse response = request
|
||||
.pushListener((mainRequest, pushedRequest) -> null)
|
||||
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
|
||||
.onPush((mainRequest, pushedRequest) -> null)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
|
@ -130,9 +135,8 @@ public class PushedResourcesTest extends AbstractTest
|
|||
|
||||
CountDownLatch latch1 = new CountDownLatch(1);
|
||||
CountDownLatch latch2 = new CountDownLatch(1);
|
||||
HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort());
|
||||
ContentResponse response = request
|
||||
.pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
|
||||
.onPush((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
|
@ -191,9 +195,8 @@ public class PushedResourcesTest extends AbstractTest
|
|||
});
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort());
|
||||
ContentResponse response = request
|
||||
.pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
|
||||
.onPush((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
|
@ -211,4 +214,118 @@ public class PushedResourcesTest extends AbstractTest
|
|||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
|
|
|
@ -45,6 +45,14 @@ public interface ConnectionMetaData extends Attributes
|
|||
|
||||
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
|
||||
* 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return getWrapped().isPushSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
boolean isPushSupported();
|
||||
|
||||
void push(MetaData.Request request);
|
||||
/**
|
||||
* <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)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
boolean isCommitted();
|
||||
|
||||
|
@ -170,15 +178,9 @@ public interface HttpStream extends Callback
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isPushSupported()
|
||||
public void push(MetaData.Request resource)
|
||||
{
|
||||
return getWrapped().isPushSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
{
|
||||
getWrapped().push(request);
|
||||
getWrapped().push(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -203,14 +203,18 @@ public interface Request extends Attributes, Content.Source
|
|||
@Override
|
||||
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>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
|
||||
public boolean isPushSupported()
|
||||
public void push(MetaData.Request resource)
|
||||
{
|
||||
return getWrapped().isPushSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
{
|
||||
getWrapped().push(request);
|
||||
getWrapped().push(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -999,15 +999,9 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
public void push(MetaData.Request resource)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
{
|
||||
getHttpStream().push(request);
|
||||
getHttpStream().push(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1441,18 +1441,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
_sendCallback.iterate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
|
|
|
@ -931,10 +931,10 @@ public class HttpChannelTest
|
|||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
public void push(MetaData.Request resource)
|
||||
{
|
||||
history.add("push");
|
||||
super.push(request);
|
||||
super.push(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -204,18 +204,6 @@ public class MockHttpStream implements HttpStream
|
|||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(MetaData.Request request)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
|
|
|
@ -153,17 +153,6 @@ public class TestableRequest implements Request
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(org.eclipse.jetty.http.MetaData.Request request)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
|
@ -811,8 +812,75 @@ public class ServletContextRequest extends ContextRequest implements Runnable
|
|||
@Override
|
||||
public PushBuilder newPushBuilder()
|
||||
{
|
||||
// TODO NYI
|
||||
return null;
|
||||
if (!getConnectionMetaData().isPushSupported())
|
||||
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
|
||||
|
|
|
@ -91,6 +91,11 @@
|
|||
<artifactId>jetty-ee10-servlet</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-servlets</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
|
|
|
@ -85,6 +85,13 @@ public class AbstractTest
|
|||
return transports;
|
||||
}
|
||||
|
||||
public static Collection<Transport> transportsWithPushSupport()
|
||||
{
|
||||
Collection<Transport> transports = transports();
|
||||
transports.retainAll(List.of(Transport.H2C, Transport.H2));
|
||||
return transports;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
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.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PushBuilderImpl implements PushBuilder
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PushBuilderImpl.class);
|
||||
|
|
|
@ -243,7 +243,7 @@ public class Request implements HttpServletRequest
|
|||
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
return !isPush() && getCoreRequest().isPushSupported();
|
||||
return !isPush() && getCoreRequest().getConnectionMetaData().isPushSupported();
|
||||
}
|
||||
|
||||
private static final EnumSet<HttpHeader> NOT_PUSHED_HEADERS = EnumSet.of(
|
||||
|
@ -274,7 +274,7 @@ public class Request implements HttpServletRequest
|
|||
String id;
|
||||
try
|
||||
{
|
||||
HttpSession session = getSession();
|
||||
HttpSession session = getSession(false);
|
||||
if (session != null)
|
||||
{
|
||||
session.getLastAccessedTime(); // checks if session is valid
|
||||
|
@ -343,8 +343,12 @@ public class Request implements HttpServletRequest
|
|||
fields.add(new HttpField(HttpHeader.COOKIE, buff.toString()));
|
||||
}
|
||||
|
||||
PushBuilder builder = new PushBuilderImpl(this, fields, getMethod(), getQueryString(), id);
|
||||
builder.addHeader("referer", getRequestURL().toString());
|
||||
String query = getQueryString();
|
||||
PushBuilder builder = new PushBuilderImpl(this, fields, getMethod(), query, id);
|
||||
String referrer = getRequestURL().toString();
|
||||
if (query != null)
|
||||
referrer += "?" + query;
|
||||
builder.addHeader("referer", referrer);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.UriCompliance;
|
||||
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
|
||||
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
|
||||
public boolean addErrorListener(Predicate<Throwable> onError)
|
||||
{
|
||||
|
|
|
@ -91,6 +91,11 @@
|
|||
<artifactId>jetty-ee9-servlet</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-servlets</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
|
|
|
@ -85,6 +85,13 @@ public class AbstractTest
|
|||
return transports;
|
||||
}
|
||||
|
||||
public static Collection<Transport> transportsWithPushSupport()
|
||||
{
|
||||
Collection<Transport> transports = transports();
|
||||
transports.retainAll(List.of(Transport.H2C, Transport.H2));
|
||||
return transports;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
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