diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PushCacheFilterTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PushCacheFilterTest.java index 53a0c8e67c4..5258127bb48 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PushCacheFilterTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PushCacheFilterTest.java @@ -418,7 +418,8 @@ public class PushCacheFilterTest extends AbstractTest public void testRecursivePush() throws Exception { final String primaryResource = "/primary.html"; - final String secondaryResource = "/secondary.css"; + final String secondaryResource1 = "/secondary1.css"; + final String secondaryResource2 = "/secondary2.js"; final String tertiaryResource = "/tertiary.png"; start(new HttpServlet() { @@ -429,8 +430,10 @@ public class PushCacheFilterTest extends AbstractTest final ServletOutputStream output = response.getOutputStream(); if (requestURI.endsWith(primaryResource)) output.print("PRIMARY"); - else if (requestURI.endsWith(secondaryResource)) + 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)); } @@ -442,7 +445,7 @@ public class PushCacheFilterTest extends AbstractTest final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource; HttpFields primaryFields = new HttpFields(); MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); + final CountDownLatch warmupLatch = new CountDownLatch(2); session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() { @Override @@ -451,12 +454,12 @@ public class PushCacheFilterTest extends AbstractTest callback.succeeded(); if (frame.isEndStream()) { - // Request for the secondary resource. - final String secondaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + secondaryResource; - HttpFields secondaryFields = new HttpFields(); - 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.Adapter() + // Request for the secondary resources. + String secondaryURI1 = "http://localhost:" + connector.getLocalPort() + servletPath + secondaryResource1; + HttpFields secondaryFields1 = new HttpFields(); + secondaryFields1.put(HttpHeader.REFERER, primaryURI); + MetaData.Request secondaryRequest1 = newRequest("GET", secondaryResource1, secondaryFields1); + session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() { @Override public void onData(Stream stream, DataFrame frame, Callback callback) @@ -466,13 +469,14 @@ public class PushCacheFilterTest extends AbstractTest { // Request for the tertiary resource. HttpFields tertiaryFields = new HttpFields(); - tertiaryFields.put(HttpHeader.REFERER, secondaryURI); + tertiaryFields.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(); } @@ -480,6 +484,20 @@ public class PushCacheFilterTest extends AbstractTest } } }); + + HttpFields secondaryFields2 = new HttpFields(); + secondaryFields2.put(HttpHeader.REFERER, primaryURI); + MetaData.Request secondaryRequest2 = newRequest("GET", secondaryResource2, secondaryFields2); + session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + callback.succeeded(); + if (frame.isEndStream()) + warmupLatch.countDown(); + } + }); } } }); @@ -487,19 +505,73 @@ public class PushCacheFilterTest extends AbstractTest Thread.sleep(1000); - // Request again the primary resource, we should get the secondary and tertiary resource pushed. + // 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 pushLatch = new CountDownLatch(2); + 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.Adapter() { @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. + Assert.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(); + } + }; + } + }; + } + }); + + Assert.assertTrue(primaryPushesLatch.await(5, TimeUnit.SECONDS)); + Assert.assertFalse(recursiveLatch.await(1, TimeUnit.SECONDS)); + Assert.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, new HttpFields()); + session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @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) { @@ -510,19 +582,14 @@ public class PushCacheFilterTest extends AbstractTest { callback.succeeded(); if (frame.isEndStream()) - pushLatch.countDown(); - } - - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - return this; + secondaryPushLatch.countDown(); } }; } }); - Assert.assertTrue(pushLatch.await(5, TimeUnit.SECONDS)); - Assert.assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); + + Assert.assertTrue(secondaryPushLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS)); } @Test diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java index aae4361f6fe..8e550f66269 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java @@ -123,6 +123,11 @@ public class AttributeNormalizer return 1; } + if( (o1.path == null) && (o2.path == null) ) + { + return 0; + } + // Different lengths? int diff = o2.path.getNameCount() - o1.path.getNameCount(); if(diff != 0) @@ -142,14 +147,19 @@ public class AttributeNormalizer } } + private URI warURI; private List attributes = new ArrayList<>(); public AttributeNormalizer(Resource baseResource) { + // WAR URI is always evaluated before paths. + warURI = baseResource == null ? null : baseResource.getURI(); + // We don't normalize or resolve the baseResource URI + if (!warURI.isAbsolute()) + throw new IllegalArgumentException("WAR URI is not absolute: " + warURI); try { // Track path attributes for expansion - attributes.add(new PathAttribute("WAR", baseResource == null ? null : baseResource.getFile().toPath()).weight(10)); attributes.add(new PathAttribute("jetty.base", "jetty.base").weight(9)); attributes.add(new PathAttribute("jetty.home", "jetty.home").weight(8)); attributes.add(new PathAttribute("user.home", "user.home").weight(7)); @@ -195,7 +205,13 @@ public class AttributeNormalizer { return "file:" + normalizePath(new File(uri).toPath()); } - + else + { + if(uri.isAbsolute()) + { + return normalizeUri(uri); + } + } } catch (Exception e) { @@ -203,6 +219,17 @@ public class AttributeNormalizer } return String.valueOf(o); } + + public String normalizeUri(URI uri) + { + String uriStr = uri.toASCIIString(); + String warStr = warURI.toASCIIString(); + if (uriStr.startsWith(warStr)) + { + return "${WAR}" + uriStr.substring(warStr.length()); + } + return uriStr; + } public String normalizePath(Path path) { @@ -306,7 +333,13 @@ public class AttributeNormalizer private String getString(String property) { - // Use known attributes first + // Use war path (if known) + if("WAR".equalsIgnoreCase(property)) + { + return warURI.toASCIIString(); + } + + // Use known path attributes for (PathAttribute attr : attributes) { if (attr.key.equalsIgnoreCase(property)) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java index 89e85e9d12f..915816e202a 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java @@ -16,14 +16,15 @@ // ======================================================================== // - package org.eclipse.jetty.servlets; import java.io.IOException; +import java.util.ArrayDeque; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Queue; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; @@ -153,7 +154,7 @@ public class PushCacheFilter implements Filter } if (LOG.isDebugEnabled()) - LOG.debug("{} {} referrer={} conditional={}", request.getMethod(), request.getRequestURI(), referrer, conditional); + LOG.debug("{} {} referrer={} conditional={} synthetic={}", request.getMethod(), request.getRequestURI(), referrer, conditional, isPushRequest(request)); String path = URIUtil.addPaths(request.getServletPath(), request.getPathInfo()); String query = request.getQueryString(); @@ -190,11 +191,11 @@ public class PushCacheFilter implements Filter { if (now - primaryTimestamp < TimeUnit.MILLISECONDS.toNanos(_associatePeriod)) { - ConcurrentMap associated = primaryResource._associated; + Set associated = primaryResource._associated; // Not strictly concurrent-safe, just best effort to limit associations. if (associated.size() <= _maxAssociations) { - if (associated.putIfAbsent(path, path) == null) + if (associated.add(path)) { if (LOG.isDebugEnabled()) LOG.debug("Associated {} to {}", path, referrerPathNoContext); @@ -229,13 +230,12 @@ public class PushCacheFilter implements Filter } } - // Push some resources? PrimaryResource primaryResource = _cache.get(path); if (primaryResource == null) { - PrimaryResource t = new PrimaryResource(); - primaryResource = _cache.putIfAbsent(path, t); - primaryResource = primaryResource == null ? t : primaryResource; + PrimaryResource r = new PrimaryResource(); + primaryResource = _cache.putIfAbsent(path, r); + primaryResource = primaryResource == null ? r : primaryResource; primaryResource._timestamp.compareAndSet(0, now); if (LOG.isDebugEnabled()) LOG.debug("Cached primary resource {}", path); @@ -251,23 +251,38 @@ public class PushCacheFilter implements Filter } } - // Push associated for non conditional - if (!conditional && !primaryResource._associated.isEmpty()) + // Push associated resources. + if (!isPushRequest(request) && !conditional && !primaryResource._associated.isEmpty()) { - PushBuilder builder = Request.getBaseRequest(request).getPushBuilder(); + PushBuilder pushBuilder = Request.getBaseRequest(request).getPushBuilder(); - for (String associated : primaryResource._associated.values()) + // Breadth-first push of associated resources. + Queue queue = new ArrayDeque<>(); + queue.offer(primaryResource); + while (!queue.isEmpty()) { - if (LOG.isDebugEnabled()) - LOG.debug("Pushing {} for {}", associated, path); + PrimaryResource parent = queue.poll(); + for (String childPath : parent._associated) + { + PrimaryResource child = _cache.get(childPath); + if (child != null) + queue.offer(child); - builder.path(associated).push(); + if (LOG.isDebugEnabled()) + LOG.debug("Pushing {} for {}", childPath, path); + pushBuilder.path(childPath).push(); + } } } chain.doFilter(request, resp); } + private boolean isPushRequest(HttpServletRequest request) + { + return Boolean.TRUE.equals(request.getAttribute("org.eclipse.jetty.pushed")); + } + @Override public void destroy() { @@ -281,7 +296,7 @@ public class PushCacheFilter implements Filter for (Map.Entry entry : _cache.entrySet()) { PrimaryResource resource = entry.getValue(); - String value = String.format("size=%d: %s", resource._associated.size(), new TreeSet<>(resource._associated.keySet())); + String value = String.format("size=%d: %s", resource._associated.size(), new TreeSet<>(resource._associated)); result.put(entry.getKey(), value); } return result; @@ -301,7 +316,7 @@ public class PushCacheFilter implements Filter private static class PrimaryResource { - private final ConcurrentMap _associated = new ConcurrentHashMap<>(); + private final Set _associated = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final AtomicLong _timestamp = new AtomicLong(); } } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java index 4d4f3135a11..7c581b7433f 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java @@ -16,10 +16,11 @@ // ======================================================================== // - package org.eclipse.jetty.servlets; import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -41,29 +42,20 @@ import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; - -/* ------------------------------------------------------------ */ -/** - */ public class PushSessionCacheFilter implements Filter { - private static final String TARGET_ATTR="PushCacheFilter.target"; - private static final String TIMESTAMP_ATTR="PushCacheFilter.timestamp"; + private static final String TARGET_ATTR = "PushCacheFilter.target"; + private static final String TIMESTAMP_ATTR = "PushCacheFilter.timestamp"; private static final Logger LOG = Log.getLogger(PushSessionCacheFilter.class); private final ConcurrentMap _cache = new ConcurrentHashMap<>(); - - private long _associateDelay=5000L; - - /* ------------------------------------------------------------ */ - /** - * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) - */ + private long _associateDelay = 5000L; + @Override public void init(FilterConfig config) throws ServletException { - if (config.getInitParameter("associateDelay")!=null) - _associateDelay=Long.valueOf(config.getInitParameter("associateDelay")); - + if (config.getInitParameter("associateDelay") != null) + _associateDelay = Long.valueOf(config.getInitParameter("associateDelay")); + // Add a listener that is used to collect information about associated resource, // etags and modified dates config.getServletContext().addListener(new ServletRequestListener() @@ -74,45 +66,47 @@ public class PushSessionCacheFilter implements Filter { Request request = Request.getBaseRequest(sre.getServletRequest()); Target target = (Target)request.getAttribute(TARGET_ATTR); - if (target==null) + if (target == null) return; // Update conditional data Response response = request.getResponse(); - target._etag=response.getHttpFields().get(HttpHeader.ETAG); - target._lastModified=response.getHttpFields().get(HttpHeader.LAST_MODIFIED); - + target._etag = response.getHttpFields().get(HttpHeader.ETAG); + target._lastModified = response.getHttpFields().get(HttpHeader.LAST_MODIFIED); + // Don't associate pushes if (request.isPush()) { if (LOG.isDebugEnabled()) - LOG.debug("Pushed {} for {}",request.getResponse().getStatus(),request.getRequestURI()); + LOG.debug("Pushed {} for {}", request.getResponse().getStatus(), request.getRequestURI()); return; - } + } else if (LOG.isDebugEnabled()) - LOG.debug("Served {} for {}",request.getResponse().getStatus(),request.getRequestURI()); - + { + LOG.debug("Served {} for {}", request.getResponse().getStatus(), request.getRequestURI()); + } + // Does this request have a referer? String referer = request.getHttpFields().get(HttpHeader.REFERER); - - if (referer!=null) + + if (referer != null) { // Is the referer from this contexts? HttpURI referer_uri = new HttpURI(referer); if (request.getServerName().equals(referer_uri.getHost())) { Target referer_target = _cache.get(referer_uri.getPath()); - if (referer_target!=null) + if (referer_target != null) { - HttpSession session = request.getSession(); + HttpSession session = request.getSession(); ConcurrentHashMap timestamps = (ConcurrentHashMap)session.getAttribute(TIMESTAMP_ATTR); Long last = timestamps.get(referer_target._path); - if (last!=null && (System.currentTimeMillis()-last)<_associateDelay) + if (last != null && (System.currentTimeMillis() - last) < _associateDelay) { - if (referer_target._associated.putIfAbsent(target._path,target)==null) + if (referer_target._associated.putIfAbsent(target._path, target) == null) { if (LOG.isDebugEnabled()) - LOG.debug("ASSOCIATE {}->{}",referer_target._path,target._path); + LOG.debug("ASSOCIATE {}->{}", referer_target._path, target._path); } } } @@ -124,24 +118,18 @@ public class PushSessionCacheFilter implements Filter public void requestInitialized(ServletRequestEvent sre) { } - }); - } - /* ------------------------------------------------------------ */ - /** - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) - */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException - { + { // Get Jetty request as these APIs are not yet standard Request baseRequest = Request.getBaseRequest(request); - String uri=baseRequest.getRequestURI(); + String uri = baseRequest.getRequestURI(); if (LOG.isDebugEnabled()) - LOG.debug("{} {} push={}",baseRequest.getMethod(),uri,baseRequest.isPush()); + LOG.debug("{} {} push={}", baseRequest.getMethod(), uri, baseRequest.isPush()); HttpSession session = baseRequest.getSession(true); @@ -149,66 +137,70 @@ public class PushSessionCacheFilter implements Filter Target target = _cache.get(uri); if (target == null) { - Target t=new Target(uri); - target = _cache.putIfAbsent(uri,t); - target = target==null?t:target; + Target t = new Target(uri); + target = _cache.putIfAbsent(uri, t); + target = target == null ? t : target; } - request.setAttribute(TARGET_ATTR,target); - + request.setAttribute(TARGET_ATTR, target); + // Set the timestamp for this resource in this session ConcurrentHashMap timestamps = (ConcurrentHashMap)session.getAttribute(TIMESTAMP_ATTR); - if (timestamps==null) + if (timestamps == null) { - timestamps=new ConcurrentHashMap<>(); - session.setAttribute(TIMESTAMP_ATTR,timestamps); + timestamps = new ConcurrentHashMap<>(); + session.setAttribute(TIMESTAMP_ATTR, timestamps); } - timestamps.put(uri,System.currentTimeMillis()); - + timestamps.put(uri, System.currentTimeMillis()); + // push any associated resources - if (baseRequest.isPushSupported() && target._associated.size()>0) + if (baseRequest.isPushSupported() && !baseRequest.isPush() && !target._associated.isEmpty()) { - PushBuilder builder = baseRequest.getPushBuilder(); - builder.addHeader("X-Pusher",PushSessionCacheFilter.class.toString()); - for (Target associated : target._associated.values()) + // Breadth-first push of associated resources. + Queue queue = new ArrayDeque<>(); + queue.offer(target); + while (!queue.isEmpty()) { - String path = associated._path; - if (LOG.isDebugEnabled()) - LOG.debug("PUSH {} <- {}",path,uri); - - builder.path(path).etag(associated._etag).lastModified(associated._lastModified).push(); + Target parent = queue.poll(); + PushBuilder builder = baseRequest.getPushBuilder(); + builder.addHeader("X-Pusher", PushSessionCacheFilter.class.toString()); + for (Target child : parent._associated.values()) + { + queue.offer(child); + + String path = child._path; + if (LOG.isDebugEnabled()) + LOG.debug("PUSH {} <- {}", path, uri); + + builder.path(path).etag(child._etag).lastModified(child._lastModified).push(); + } } } - chain.doFilter(request,response); + chain.doFilter(request, response); } - - /* ------------------------------------------------------------ */ - /** - * @see javax.servlet.Filter#destroy() - */ @Override public void destroy() - { + { + _cache.clear(); } - - public static class Target + private static class Target { - final String _path; - final ConcurrentMap _associated = new ConcurrentHashMap<>(); - volatile String _etag; - volatile String _lastModified; - - public Target(String path) + private final String _path; + private final ConcurrentMap _associated = new ConcurrentHashMap<>(); + private volatile String _etag; + private volatile String _lastModified; + + private Target(String path) { - _path=path; + _path = path; } - + @Override public String toString() { - return String.format("Target{p=%s,e=%s,m=%s,a=%d}",_path,_etag,_lastModified,_associated.size()); + return String.format("Target{p=%s,e=%s,m=%s,a=%d}", _path, _etag, _lastModified, _associated.size()); } } } diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerPathTest.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerPathTest.java new file mode 100644 index 00000000000..694da54857e --- /dev/null +++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerPathTest.java @@ -0,0 +1,244 @@ + // +// ======================================================================== +// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.quickstart; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.util.resource.Resource; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class AttributeNormalizerPathTest +{ + @Parameters(name="{0} = {1}") + public static List data() + { + String[][] tests = { + // Can't test 'WAR' property, as its not a Path (which this testcase works with) + // { "WAR", toSystemPath("http://localhost/resources/webapps/root") }, + { "jetty.home", toSystemPath("/opt/jetty-distro") }, + { "jetty.base", toSystemPath("/opt/jetty-distro/demo.base") }, + { "user.home", toSystemPath("/home/user") }, + { "user.dir", toSystemPath("/etc/init.d") }, + }; + + return Arrays.asList(tests); + } + + /** + * As the declared paths in this testcase might be actual paths on the system + * running these tests, the expected paths should be cleaned up to represent + * the actual system paths. + *

+ * Eg: on fedora /etc/init.d is a symlink to /etc/rc.d/init.d + */ + private static String toSystemPath(String rawpath) + { + Path path = FileSystems.getDefault().getPath(rawpath); + if (Files.exists(path)) + { + // It exists, resolve it to the real path + try + { + path = path.toRealPath(); + } + catch (IOException e) + { + // something prevented us from resolving to real path, fallback to + // absolute path resolution (not as accurate) + path = path.toAbsolutePath(); + e.printStackTrace(); + } + } + else + { + // File doesn't exist, resolve to absolute path + // We can't rely on File.toCanonicalPath() here + path = path.toAbsolutePath(); + } + return path.toString(); + } + + private static String origJettyBase; + private static String origJettyHome; + private static String origUserHome; + private static String origUserDir; + + @BeforeClass + public static void initProperties() + { + origJettyBase = System.getProperty("jetty.base"); + origJettyHome = System.getProperty("jetty.home"); + origUserHome = System.getProperty("user.home"); + origUserDir = System.getProperty("user.dir"); + + System.setProperty("jetty.home","/opt/jetty-distro"); + System.setProperty("jetty.base","/opt/jetty-distro/demo.base"); + System.setProperty("user.home","/home/user"); + System.setProperty("user.dir","/etc/init.d"); + } + + @AfterClass + public static void restoreProperties() + { + if(origJettyBase != null) System.setProperty("jetty.base",origJettyBase); + if(origJettyHome != null) System.setProperty("jetty.home",origJettyHome); + if(origUserHome != null) System.setProperty("user.home",origUserHome); + if(origUserDir != null) System.setProperty("user.dir",origUserDir); + } + + @Parameter(0) + public String key; + + @Parameter(1) + public String path; + + private AttributeNormalizer normalizer; + + public AttributeNormalizerPathTest() throws MalformedURLException + { + normalizer = new AttributeNormalizer(Resource.newResource("/opt/jetty-distro/demo.base/webapps/root")); + } + + @Test + public void testEqual() + { + assertThat(normalizer.normalize("file:" + path), is("file:${" + key + "}")); + } + + @Test + public void testEqualsSlash() + { + assertThat(normalizer.normalize("file:" + path + "/"), is("file:${" + key + "}")); + } + + @Test + public void testEqualsSlashFile() + { + assertThat(normalizer.normalize("file:" + path + "/file"), is("file:${" + key + "}/file")); + } + + @Test + public void testURIEquals() throws URISyntaxException + { + assertThat(normalizer.normalize(new URI("file:" + path)), is("file:${" + key + "}")); + } + + @Test + public void testURIEqualsSlash() throws URISyntaxException + { + assertThat(normalizer.normalize(new URI("file:" + path + "/")), is("file:${" + key + "}")); + } + + @Test + public void testURIEqualsSlashFile() throws URISyntaxException + { + assertThat(normalizer.normalize(new URI("file:" + path + "/file")), is("file:${" + key + "}/file")); + } + + @Test + public void testURLEquals() throws MalformedURLException + { + assertThat(normalizer.normalize(new URL("file:" + path)), is("file:${" + key + "}")); + } + + @Test + public void testURLEqualsSlash() throws MalformedURLException + { + assertThat(normalizer.normalize(new URL("file:" + path + "/")), is("file:${" + key + "}")); + } + + @Test + public void testURLEqualsSlashFile() throws MalformedURLException + { + assertThat(normalizer.normalize(new URL("file:" + path + "/file")), is("file:${" + key + "}/file")); + } + + @Test + public void testJarFileEquals_BangFile() + { + assertThat(normalizer.normalize("jar:file:" + path + "!/file"), is("jar:file:${" + key + "}!/file")); + } + + @Test + public void testJarFileEquals_SlashBangFile() + { + assertThat(normalizer.normalize("jar:file:" + path + "/!/file"), is("jar:file:${" + key + "}!/file")); + } + + @Test + public void testJarFileEquals_FileBangFile() + { + assertThat(normalizer.normalize("jar:file:" + path + "/file!/file"), is("jar:file:${" + key + "}/file!/file")); + } + + @Test + public void testJarFileEquals_URIBangFile() throws URISyntaxException + { + assertThat(normalizer.normalize(new URI("jar:file:" + path + "!/file")), is("jar:file:${" + key + "}!/file")); + } + + @Test + public void testJarFileEquals_URISlashBangFile() throws URISyntaxException + { + assertThat(normalizer.normalize(new URI("jar:file:" + path + "/!/file")), is("jar:file:${" + key + "}!/file")); + } + + @Test + public void testJarFileEquals_URIFileBangFile() throws URISyntaxException + { + assertThat(normalizer.normalize(new URI("jar:file:" + path + "/file!/file")), is("jar:file:${" + key + "}/file!/file")); + } + + @Test + public void testJarFileEquals_URLBangFile() throws MalformedURLException + { + assertThat(normalizer.normalize(new URL("jar:file:" + path + "!/file")), is("jar:file:${" + key + "}!/file")); + } + + @Test + public void testJarFileEquals_URLSlashBangFile() throws MalformedURLException + { + assertThat(normalizer.normalize(new URL("jar:file:" + path + "/!/file")), is("jar:file:${" + key + "}!/file")); + } + + @Test + public void testJarFileEquals_URLFileBangFile() throws MalformedURLException + { + assertThat(normalizer.normalize(new URL("jar:file:" + path + "/file!/file")), is("jar:file:${" + key + "}/file!/file")); + } +} diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java index e0bec424b7b..b9c6f1e8c87 100644 --- a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java +++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java @@ -1,4 +1,4 @@ - // +// // ======================================================================== // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ @@ -18,226 +18,29 @@ package org.eclipse.jetty.quickstart; +import java.net.MalformedURLException; +import java.net.URI; + +import org.eclipse.jetty.util.resource.Resource; +import org.junit.Test; + import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; - -import org.eclipse.jetty.util.resource.Resource; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -@RunWith(Parameterized.class) public class AttributeNormalizerTest { - @Parameters(name="{0} = {1}") - public static List data() - { - String[][] tests = { - { "WAR", toSystemPath("/opt/jetty-distro/demo.base/webapps/root") }, - { "jetty.home", toSystemPath("/opt/jetty-distro") }, - { "jetty.base", toSystemPath("/opt/jetty-distro/demo.base") }, - { "user.home", toSystemPath("/home/user") }, - { "user.dir", toSystemPath("/etc/init.d") }, - }; - - return Arrays.asList(tests); - } - - /** - * As the declared paths in this testcase might be actual paths on the system - * running these tests, the expected paths should be cleaned up to represent - * the actual system paths. - *

- * Eg: on fedora /etc/init.d is a symlink to /etc/rc.d/init.d - */ - private static String toSystemPath(String rawpath) - { - Path path = FileSystems.getDefault().getPath(rawpath); - if (Files.exists(path)) - { - // It exists, resolve it to the real path - try - { - path = path.toRealPath(); - } - catch (IOException e) - { - // something prevented us from resolving to real path, fallback to - // absolute path resolution (not as accurate) - path = path.toAbsolutePath(); - e.printStackTrace(); - } - } - else - { - // File doesn't exist, resolve to absolute path - // We can't rely on File.toCanonicalPath() here - path = path.toAbsolutePath(); - } - return path.toString(); - } - - private static String origJettyBase; - private static String origJettyHome; - private static String origUserHome; - private static String origUserDir; - - @BeforeClass - public static void initProperties() - { - origJettyBase = System.getProperty("jetty.base"); - origJettyHome = System.getProperty("jetty.home"); - origUserHome = System.getProperty("user.home"); - origUserDir = System.getProperty("user.dir"); - - System.setProperty("jetty.home","/opt/jetty-distro"); - System.setProperty("jetty.base","/opt/jetty-distro/demo.base"); - System.setProperty("user.home","/home/user"); - System.setProperty("user.dir","/etc/init.d"); - } - - @AfterClass - public static void restoreProperties() - { - if(origJettyBase != null) System.setProperty("jetty.base",origJettyBase); - if(origJettyHome != null) System.setProperty("jetty.home",origJettyHome); - if(origUserHome != null) System.setProperty("user.home",origUserHome); - if(origUserDir != null) System.setProperty("user.dir",origUserDir); - } - - @Parameter(0) - public String key; - - @Parameter(1) - public String path; - - private AttributeNormalizer normalizer; - - public AttributeNormalizerTest() throws MalformedURLException - { - normalizer = new AttributeNormalizer(Resource.newResource("/opt/jetty-distro/demo.base/webapps/root")); - } - @Test - public void testEqual() + public void testNormalizeWAR() throws MalformedURLException { - assertThat(normalizer.normalize("file:" + path), is("file:${" + key + "}")); - } + String webref = "http://localhost/resource/webapps/root"; + Resource webresource = Resource.newResource(webref); + AttributeNormalizer normalizer = new AttributeNormalizer(webresource); + String result = null; - @Test - public void testEqualsSlash() - { - assertThat(normalizer.normalize("file:" + path + "/"), is("file:${" + key + "}")); - } + result = normalizer.normalize(URI.create(webref)); + assertThat(result, is("${WAR}")); - @Test - public void testEqualsSlashFile() - { - assertThat(normalizer.normalize("file:" + path + "/file"), is("file:${" + key + "}/file")); - } - - @Test - public void testURIEquals() throws URISyntaxException - { - assertThat(normalizer.normalize(new URI("file:" + path)), is("file:${" + key + "}")); - } - - @Test - public void testURIEqualsSlash() throws URISyntaxException - { - assertThat(normalizer.normalize(new URI("file:" + path + "/")), is("file:${" + key + "}")); - } - - @Test - public void testURIEqualsSlashFile() throws URISyntaxException - { - assertThat(normalizer.normalize(new URI("file:" + path + "/file")), is("file:${" + key + "}/file")); - } - - @Test - public void testURLEquals() throws MalformedURLException - { - assertThat(normalizer.normalize(new URL("file:" + path)), is("file:${" + key + "}")); - } - - @Test - public void testURLEqualsSlash() throws MalformedURLException - { - assertThat(normalizer.normalize(new URL("file:" + path + "/")), is("file:${" + key + "}")); - } - - @Test - public void testURLEqualsSlashFile() throws MalformedURLException - { - assertThat(normalizer.normalize(new URL("file:" + path + "/file")), is("file:${" + key + "}/file")); - } - - @Test - public void testJarFileEquals_BangFile() - { - assertThat(normalizer.normalize("jar:file:" + path + "!/file"), is("jar:file:${" + key + "}!/file")); - } - - @Test - public void testJarFileEquals_SlashBangFile() - { - assertThat(normalizer.normalize("jar:file:" + path + "/!/file"), is("jar:file:${" + key + "}!/file")); - } - - @Test - public void testJarFileEquals_FileBangFile() - { - assertThat(normalizer.normalize("jar:file:" + path + "/file!/file"), is("jar:file:${" + key + "}/file!/file")); - } - - @Test - public void testJarFileEquals_URIBangFile() throws URISyntaxException - { - assertThat(normalizer.normalize(new URI("jar:file:" + path + "!/file")), is("jar:file:${" + key + "}!/file")); - } - - @Test - public void testJarFileEquals_URISlashBangFile() throws URISyntaxException - { - assertThat(normalizer.normalize(new URI("jar:file:" + path + "/!/file")), is("jar:file:${" + key + "}!/file")); - } - - @Test - public void testJarFileEquals_URIFileBangFile() throws URISyntaxException - { - assertThat(normalizer.normalize(new URI("jar:file:" + path + "/file!/file")), is("jar:file:${" + key + "}/file!/file")); - } - - @Test - public void testJarFileEquals_URLBangFile() throws MalformedURLException - { - assertThat(normalizer.normalize(new URL("jar:file:" + path + "!/file")), is("jar:file:${" + key + "}!/file")); - } - - @Test - public void testJarFileEquals_URLSlashBangFile() throws MalformedURLException - { - assertThat(normalizer.normalize(new URL("jar:file:" + path + "/!/file")), is("jar:file:${" + key + "}!/file")); - } - - @Test - public void testJarFileEquals_URLFileBangFile() throws MalformedURLException - { - assertThat(normalizer.normalize(new URL("jar:file:" + path + "/file!/file")), is("jar:file:${" + key + "}/file!/file")); + result = normalizer.normalize(URI.create(webref + "/deep/ref")); + assertThat(result, is("${WAR}/deep/ref")); } }