From 3527c6a71b7247edc8ba92352b706679d958efd7 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 19 Jun 2015 16:48:53 +1000 Subject: StringUtil.csvSplit(String) Conflicts: jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java --- .../jetty/annotations/AnnotationConfiguration.java | 3 +- .../jetty/client/util/DigestAuthentication.java | 3 +- .../java/org/eclipse/jetty/http/HttpGenerator.java | 2 +- .../jetty/maven/plugin/AbstractJettyMojo.java | 3 +- .../jetty/maven/plugin/JettyWebAppContext.java | 3 +- .../eclipse/jetty/maven/plugin/OverlayConfig.java | 3 +- .../org/eclipse/jetty/maven/plugin/Starter.java | 13 +- .../eclipse/jetty/maven/plugin/WarPluginInfo.java | 5 +- .../java/org/eclipse/jetty/rhttp/gateway/Main.java | 2 +- .../eclipse/jetty/security/PropertyUserStore.java | 3 +- .../java/org/eclipse/jetty/server/Response.java | 5 +- .../eclipse/jetty/servlets/CrossOriginFilter.java | 11 +- .../java/org/eclipse/jetty/servlets/DoSFilter.java | 3 +- .../java/org/eclipse/jetty/servlets/PutFilter.java | 3 +- .../org/eclipse/jetty/servlets/PutFilterTest.java | 5 +- .../java/org/eclipse/jetty/util/StringUtil.java | 183 ++++++++++++++++++++- .../org/eclipse/jetty/util/StringUtilTest.java | 33 ++++ .../jetty/util/ssl/SslContextFactoryTest.java | 6 + .../jsr356/server/browser/JsrBrowserSocket.java | 4 +- 19 files changed, 262 insertions(+), 31 deletions(-) diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java index 4f1daa6332..2626112a21 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java @@ -46,6 +46,7 @@ import org.eclipse.jetty.annotations.AnnotationParser.Handler; import org.eclipse.jetty.plus.annotation.ContainerInitializer; import org.eclipse.jetty.util.ConcurrentHashSet; import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -274,7 +275,7 @@ public class AnnotationConfiguration extends AbstractConfiguration { _ordering = ordering; - String[] tmp = ordering.split(","); + String[] tmp = StringUtil.csvSplit(ordering); for (int i=0; i serverQOPValues = Arrays.asList(serverQOP.split(",")); + List serverQOPValues = StringUtil.csvSplit(null,serverQOP,0,serverQOP.length()); if (serverQOPValues.contains("auth")) clientQOP = "auth"; else if (serverQOPValues.contains("auth-int")) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index 520822637b..4034eec24e 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -633,7 +633,7 @@ public class HttpGenerator if (values[0]==null) { - split = field.getValue().split("\\s*,\\s*"); + split = StringUtil.csvSplit(field.getValue()); if (split.length>0) { values=new HttpHeaderValue[split.length]; diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java index 3e373a15d4..203196263c 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java @@ -47,6 +47,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.util.Scanner; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.xml.XmlConfiguration; @@ -804,7 +805,7 @@ public abstract class AbstractJettyMojo extends AbstractMojo } else { - String[] files = this.jettyXml.split(","); + String[] files = StringUtil.csvSplit(this.jettyXml); for ( String file : files ) { diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java index 0d56f0559d..b5d1dee4c0 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java @@ -39,6 +39,7 @@ import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -252,7 +253,7 @@ public class JettyWebAppContext extends WebAppContext List resources = new ArrayList(); for (String rl:resourceBases) { - String[] rs = rl.split(" *, *"); + String[] rs = StringUtil.csvSplit(rl); for (String r:rs) resources.add(r); } diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java index 287ba131cd..5f638c61cf 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.eclipse.jetty.util.StringUtil; /** * OverlayConfig @@ -48,7 +49,7 @@ public class OverlayConfig { if (fmt == null) return; - String[] atoms = fmt.split(","); + String[] atoms = StringUtil.csvSplit(fmt); for (int i=0;i= 3) { gid = atoms[0].trim(); @@ -226,7 +227,7 @@ public class Starter str = (String)props.getProperty("base.dirs"); if (str != null && !"".equals(str.trim())) { - ResourceCollection bases = new ResourceCollection(str.split(",")); + ResourceCollection bases = new ResourceCollection(StringUtil.csvSplit(str)); webApp.setWar(null); webApp.setBaseResource(bases); } @@ -235,7 +236,7 @@ public class Starter str = (String)props.get("base.dirs.orig"); if (str != null && !"".equals(str.trim())) { - ResourceCollection bases = new ResourceCollection(str.split(",")); + ResourceCollection bases = new ResourceCollection(StringUtil.csvSplit(str)); webApp.setAttribute ("org.eclipse.jetty.resources.originalBases", bases); } @@ -331,7 +332,7 @@ public class Starter if (str != null && !"".equals(str.trim())) { List jars = new ArrayList(); - String[] names = str.split(","); + String[] names = StringUtil.csvSplit(str); for (int j=0; names != null && j < names.length; j++) jars.add(new File(names[j].trim())); webApp.setWebInfLib(jars); @@ -360,7 +361,7 @@ public class Starter if ("--jetty-xml".equals(args[i])) { jettyXmls = new ArrayList(); - String[] names = args[++i].split(","); + String[] names = StringUtil.csvSplit(args[++i]); for (int j=0; names!= null && j < names.length; j++) { jettyXmls.add(new File(names[j].trim())); @@ -489,7 +490,7 @@ public class Starter { if (csv == null || "".equals(csv.trim())) return null; - String[] atoms = csv.split(","); + String[] atoms = StringUtil.csvSplit(csv); List list = new ArrayList(); for (String a:atoms) { diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WarPluginInfo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WarPluginInfo.java index f939a220cc..e8ae0e0672 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WarPluginInfo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WarPluginInfo.java @@ -28,6 +28,7 @@ import java.util.List; import org.apache.maven.model.Plugin; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.eclipse.jetty.util.StringUtil; @@ -105,7 +106,7 @@ public class WarPluginInfo if (node == null) return null; String val = node.getValue(); - _dependentMavenWarIncludes = Arrays.asList(val.split(",")); + _dependentMavenWarIncludes = StringUtil.csvSplit(null,val,0,val.length()); } return _dependentMavenWarIncludes; } @@ -134,7 +135,7 @@ public class WarPluginInfo if (node == null) return null; String val = node.getValue(); - _dependentMavenWarExcludes = Arrays.asList(val.split(",")); + _dependentMavenWarExcludes = StringUtil.csvSplit(null,val,0,val.length()); } return _dependentMavenWarExcludes; } diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Main.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Main.java index cddc8b63de..b21ce11f55 100644 --- a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Main.java +++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Main.java @@ -113,7 +113,7 @@ public class Main String argValue = matcher.group(2); if (argValue.startsWith("host,")) { - String[] typeAndSuffix = argValue.split(","); + String[] typeAndSuffix = StringUtil.split(argValue); if (typeAndSuffix.length != 2) throw new IllegalArgumentException("Invalid option " + arg + ", must be of the form --" + RETRIEVER_ARG + "=host,suffix"); diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java index b4937d3a66..9e7ec5261f 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.security.MappedLoginService.RolePrincipal; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.Scanner.BulkListener; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -153,7 +154,7 @@ public class PropertyUserStore extends AbstractLifeCycle String[] roleArray = IdentityService.NO_ROLES; if (roles != null && roles.length() > 0) { - roleArray = roles.split(","); + roleArray = StringUtil.csvSplit(roles); } known.add(username); Credential credential = Credential.getCredential(credentials); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 93cc88b181..cc1e8dcb13 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -1187,10 +1187,9 @@ public class Response implements HttpServletResponse String connection = _channel.getRequest().getHttpFields().getStringField(HttpHeader.CONNECTION); if (connection != null) { - String[] values = connection.split(","); - for (int i = 0; values != null && i < values.length; i++) + for (String value: StringUtil.csvSplit(null,connection,0,connection.length())) { - HttpHeaderValue cb = HttpHeaderValue.CACHE.get(values[0].trim()); + HttpHeaderValue cb = HttpHeaderValue.CACHE.get(value); if (cb != null) { diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java index e1ba9b988c..a8d668e9af 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java @@ -35,6 +35,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -136,7 +137,7 @@ public class CrossOriginFilter implements Filter String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM); if (allowedOriginsConfig == null) allowedOriginsConfig = "*"; - String[] allowedOrigins = allowedOriginsConfig.split(","); + String[] allowedOrigins = StringUtil.csvSplit(allowedOriginsConfig); for (String allowedOrigin : allowedOrigins) { allowedOrigin = allowedOrigin.trim(); @@ -159,7 +160,7 @@ public class CrossOriginFilter implements Filter if (allowedMethodsConfig == null) allowedMethods.addAll(DEFAULT_ALLOWED_METHODS); else - allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(","))); + allowedMethods.addAll(Arrays.asList(StringUtil.csvSplit(allowedMethodsConfig))); String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM); if (allowedHeadersConfig == null) @@ -167,7 +168,7 @@ public class CrossOriginFilter implements Filter else if ("*".equals(allowedHeadersConfig)) anyHeadersAllowed = true; else - allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(","))); + allowedHeaders.addAll(Arrays.asList(StringUtil.csvSplit(allowedHeadersConfig))); String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM); if (preflightMaxAgeConfig == null) @@ -189,7 +190,7 @@ public class CrossOriginFilter implements Filter String exposedHeadersConfig = config.getInitParameter(EXPOSED_HEADERS_PARAM); if (exposedHeadersConfig == null) exposedHeadersConfig = ""; - exposedHeaders.addAll(Arrays.asList(exposedHeadersConfig.split(","))); + exposedHeaders.addAll(Arrays.asList(StringUtil.csvSplit(exposedHeadersConfig))); String chainPreflightConfig = config.getInitParameter(OLD_CHAIN_PREFLIGHT_PARAM); if (chainPreflightConfig != null) @@ -400,7 +401,7 @@ public class CrossOriginFilter implements Filter return Collections.emptyList(); List requestedHeaders = new ArrayList(); - String[] headers = accessControlRequestHeaders.split(","); + String[] headers = StringUtil.csvSplit(accessControlRequestHeaders); for (String header : headers) { String h = header.trim(); diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java index f1e331591a..1e23bfe34f 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java @@ -52,6 +52,7 @@ import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionEvent; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; @@ -1036,7 +1037,7 @@ public class DoSFilter implements Filter public void setWhitelist(String commaSeparatedList) { List result = new ArrayList<>(); - for (String address : commaSeparatedList.split(",")) + for (String address : StringUtil.csvSplit(commaSeparatedList)) addWhitelistAddress(result, address); clearWhitelist(); _whitelist.addAll(result); diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java index b31ab31840..24a1b75ede 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java @@ -44,6 +44,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; /** @@ -331,7 +332,7 @@ public class PutFilter implements Filter if ("Allow".equalsIgnoreCase(name)) { Set options = new HashSet(); - options.addAll(Arrays.asList(value.split(" *, *"))); + options.addAll(Arrays.asList(StringUtil.csvSplit(value))); options.addAll(_operations); value=null; for (String o : options) diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java index dbce361080..d656eaf3bb 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletTester; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.StringUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -266,8 +267,8 @@ public class PutFilterTest assertEquals(HttpServletResponse.SC_OK,response.getStatus()); Set options = new HashSet(); - options.addAll(Arrays.asList(response.get("Allow").split(" *, *"))); - + String allow=response.get("Allow"); + options.addAll(StringUtil.csvSplit(null,allow,0,allow.length())); assertTrue(options.contains("GET")); assertTrue(options.contains("POST")); assertTrue(options.contains("PUT")); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java index e82b8c9387..9c3315f7cc 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java @@ -21,6 +21,8 @@ package org.eclipse.jetty.util; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -721,6 +723,11 @@ public class StringUtil return str.substring(0,maxSize); } + /** + * Parse the string representation of a list using {@link #csvSplit(List,String,int,int)} + * @param s The string to parse, expected to be enclosed as '[...]' + * @return An array of parsed values. + */ public static String[] arrayFromString(String s) { if (s==null) @@ -731,9 +738,183 @@ public class StringUtil if (s.length()==2) return new String[]{}; - return s.substring(1,s.length()-1).split(" *, *"); + return csvSplit(s,1,s.length()-2); + } + + /** + * Parse a CSV string using {@link #csvSplit(List,String, int, int)} + * @param s The string to parse + * @return An array of parsed values. + */ + public static String[] csvSplit(String s) + { + if (s==null) + return null; + return csvSplit(s,0,s.length()); } + /** + * Parse a CSV string using {@link #csvSplit(List,String, int, int)} + * @param s The string to parse + * @param off The offset into the string to start parsing + * @param len The len in characters to parse + * @return An array of parsed values. + */ + public static String[] csvSplit(String s, int off,int len) + { + if (s==null) + return null; + if (off<0 || len<0 || off>s.length()) + throw new IllegalArgumentException(); + + List list = new ArrayList<>(); + csvSplit(list,s,off,len); + return list.toArray(new String[list.size()]); + } + + enum CsvSplitState { PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA }; + + /** Split a quoted comma separated string to a list + *

Handle rfc4180-like + * CSV strings, with the exceptions:

    + *
  • quoted values may contain double quotes escaped with back-slash + *
  • Non-quoted values are trimmed of leading trailing white space + *
  • trailing commas are ignored + *
  • double commas result in a empty string value + *
+ * @param list The Collection to split to (or null to get a new list) + * @param s The string to parse + * @param off The offset into the string to start parsing + * @param len The len in characters to parse + * @return list containing the parsed list values + */ + public static List csvSplit(List list,String s, int off,int len) + { + if (list==null) + list=new ArrayList<>(); + CsvSplitState state = CsvSplitState.PRE_DATA; + StringBuilder out = new StringBuilder(); + int last=-1; + while (len>0) + { + char ch = s.charAt(off++); + len--; + + switch(state) + { + case PRE_DATA: + if (Character.isWhitespace(ch)) + continue; + + if ('"'==ch) + { + state=CsvSplitState.QUOTE; + continue; + } + + if (','==ch) + { + list.add(""); + continue; + } + + state=CsvSplitState.DATA; + out.append(ch); + continue; + + case DATA: + if (Character.isWhitespace(ch)) + { + last=out.length(); + out.append(ch); + state=CsvSplitState.WHITE; + continue; + } + + if (','==ch) + { + list.add(out.toString()); + out.setLength(0); + state=CsvSplitState.PRE_DATA; + continue; + } + + out.append(ch); + continue; + + case WHITE: + if (Character.isWhitespace(ch)) + { + out.append(ch); + continue; + } + + if (','==ch) + { + out.setLength(last); + list.add(out.toString()); + out.setLength(0); + state=CsvSplitState.PRE_DATA; + continue; + } + + state=CsvSplitState.DATA; + out.append(ch); + last=-1; + continue; + + case QUOTE: + if ('\\'==ch) + { + state=CsvSplitState.SLOSH; + continue; + } + if ('"'==ch) + { + list.add(out.toString()); + out.setLength(0); + state=CsvSplitState.POST_DATA; + continue; + } + out.append(ch); + continue; + + case SLOSH: + out.append(ch); + state=CsvSplitState.QUOTE; + continue; + + case POST_DATA: + if (','==ch) + { + state=CsvSplitState.PRE_DATA; + continue; + } + continue; + } + } + + switch(state) + { + case PRE_DATA: + case POST_DATA: + break; + + case DATA: + case QUOTE: + case SLOSH: + list.add(out.toString()); + break; + + case WHITE: + out.setLength(last); + list.add(out.toString()); + break; + } + + return list; + } + public static String sanitizeXmlString(String html) { if (html==null) diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java index 8c4b05f8ee..394d1f3fe7 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java @@ -18,12 +18,17 @@ package org.eclipse.jetty.util; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.emptyArray; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.nio.charset.StandardCharsets; +import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -241,4 +246,32 @@ public class StringUtilTest assertEquals("Hello <Cruel> World",StringUtil.sanitizeXmlString("Hello World")); assertEquals("Hello ? World",StringUtil.sanitizeXmlString("Hello \u0000 World")); } + + @Test + public void testSplit() + { + assertThat(StringUtil.csvSplit(null),nullValue()); + assertThat(StringUtil.csvSplit(null),nullValue()); + + assertThat(StringUtil.csvSplit(""),emptyArray()); + assertThat(StringUtil.csvSplit(" \t\n"),emptyArray()); + + assertThat(StringUtil.csvSplit("aaa"),arrayContaining("aaa")); + assertThat(StringUtil.csvSplit(" \taaa\n"),arrayContaining("aaa")); + assertThat(StringUtil.csvSplit(" \ta\n"),arrayContaining("a")); + assertThat(StringUtil.csvSplit(" \t\u1234\n"),arrayContaining("\u1234")); + + assertThat(StringUtil.csvSplit("aaa,bbb,ccc"),arrayContaining("aaa","bbb","ccc")); + assertThat(StringUtil.csvSplit("aaa,,ccc"),arrayContaining("aaa","","ccc")); + assertThat(StringUtil.csvSplit(",b b,"),arrayContaining("","b b")); + assertThat(StringUtil.csvSplit(",,bbb,,"),arrayContaining("","","bbb","")); + + assertThat(StringUtil.csvSplit(" aaa, bbb, ccc"),arrayContaining("aaa","bbb","ccc")); + assertThat(StringUtil.csvSplit("aaa,\t,ccc"),arrayContaining("aaa","","ccc")); + assertThat(StringUtil.csvSplit(" , b b , "),arrayContaining("","b b")); + assertThat(StringUtil.csvSplit(" ,\n,bbb, , "),arrayContaining("","","bbb","")); + + assertThat(StringUtil.csvSplit("\"aaa\", \" b,\\\"\",\"\""),arrayContaining("aaa"," b,\"","")); + } + } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java index a59a4b33cd..e14fa54f9a 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java @@ -31,11 +31,14 @@ import java.security.KeyStore; import javax.net.ssl.SSLEngine; +import org.eclipse.jetty.toolchain.test.JDK; +import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StdErrLog; import org.eclipse.jetty.util.resource.Resource; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -208,6 +211,9 @@ public class SslContextFactoryTest @Test public void testSetIncludeCipherSuitesRegex() throws Exception { + // Test does not work on JDK 8+ (RC4 is disabled) + Assume.assumeFalse(JDK.IS_8); + cf.setIncludeCipherSuites(".*RC4.*"); cf.start(); SSLEngine sslEngine = cf.newSSLEngine(); diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java index ace05d8205..8540689a72 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java @@ -132,7 +132,7 @@ public class JsrBrowserSocket } case "many": { - String parts[] = val.split(","); + String parts[] = StringUtil.csvSplit(val); int size = Integer.parseInt(parts[0]); int count = Integer.parseInt(parts[1]); @@ -141,7 +141,7 @@ public class JsrBrowserSocket } case "manythreads": { - String parts[] = val.split(","); + String parts[] = StringUtil.csvSplit(val); int threadCount = Integer.parseInt(parts[0]); int size = Integer.parseInt(parts[1]); int count = Integer.parseInt(parts[2]); -- cgit v1.2.3 From e2a20e04652a08ceabe1d6e29f99e9387b7176be Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 13 Nov 2015 12:36:35 -0700 Subject: Using Map interface to get around ConcurrentHashMap.keySet() bug with Java 8 See https://gist.github.com/AlainODea/1375759b8720a3f9f094 for details Conflicts: jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java --- .../main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java index ed4987138f..a591fcb6a6 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.servlet.listener; import java.lang.reflect.Field; import java.util.Iterator; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletContextEvent; @@ -98,7 +99,7 @@ public class ELContextCleaner implements ServletContextListener if (!properties.isAccessible()) properties.setAccessible(true); - ConcurrentHashMap, Object> map = (ConcurrentHashMap, Object>) properties.get(null); + Map map = (Map) properties.get(null); if (map == null) return; -- cgit v1.2.3 From 5e3fbbccd0df05049f248e5ac3a9207feaeb3b29 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Dec 2015 15:51:25 -0700 Subject: Backporting GzipHandler's IncludeExclude configs --- .../main/java/org/eclipse/jetty/http/PathMap.java | 50 +++ .../org/eclipse/jetty/servlets/GzipFilter.java | 9 +- .../servlets/gzip/CompressedResponseWrapper.java | 10 +- .../eclipse/jetty/servlets/gzip/GzipHandler.java | 406 +++++++++++++++------ .../org/eclipse/jetty/util/IncludeExclude.java | 136 +++++++ .../main/java/org/eclipse/jetty/util/RegexSet.java | 103 ++++++ .../org/eclipse/jetty/util/IncludeExcludeTest.java | 153 ++++++++ .../java/org/eclipse/jetty/util/RegexSetTest.java | 86 +++++ 8 files changed, 843 insertions(+), 110 deletions(-) create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 2c68dcea7f..482c92b9da 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -18,14 +18,17 @@ package org.eclipse.jetty.http; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.jetty.util.ArrayTernaryTrie; +import org.eclipse.jetty.util.IncludeExclude; import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.URIUtil; @@ -569,4 +572,51 @@ public class PathMap extends HashMap this.mapped = mapped; } } + + public static class PathSet extends AbstractSet implements IncludeExclude.MatchSet + { + private final PathMap _map = new PathMap<>(); + + @Override + public Iterator iterator() + { + return _map.keySet().iterator(); + } + + @Override + public int size() + { + return _map.size(); + } + + @Override + public boolean add(String item) + { + return _map.put(item,Boolean.TRUE)==null; + } + + @Override + public boolean remove(Object item) + { + return _map.remove(item)!=null; + } + + @Override + public boolean contains(Object o) + { + return _map.containsKey(o); + } + + + public boolean containsMatch(String s) + { + return _map.containsMatch(s); + } + + @Override + public boolean matches(String item) + { + return _map.containsMatch(item); + } + } } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java index 24738a1989..8f559ad9b1 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java @@ -45,6 +45,7 @@ import org.eclipse.jetty.servlets.gzip.AbstractCompressedStream; import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper; import org.eclipse.jetty.servlets.gzip.DeflatedOutputStream; import org.eclipse.jetty.servlets.gzip.GzipOutputStream; +import org.eclipse.jetty.util.IncludeExclude; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -539,7 +540,13 @@ public class GzipFilter extends UserAgentFilter protected void configureWrappedResponse(CompressedResponseWrapper wrappedResponse) { - wrappedResponse.setMimeTypes(_mimeTypes,_excludeMimeTypes); + IncludeExclude mimeTypeExclusions = new IncludeExclude<>(); + if(_excludeMimeTypes) + mimeTypeExclusions.getExcluded().addAll(_mimeTypes); + else + mimeTypeExclusions.getIncluded().addAll(_mimeTypes); + + wrappedResponse.setMimeTypes(mimeTypeExclusions); wrappedResponse.setBufferSize(_bufferSize); wrappedResponse.setMinCompressSize(_minGzipSize); } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java index b9ad084495..fdd7fb60ea 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java @@ -23,13 +23,13 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; -import java.util.Set; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; +import org.eclipse.jetty.util.IncludeExclude; import org.eclipse.jetty.util.StringUtil; /*------------------------------------------------------------ */ @@ -41,8 +41,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp public static final int DEFAULT_BUFFER_SIZE = 8192; public static final int DEFAULT_MIN_COMPRESS_SIZE = 256; - private Set _mimeTypes; - private boolean _excludeMimeTypes; + private IncludeExclude _mimeTypes; private int _bufferSize=DEFAULT_BUFFER_SIZE; private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE; protected HttpServletRequest _request; @@ -95,9 +94,8 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp /* ------------------------------------------------------------ */ /** */ - public void setMimeTypes(Set mimeTypes,boolean excludeMimeTypes) + public void setMimeTypes(IncludeExclude mimeTypes) { - _excludeMimeTypes=excludeMimeTypes; _mimeTypes = mimeTypes; } @@ -138,7 +136,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp if (colon>0) ct=ct.substring(0,colon); - if (_mimeTypes.contains(StringUtil.asciiToLowerCase(ct))==_excludeMimeTypes) + if (!_mimeTypes.matches(StringUtil.asciiToLowerCase(ct))) noCompression(); } } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java index c9976e4795..30a8d784fc 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java @@ -23,22 +23,26 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; -import java.util.HashSet; import java.util.Set; -import java.util.StringTokenizer; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.util.IncludeExclude; +import org.eclipse.jetty.util.RegexSet; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -63,12 +67,17 @@ public class GzipHandler extends HandlerWrapper { private static final Logger LOG = Log.getLogger(GzipHandler.class); - final protected Set _mimeTypes=new HashSet<>(); - protected boolean _excludeMimeTypes=false; - protected Set _excludedUA; + // final protected Set _mimeTypes=new HashSet<>(); + // protected boolean _excludeMimeTypes=false; + // protected Set _excludedUA; protected int _bufferSize = 8192; protected int _minGzipSize = 256; protected String _vary = "Accept-Encoding, User-Agent"; + + private final IncludeExclude _agentPatterns=new IncludeExclude<>(RegexSet.class); + private final IncludeExclude _methods = new IncludeExclude<>(); + private final IncludeExclude _paths = new IncludeExclude<>(PathMap.PathSet.class); + private final IncludeExclude _mimeTypes = new IncludeExclude<>(); /* ------------------------------------------------------------ */ /** @@ -76,6 +85,174 @@ public class GzipHandler extends HandlerWrapper */ public GzipHandler() { + _methods.include(HttpMethod.GET.asString()); + for (String type:MimeTypes.getKnownMimeTypes()) + { + if ("image/svg+xml".equals(type)) + _paths.exclude("*.svgz"); + else if (type.startsWith("image/")|| + type.startsWith("audio/")|| + type.startsWith("video/")) + _mimeTypes.exclude(type); + } + _mimeTypes.exclude("application/compress"); + _mimeTypes.exclude("application/zip"); + _mimeTypes.exclude("application/gzip"); + _mimeTypes.exclude("application/bzip2"); + _mimeTypes.exclude("application/x-rar-compressed"); + LOG.debug("{} mime types {}",this,_mimeTypes); + + _agentPatterns.exclude(".*MSIE 6.0.*"); + } + + /* ------------------------------------------------------------ */ + /** + * @param patterns Regular expressions matching user agents to exclude + */ + public void addExcludedAgentPatterns(String... patterns) + { + _agentPatterns.exclude(patterns); + } + + /* ------------------------------------------------------------ */ + /** + * @param methods The methods to exclude in compression + */ + public void addExcludedMethods(String... methods) + { + for (String m : methods) + _methods.exclude(m); + } + + /* ------------------------------------------------------------ */ + /** + * Set the mime types. + * @param types The mime types to exclude (without charset or other parameters). + * For backward compatibility the mimetypes may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addExcludedMimeTypes(String... types) + { + for (String t : types) + _mimeTypes.exclude(StringUtil.csvSplit(t)); + } + + /* ------------------------------------------------------------ */ + /** + * @param pathspecs Path specs (as per servlet spec) to exclude. If a + * ServletContext is available, the paths are relative to the context path, + * otherwise they are absolute. + * For backward compatibility the pathspecs may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addExcludedPaths(String... pathspecs) + { + for (String p : pathspecs) + _paths.exclude(StringUtil.csvSplit(p)); + } + + /* ------------------------------------------------------------ */ + /** + * @param patterns Regular expressions matching user agents to exclude + */ + public void addIncludedAgentPatterns(String... patterns) + { + _agentPatterns.include(patterns); + } + + /* ------------------------------------------------------------ */ + /** + * @param methods The methods to include in compression + */ + public void addIncludedMethods(String... methods) + { + for (String m : methods) + _methods.include(m); + } + + /* ------------------------------------------------------------ */ + /** + * Add included mime types. Inclusion takes precedence over + * exclusion. + * @param types The mime types to include (without charset or other parameters) + * For backward compatibility the mimetypes may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addIncludedMimeTypes(String... types) + { + for (String t : types) + _mimeTypes.include(StringUtil.csvSplit(t)); + } + + /* ------------------------------------------------------------ */ + /** + * Add path specs to include. Inclusion takes precedence over exclusion. + * @param pathspecs Path specs (as per servlet spec) to include. If a + * ServletContext is available, the paths are relative to the context path, + * otherwise they are absolute + * For backward compatibility the pathspecs may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addIncludedPaths(String... pathspecs) + { + for (String p : pathspecs) + _paths.include(StringUtil.csvSplit(p)); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedAgentPatterns() + { + Set excluded=_agentPatterns.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedMethods() + { + Set excluded=_methods.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedMimeTypes() + { + Set excluded=_mimeTypes.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedPaths() + { + Set excluded=_paths.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedAgentPatterns() + { + Set includes=_agentPatterns.getIncluded(); + return includes.toArray(new String[includes.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedMethods() + { + Set includes=_methods.getIncluded(); + return includes.toArray(new String[includes.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedMimeTypes() + { + Set includes=_mimeTypes.getIncluded(); + return includes.toArray(new String[includes.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedPaths() + { + Set includes=_paths.getIncluded(); + return includes.toArray(new String[includes.size()]); } /* ------------------------------------------------------------ */ @@ -84,9 +261,10 @@ public class GzipHandler extends HandlerWrapper * * @return mime types to set */ + @Deprecated public Set getMimeTypes() { - return _mimeTypes; + throw new UnsupportedOperationException("Use getIncludedMimeTypes or getExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -96,11 +274,10 @@ public class GzipHandler extends HandlerWrapper * @param mimeTypes * the mime types to set */ + @Deprecated public void setMimeTypes(Set mimeTypes) { - _excludeMimeTypes=false; - _mimeTypes.clear(); - _mimeTypes.addAll(mimeTypes); + throw new UnsupportedOperationException("Use setIncludedMimeTypes or setExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -110,18 +287,10 @@ public class GzipHandler extends HandlerWrapper * @param mimeTypes * the mime types to set */ + @Deprecated public void setMimeTypes(String mimeTypes) { - if (mimeTypes != null) - { - _excludeMimeTypes=false; - _mimeTypes.clear(); - StringTokenizer tok = new StringTokenizer(mimeTypes,",",false); - while (tok.hasMoreTokens()) - { - _mimeTypes.add(tok.nextToken()); - } - } + throw new UnsupportedOperationException("Use setIncludedMimeTypes or setExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -130,7 +299,7 @@ public class GzipHandler extends HandlerWrapper */ public void setExcludeMimeTypes(boolean exclude) { - _excludeMimeTypes=exclude; + throw new UnsupportedOperationException("Use setExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -141,7 +310,7 @@ public class GzipHandler extends HandlerWrapper */ public Set getExcluded() { - return _excludedUA; + return _agentPatterns.getExcluded(); } /* ------------------------------------------------------------ */ @@ -153,7 +322,8 @@ public class GzipHandler extends HandlerWrapper */ public void setExcluded(Set excluded) { - _excludedUA = excluded; + _agentPatterns.getExcluded().clear(); + _agentPatterns.getExcluded().addAll(excluded); } /* ------------------------------------------------------------ */ @@ -165,12 +335,11 @@ public class GzipHandler extends HandlerWrapper */ public void setExcluded(String excluded) { + _agentPatterns.getExcluded().clear(); + if (excluded != null) { - _excludedUA = new HashSet(); - StringTokenizer tok = new StringTokenizer(excluded,",",false); - while (tok.hasMoreTokens()) - _excludedUA.add(tok.nextToken()); + _agentPatterns.exclude(StringUtil.csvSplit(excluded)); } } @@ -249,22 +418,6 @@ public class GzipHandler extends HandlerWrapper @Override protected void doStart() throws Exception { - if (_mimeTypes.size()==0 && _excludeMimeTypes) - { - _excludeMimeTypes = true; - for (String type:MimeTypes.getKnownMimeTypes()) - { - if (type.equals("image/svg+xml")) //always compressable (unless .svgz file) - continue; - if (type.startsWith("image/")|| - type.startsWith("audio/")|| - type.startsWith("video/")) - _mimeTypes.add(type); - } - _mimeTypes.add("application/compress"); - _mimeTypes.add("application/zip"); - _mimeTypes.add("application/gzip"); - } super.doStart(); } @@ -275,80 +428,127 @@ public class GzipHandler extends HandlerWrapper @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if (_handler!=null && isStarted()) + if(_handler == null || !isStarted()) { - String ae = request.getHeader("accept-encoding"); - if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding") - && !HttpMethod.HEAD.is(request.getMethod())) - { - if (_excludedUA!=null) - { - String ua = request.getHeader("User-Agent"); - if (_excludedUA.contains(ua)) - { - _handler.handle(target,baseRequest, request, response); - return; - } - } - - final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response); + // do nothing + return; + } + + if(isGzippable(baseRequest, request, response)) + { + final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response); - boolean exceptional=true; - try - { - _handler.handle(target, baseRequest, request, wrappedResponse); - exceptional=false; - } - finally + boolean exceptional=true; + try + { + _handler.handle(target, baseRequest, request, wrappedResponse); + exceptional=false; + } + finally + { + if (request.isAsyncStarted()) { - if (request.isAsyncStarted()) + request.getAsyncContext().addListener(new AsyncListener() { - request.getAsyncContext().addListener(new AsyncListener() + + @Override + public void onTimeout(AsyncEvent event) throws IOException { - - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - } - - @Override - public void onStartAsync(AsyncEvent event) throws IOException - { - } - - @Override - public void onError(AsyncEvent event) throws IOException + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException + { + } + + @Override + public void onError(AsyncEvent event) throws IOException + { + } + + @Override + public void onComplete(AsyncEvent event) throws IOException + { + try { + wrappedResponse.finish(); } - - @Override - public void onComplete(AsyncEvent event) throws IOException + catch(IOException e) { - try - { - wrappedResponse.finish(); - } - catch(IOException e) - { - LOG.warn(e); - } + LOG.warn(e); } - }); - } - else if (exceptional && !response.isCommitted()) - { - wrappedResponse.resetBuffer(); - wrappedResponse.noCompression(); - } - else - wrappedResponse.finish(); + } + }); + } + else if (exceptional && !response.isCommitted()) + { + wrappedResponse.resetBuffer(); + wrappedResponse.noCompression(); } + else + wrappedResponse.finish(); } - else + } + else + { + _handler.handle(target,baseRequest, request, response); + } + } + + private boolean isGzippable(Request baseRequest, HttpServletRequest request, HttpServletResponse response) + { + String ae = request.getHeader("accept-encoding"); + if (ae == null || !ae.contains("gzip")) + { + // Request not indicated for Gzip + return false; + } + + if(response.containsHeader("Content-Encoding")) + { + // Response is already declared, can't gzip + LOG.debug("{} excluded as Content-Encoding already declared {}",this,request); + return false; + } + + if(HttpMethod.HEAD.is(request.getMethod())) + { + // HEAD is never Gzip'd + LOG.debug("{} excluded by method {}",this,request); + return false; + } + + // Exclude based on Request Method + if (!_methods.matches(baseRequest.getMethod())) + { + LOG.debug("{} excluded by method {}",this,request); + return false; + } + + // Exclude based on Request Path + ServletContext context = baseRequest.getServletContext(); + String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo()); + + if(path != null && !_paths.matches(path)) + { + LOG.debug("{} excluded by path {}",this,request); + return false; + } + + + // Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded + String mimeType = context==null?null:context.getMimeType(path); + if (mimeType!=null) + { + mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType); + if (!_mimeTypes.matches(mimeType)) { - _handler.handle(target,baseRequest, request, response); + LOG.debug("{} excluded by path suffix mime type {}",this,request); + return false; } } + + return true; } /** @@ -363,7 +563,7 @@ public class GzipHandler extends HandlerWrapper return new CompressedResponseWrapper(request,response) { { - super.setMimeTypes(GzipHandler.this._mimeTypes,GzipHandler.this._excludeMimeTypes); + super.setMimeTypes(GzipHandler.this._mimeTypes); super.setBufferSize(GzipHandler.this._bufferSize); super.setMinCompressSize(GzipHandler.this._minGzipSize); } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java new file mode 100644 index 0000000000..b5af37356e --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java @@ -0,0 +1,136 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import java.util.HashSet; +import java.util.Set; + + +/** Utility class to maintain a set of inclusions and exclusions. + *

Maintains a set of included and excluded elements. The method {@link #matches(Object)} + * will return true IFF the passed object is not in the excluded set AND ( either the + * included set is empty OR the object is in the included set) + *

The type of the underlying {@link Set} used may be passed into the + * constructor, so special sets like Servlet PathMap may be used. + *

+ * @param The type of element + */ +public class IncludeExclude +{ + public interface MatchSet extends Set + { + public boolean matches(ITEM item); + } + + @SuppressWarnings("serial") + protected static class ContainsMatchSet extends HashSet implements MatchSet + { + @Override + public boolean matches(ITEM item) + { + return contains(item); + } + } + + private final MatchSet _includes; + private final MatchSet _excludes; + + /** + * Default constructor over {@link HashSet} + */ + public IncludeExclude() + { + _includes = new ContainsMatchSet(); + _excludes = new ContainsMatchSet(); + } + + /** + * Construct an IncludeExclude + * @param setClass The type of {@link Set} to using internally + * @param matcher A function to test if a passed ITEM is matched by the passed SET, or null to use {@link Set#contains(Object)} + */ + public IncludeExclude(Class> setClass) + { + try + { + _includes = setClass.newInstance(); + _excludes = setClass.newInstance(); + } + catch (InstantiationException | IllegalAccessException e) + { + throw new RuntimeException(e); + } + } + + public void include(ITEM element) + { + _includes.add(element); + } + + public void include(ITEM... element) + { + for (ITEM e: element) + _includes.add(e); + } + + public void exclude(ITEM element) + { + _excludes.add(element); + } + + public void exclude(ITEM... element) + { + for (ITEM e: element) + _excludes.add(e); + } + + public boolean matches(ITEM e) + { + if (_includes.size()>0 && !_includes.matches(e)) + return false; + return !_excludes.matches(e); + } + + public int size() + { + return _includes.size()+_excludes.size(); + } + + public Set getIncluded() + { + return _includes; + } + + public Set getExcluded() + { + return _excludes; + } + + public void clear() + { + _includes.clear(); + _excludes.clear(); + } + + @Override + public String toString() + { + return String.format("%s@%x{i=%s,e=%s}",this.getClass().getSimpleName(),hashCode(),_includes,_excludes); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java new file mode 100644 index 0000000000..288e5154f2 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java @@ -0,0 +1,103 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import java.util.AbstractSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * A Set of Regular expressions strings. + *

+ * Provides the efficient {@link #matches(String)} method to check for a match against all the combined Regex's + */ +public class RegexSet extends AbstractSet implements IncludeExclude.MatchSet +{ + private final Set _patterns=new HashSet(); + private final Set _unmodifiable=Collections.unmodifiableSet(_patterns); + private Pattern _pattern; + + @Override + public Iterator iterator() + { + return _unmodifiable.iterator(); + } + + @Override + public int size() + { + return _patterns.size(); + } + + @Override + public boolean add(String pattern) + { + boolean added = _patterns.add(pattern); + if (added) + updatePattern(); + return added; + } + + @Override + public boolean remove(Object pattern) + { + boolean removed = _patterns.remove(pattern); + + if (removed) + updatePattern(); + return removed; + } + + @Override + public boolean isEmpty() + { + return _patterns.isEmpty(); + } + + @Override + public void clear() + { + _patterns.clear(); + _pattern=null; + } + + private void updatePattern() + { + StringBuilder builder = new StringBuilder(); + builder.append("^("); + for (String pattern: _patterns) + { + if (builder.length()>2) + builder.append('|'); + builder.append('('); + builder.append(pattern); + builder.append(')'); + } + builder.append(")$"); + _pattern = Pattern.compile(builder.toString()); + } + + public boolean matches(String s) + { + return _pattern!=null && _pattern.matcher(s).matches(); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java new file mode 100644 index 0000000000..c2b4d61559 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java @@ -0,0 +1,153 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IncludeExcludeTest +{ + @Test + public void testEmpty() + { + IncludeExclude ie = new IncludeExclude<>(); + + assertEquals(0,ie.size()); + assertEquals(true,ie.matches("foo")); + } + + @Test + public void testIncludeOnly() + { + IncludeExclude ie = new IncludeExclude<>(); + ie.include("foo"); + ie.include("bar"); + + assertEquals(2,ie.size()); + assertEquals(false,ie.matches("")); + assertEquals(true,ie.matches("foo")); + assertEquals(true,ie.matches("bar")); + assertEquals(false,ie.matches("foobar")); + } + + @Test + public void testExcludeOnly() + { + IncludeExclude ie = new IncludeExclude<>(); + ie.exclude("foo"); + ie.exclude("bar"); + + assertEquals(2,ie.size()); + + assertEquals(false,ie.matches("foo")); + assertEquals(false,ie.matches("bar")); + assertEquals(true,ie.matches("")); + assertEquals(true,ie.matches("foobar")); + assertEquals(true,ie.matches("wibble")); + } + + @Test + public void testIncludeExclude() + { + IncludeExclude ie = new IncludeExclude<>(); + ie.include("foo"); + ie.include("bar"); + ie.exclude("bar"); + ie.exclude("xxx"); + + assertEquals(4,ie.size()); + + assertEquals(true,ie.matches("foo")); + assertEquals(false,ie.matches("bar")); + assertEquals(false,ie.matches("")); + assertEquals(false,ie.matches("foobar")); + assertEquals(false,ie.matches("xxx")); + } + + + + @Test + public void testEmptyRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + + assertEquals(0,ie.size()); + assertEquals(true,ie.matches("foo")); + } + + @Test + public void testIncludeRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + ie.include("f.."); + ie.include("b((ar)|(oo))"); + + assertEquals(2,ie.size()); + assertEquals(false,ie.matches("")); + assertEquals(true,ie.matches("foo")); + assertEquals(true,ie.matches("far")); + assertEquals(true,ie.matches("bar")); + assertEquals(true,ie.matches("boo")); + assertEquals(false,ie.matches("foobar")); + assertEquals(false,ie.matches("xxx")); + } + + @Test + public void testExcludeRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + ie.exclude("f.."); + ie.exclude("b((ar)|(oo))"); + + assertEquals(2,ie.size()); + + assertEquals(false,ie.matches("foo")); + assertEquals(false,ie.matches("far")); + assertEquals(false,ie.matches("bar")); + assertEquals(false,ie.matches("boo")); + assertEquals(true,ie.matches("")); + assertEquals(true,ie.matches("foobar")); + assertEquals(true,ie.matches("xxx")); + } + + @Test + public void testIncludeExcludeRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + ie.include(".*[aeiou].*"); + ie.include("[AEIOU].*"); + ie.exclude("f.."); + ie.exclude("b((ar)|(oo))"); + + assertEquals(4,ie.size()); + assertEquals(false,ie.matches("foo")); + assertEquals(false,ie.matches("far")); + assertEquals(false,ie.matches("bar")); + assertEquals(false,ie.matches("boo")); + assertEquals(false,ie.matches("")); + assertEquals(false,ie.matches("xxx")); + + assertEquals(true,ie.matches("foobar")); + assertEquals(true,ie.matches("Ant")); + + } + + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java new file mode 100644 index 0000000000..f80c2eda5c --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java @@ -0,0 +1,86 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import org.junit.Test; + +import org.junit.Assert; + +public class RegexSetTest +{ + + @Test + public void testEmpty() + { + RegexSet set = new RegexSet(); + + Assert.assertEquals(false,set.contains("foo")); + Assert.assertEquals(false,set.matches("foo")); + Assert.assertEquals(false,set.matches("")); + + } + + @Test + public void testSimple() + { + RegexSet set = new RegexSet(); + set.add("foo.*"); + + Assert.assertEquals(true,set.contains("foo.*")); + Assert.assertEquals(true,set.matches("foo")); + Assert.assertEquals(true,set.matches("foobar")); + Assert.assertEquals(false,set.matches("bar")); + Assert.assertEquals(false,set.matches("")); + + } + + @Test + public void testSimpleTerminated() + { + RegexSet set = new RegexSet(); + set.add("^foo.*$"); + + Assert.assertEquals(true,set.contains("^foo.*$")); + Assert.assertEquals(true,set.matches("foo")); + Assert.assertEquals(true,set.matches("foobar")); + Assert.assertEquals(false,set.matches("bar")); + Assert.assertEquals(false,set.matches("")); + } + + @Test + public void testCombined() + { + RegexSet set = new RegexSet(); + set.add("^foo.*$"); + set.add("bar"); + set.add("[a-z][0-9][a-z][0-9]"); + + Assert.assertEquals(true,set.contains("^foo.*$")); + Assert.assertEquals(true,set.matches("foo")); + Assert.assertEquals(true,set.matches("foobar")); + Assert.assertEquals(true,set.matches("bar")); + Assert.assertEquals(true,set.matches("c3p0")); + Assert.assertEquals(true,set.matches("r2d2")); + + Assert.assertEquals(false,set.matches("wibble")); + Assert.assertEquals(false,set.matches("barfoo")); + Assert.assertEquals(false,set.matches("2b!b")); + Assert.assertEquals(false,set.matches("")); + } +} -- cgit v1.2.3 From bf104f71abb7464bf98c059138f193a7ed221160 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Dec 2015 18:40:48 -0700 Subject: GzipHandler Deprecations and User-Agent + Adding javadoc @deprecated for deprecated methods + Adding exclude support for user-agent --- .../org/eclipse/jetty/servlets/gzip/GzipHandler.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java index 30a8d784fc..7a843a5879 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java @@ -67,9 +67,6 @@ public class GzipHandler extends HandlerWrapper { private static final Logger LOG = Log.getLogger(GzipHandler.class); - // final protected Set _mimeTypes=new HashSet<>(); - // protected boolean _excludeMimeTypes=false; - // protected Set _excludedUA; protected int _bufferSize = 8192; protected int _minGzipSize = 256; protected String _vary = "Accept-Encoding, User-Agent"; @@ -260,6 +257,7 @@ public class GzipHandler extends HandlerWrapper * Get the mime types. * * @return mime types to set + * @deprecated use {@link #getExcludedMimeTypes()} or {@link #getIncludedMimeTypes()} instead */ @Deprecated public Set getMimeTypes() @@ -273,6 +271,7 @@ public class GzipHandler extends HandlerWrapper * * @param mimeTypes * the mime types to set + * @deprecated use {@link #setExcludedMimeTypes()} or {@link #setIncludedMimeTypes()} instead */ @Deprecated public void setMimeTypes(Set mimeTypes) @@ -286,6 +285,7 @@ public class GzipHandler extends HandlerWrapper * * @param mimeTypes * the mime types to set + * @deprecated use {@link #setExcludedMimeTypes()} or {@link #setIncludedMimeTypes()} instead */ @Deprecated public void setMimeTypes(String mimeTypes) @@ -296,7 +296,9 @@ public class GzipHandler extends HandlerWrapper /* ------------------------------------------------------------ */ /** * Set the mime types. + * @deprecated use {@link #setExcludedMimeTypes()} instead */ + @Deprecated public void setExcludeMimeTypes(boolean exclude) { throw new UnsupportedOperationException("Use setExcludedMimeTypes instead"); @@ -548,6 +550,14 @@ public class GzipHandler extends HandlerWrapper } } + // Exclude on User Agent + String ua = request.getHeader("User-Agent"); + if(ua != null && !_agentPatterns.matches(ua)) + { + LOG.debug("{} excluded by user-agent {}",this,request); + return false; + } + return true; } -- cgit v1.2.3 From e8b3b68e90155e25f0fe7a989e803a93caeafd12 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Mon, 14 Dec 2015 15:03:31 -0500 Subject: [447816] - ServletHolder#compareTo not transitive Updating compareTo to properly order when one _className is null and the other is not. Signed-off-by: Matt Gilman --- .../org/eclipse/jetty/servlet/ServletHolder.java | 18 ++++++- .../eclipse/jetty/servlet/ServletHolderTest.java | 55 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index b9519021a0..36ab05fa6d 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -207,15 +207,29 @@ public class ServletHolder extends Holder implements UserIdentity.Scope { if (sh==this) return 0; + if (sh._initOrder<_initOrder) return 1; + if (sh._initOrder>_initOrder) return -1; - int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0; + // consider _className, need to position properly when one is configured but not the other + int c; + if (_className==null && sh._className==null) + c=0; + else if (_className==null) + c=-1; + else if (sh._className==null) + c=1; + else + c=_className.compareTo(sh._className); + + // if _initOrder and _className are the same, consider the _name if (c==0) c=_name.compareTo(sh._name); - return c; + + return c; } /* ------------------------------------------------------------ */ diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java new file mode 100644 index 0000000000..7ec8c17277 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java @@ -0,0 +1,55 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.servlet; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class ServletHolderTest { + + @Test + public void testTransitiveCompareTo() throws Exception + { + // example of jsp-file referenced in web.xml + final ServletHolder one = new ServletHolder(); + one.setInitOrder(-1); + one.setName("Login"); + one.setClassName(null); + + // example of pre-compiled jsp + final ServletHolder two = new ServletHolder(); + two.setInitOrder(-1); + two.setName("org.my.package.jsp.WEB_002dINF.pages.precompiled_002dpage_jsp"); + two.setClassName("org.my.package.jsp.WEB_002dINF.pages.precompiled_002dpage_jsp"); + + // example of servlet referenced in web.xml + final ServletHolder three = new ServletHolder(); + three.setInitOrder(-1); + three.setName("Download"); + three.setClassName("org.my.package.web.DownloadServlet"); + + // verify compareTo transitivity + Assert.assertTrue(one.compareTo(two) < 0); + Assert.assertTrue(two.compareTo(three) < 0); + Assert.assertTrue(one.compareTo(three) < 0); + } +} -- cgit v1.2.3 From a52a182369e397e26d0739228338f9af5cddd792 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 14 Dec 2015 11:17:31 -0700 Subject: 484349 - Promote WebSocket PathMappings / PathSpec to Jetty Http + Moving PathMappings from jetty-websocket to jetty-http + Renaming WebSocketPathSpec to UriTemplatePathSpec + Improving implementation with knowledge gained from PathMap and PathMapTest cases. --- .../eclipse/jetty/http/pathmap/MappedResource.java | 101 ++++++ .../eclipse/jetty/http/pathmap/PathMappings.java | 145 +++++++++ .../org/eclipse/jetty/http/pathmap/PathSpec.java | 167 ++++++++++ .../eclipse/jetty/http/pathmap/PathSpecGroup.java | 101 ++++++ .../eclipse/jetty/http/pathmap/RegexPathSpec.java | 176 +++++++++++ .../jetty/http/pathmap/ServletPathSpec.java | 261 ++++++++++++++++ .../jetty/http/pathmap/UriTemplatePathSpec.java | 341 +++++++++++++++++++++ .../jetty/http/pathmap/PathMappingsTest.java | 320 +++++++++++++++++++ .../eclipse/jetty/http/pathmap/PathSpecAssert.java | 37 +++ .../jetty/http/pathmap/RegexPathSpecTest.java | 135 ++++++++ .../http/pathmap/ServletPathSpecMatchListTest.java | 101 ++++++ .../http/pathmap/ServletPathSpecOrderTest.java | 103 +++++++ .../jetty/http/pathmap/ServletPathSpecTest.java | 188 ++++++++++++ .../pathmap/UriTemplatePathSpecBadSpecsTest.java | 87 ++++++ .../http/pathmap/UriTemplatePathSpecTest.java | 284 +++++++++++++++++ 15 files changed, 2547 insertions(+) create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java create mode 100644 jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java new file mode 100644 index 0000000000..b44aff11d5 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; + +@ManagedObject("Mapped Resource") +public class MappedResource implements Comparable> +{ + private final PathSpec pathSpec; + private final E resource; + + public MappedResource(PathSpec pathSpec, E resource) + { + this.pathSpec = pathSpec; + this.resource = resource; + } + + /** + * Comparison is based solely on the pathSpec + */ + @Override + public int compareTo(MappedResource other) + { + return this.pathSpec.compareTo(other.pathSpec); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + MappedResource other = (MappedResource)obj; + if (pathSpec == null) + { + if (other.pathSpec != null) + { + return false; + } + } + else if (!pathSpec.equals(other.pathSpec)) + { + return false; + } + return true; + } + + @ManagedAttribute(value = "path spec", readonly = true) + public PathSpec getPathSpec() + { + return pathSpec; + } + + @ManagedAttribute(value = "resource", readonly = true) + public E getResource() + { + return resource; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + ((pathSpec == null) ? 0 : pathSpec.hashCode()); + return result; + } + + @Override + public String toString() + { + return String.format("MappedResource[pathSpec=%s,resource=%s]",pathSpec,resource); + } +} \ No newline at end of file diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java new file mode 100644 index 0000000000..e0d975ca7d --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java @@ -0,0 +1,145 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Path Mappings of PathSpec to Resource. + *

+ * Sorted into search order upon entry into the Set + * + * @param the type of mapping endpoint + */ +@ManagedObject("Path Mappings") +public class PathMappings implements Iterable>, Dumpable +{ + private static final Logger LOG = Log.getLogger(PathMappings.class); + private List> mappings = new ArrayList>(); + private MappedResource defaultResource = null; + + @Override + public String dump() + { + return ContainerLifeCycle.dump(this); + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + ContainerLifeCycle.dump(out,indent,mappings); + } + + @ManagedAttribute(value = "mappings", readonly = true) + public List> getMappings() + { + return mappings; + } + + public void reset() + { + mappings.clear(); + } + + /** + * Return a list of MappedResource matches for the specified path. + * + * @param path the path to return matches on + * @return the list of mapped resource the path matches on + */ + public List> getMatches(String path) + { + boolean matchRoot = "/".equals(path); + + List> ret = new ArrayList<>(); + int len = mappings.size(); + for (int i = 0; i < len; i++) + { + MappedResource mr = mappings.get(i); + + switch (mr.getPathSpec().group) + { + case ROOT: + if (matchRoot) + ret.add(mr); + break; + case DEFAULT: + if (matchRoot || mr.getPathSpec().matches(path)) + ret.add(mr); + break; + default: + if (mr.getPathSpec().matches(path)) + ret.add(mr); + break; + } + } + return ret; + } + + public MappedResource getMatch(String path) + { + int len = mappings.size(); + for (int i = 0; i < len; i++) + { + MappedResource mr = mappings.get(i); + if (mr.getPathSpec().matches(path)) + { + return mr; + } + } + return defaultResource; + } + + @Override + public Iterator> iterator() + { + return mappings.iterator(); + } + + public void put(PathSpec pathSpec, E resource) + { + MappedResource entry = new MappedResource<>(pathSpec,resource); + if (pathSpec.group == PathSpecGroup.DEFAULT) + { + defaultResource = entry; + } + // TODO: warning on replacement of existing mapping? + mappings.add(entry); + if (LOG.isDebugEnabled()) + LOG.debug("Added {} to {}",entry,this); + Collections.sort(mappings); + } + + @Override + public String toString() + { + return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size()); + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java new file mode 100644 index 0000000000..122211a558 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java @@ -0,0 +1,167 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +/** + * The base PathSpec, what all other path specs are based on + */ +public abstract class PathSpec implements Comparable +{ + protected String pathSpec; + protected PathSpecGroup group; + protected int pathDepth; + protected int specLength; + + @Override + public int compareTo(PathSpec other) + { + // Grouping (increasing) + int diff = this.group.ordinal() - other.group.ordinal(); + if (diff != 0) + { + return diff; + } + + // Spec Length (decreasing) + diff = other.specLength - this.specLength; + if (diff != 0) + { + return diff; + } + + // Path Spec Name (alphabetical) + return this.pathSpec.compareTo(other.pathSpec); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + PathSpec other = (PathSpec)obj; + if (pathSpec == null) + { + if (other.pathSpec != null) + { + return false; + } + } + else if (!pathSpec.equals(other.pathSpec)) + { + return false; + } + return true; + } + + public PathSpecGroup getGroup() + { + return group; + } + + /** + * Get the number of path elements that this path spec declares. + *

+ * This is used to determine longest match logic. + * + * @return the depth of the path segments that this spec declares + */ + public int getPathDepth() + { + return pathDepth; + } + + /** + * Return the portion of the path that is after the path spec. + * + * @param path + * the path to match against + * @return the path info portion of the string + */ + public abstract String getPathInfo(String path); + + /** + * Return the portion of the path that matches a path spec. + * + * @param path + * the path to match against + * @return the match, or null if no match at all + */ + public abstract String getPathMatch(String path); + + /** + * The as-provided path spec. + * + * @return the as-provided path spec + */ + public String getPathSpec() + { + return pathSpec; + } + + /** + * Get the relative path. + * + * @param base + * the base the path is relative to + * @param path + * the additional path + * @return the base plus path with pathSpec portion removed + */ + public abstract String getRelativePath(String base, String path); + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode()); + return result; + } + + /** + * Test to see if the provided path matches this path spec + * + * @param path + * the path to test + * @return true if the path matches this path spec, false otherwise + */ + public abstract boolean matches(String path); + + @Override + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append(this.getClass().getSimpleName()).append("[\""); + str.append(pathSpec); + str.append("\",pathDepth=").append(pathDepth); + str.append(",group=").append(group); + str.append("]"); + return str.toString(); + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java new file mode 100644 index 0000000000..3a191a94e8 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +/** + * Types of path spec groups. + *

+ * This is used to facilitate proper pathspec search order. + *

+ * Search Order: + *

    + *
  1. {@link PathSpecGroup#ordinal()} [increasing]
  2. + *
  3. {@link PathSpec#specLength} [decreasing]
  4. + *
  5. {@link PathSpec#pathSpec} [natural sort order]
  6. + *
+ */ +public enum PathSpecGroup +{ + // NOTE: Order of enums determines order of Groups. + + /** + * For exactly defined path specs, no glob. + */ + EXACT, + /** + * For path specs that have a hardcoded prefix and suffix with wildcard glob in the middle. + * + *
+     *   "^/downloads/[^/]*.zip$"  - regex spec
+     *   "/a/{var}/c"              - uri-template spec
+     * 
+ * + * Note: there is no known servlet spec variant of this kind of path spec + */ + MIDDLE_GLOB, + /** + * For path specs that have a hardcoded prefix and a trailing wildcard glob. + *

+ * + *

+     *   "/downloads/*"          - servlet spec
+     *   "/api/*"                - servlet spec
+     *   "^/rest/.*$"            - regex spec
+     *   "/bookings/{guest-id}"  - uri-template spec
+     *   "/rewards/{vip-level}"  - uri-template spec
+     * 
+ */ + PREFIX_GLOB, + /** + * For path specs that have a wildcard glob with a hardcoded suffix + * + *
+     *   "*.do"        - servlet spec
+     *   "*.css"       - servlet spec
+     *   "^.*\.zip$"   - regex spec
+     * 
+ * + * Note: there is no known uri-template spec variant of this kind of path spec + */ + SUFFIX_GLOB, + /** + * The root spec for accessing the Root behavior. + * + *
+     *   ""           - servlet spec       (Root Servlet)
+     *   null         - servlet spec       (Root Servlet)
+     * 
+ * + * Note: there is no known uri-template spec variant of this kind of path spec + */ + ROOT, + /** + * The default spec for accessing the Default path behavior. + * + *
+     *   "/"           - servlet spec      (Default Servlet)
+     *   "/"           - uri-template spec (Root Context)
+     *   "^/$"         - regex spec        (Root Context)
+     * 
+ * + * Per Servlet Spec, pathInfo is always null for these specs. + * If nothing above matches, then default will match. + */ + DEFAULT, +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java new file mode 100644 index 0000000000..4f9a0a23ab --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java @@ -0,0 +1,176 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexPathSpec extends PathSpec +{ + protected Pattern pattern; + + protected RegexPathSpec() + { + super(); + } + + public RegexPathSpec(String regex) + { + super.pathSpec = regex; + boolean inGrouping = false; + this.pathDepth = 0; + this.specLength = pathSpec.length(); + // build up a simple signature we can use to identify the grouping + StringBuilder signature = new StringBuilder(); + for (char c : pathSpec.toCharArray()) + { + switch (c) + { + case '[': + inGrouping = true; + break; + case ']': + inGrouping = false; + signature.append('g'); // glob + break; + case '*': + signature.append('g'); // glob + break; + case '/': + if (!inGrouping) + { + this.pathDepth++; + } + break; + default: + if (!inGrouping) + { + if (Character.isLetterOrDigit(c)) + { + signature.append('l'); // literal (exact) + } + } + break; + } + } + this.pattern = Pattern.compile(pathSpec); + + // Figure out the grouping based on the signature + String sig = signature.toString(); + + if (Pattern.matches("^l*$",sig)) + { + this.group = PathSpecGroup.EXACT; + } + else if (Pattern.matches("^l*g+",sig)) + { + this.group = PathSpecGroup.PREFIX_GLOB; + } + else if (Pattern.matches("^g+l+$",sig)) + { + this.group = PathSpecGroup.SUFFIX_GLOB; + } + else + { + this.group = PathSpecGroup.MIDDLE_GLOB; + } + } + + public Matcher getMatcher(String path) + { + return this.pattern.matcher(path); + } + + @Override + public String getPathInfo(String path) + { + // Path Info only valid for PREFIX_GLOB types + if (group == PathSpecGroup.PREFIX_GLOB) + { + Matcher matcher = getMatcher(path); + if (matcher.matches()) + { + if (matcher.groupCount() >= 1) + { + String pathInfo = matcher.group(1); + if ("".equals(pathInfo)) + { + return "/"; + } + else + { + return pathInfo; + } + } + } + } + return null; + } + + @Override + public String getPathMatch(String path) + { + Matcher matcher = getMatcher(path); + if (matcher.matches()) + { + if (matcher.groupCount() >= 1) + { + int idx = matcher.start(1); + if (idx > 0) + { + if (path.charAt(idx - 1) == '/') + { + idx--; + } + return path.substring(0,idx); + } + } + return path; + } + return null; + } + + public Pattern getPattern() + { + return this.pattern; + } + + @Override + public String getRelativePath(String base, String path) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean matches(final String path) + { + int idx = path.indexOf('?'); + if (idx >= 0) + { + // match only non-query part + return getMatcher(path.substring(0,idx)).matches(); + } + else + { + // match entire path + return getMatcher(path).matches(); + } + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java new file mode 100644 index 0000000000..55399748e2 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java @@ -0,0 +1,261 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import org.eclipse.jetty.util.URIUtil; + +public class ServletPathSpec extends PathSpec +{ + public ServletPathSpec(String servletPathSpec) + { + super(); + assertValidServletPathSpec(servletPathSpec); + + // The Root Path Spec + if ((servletPathSpec == null) || (servletPathSpec.length() == 0)) + { + super.pathSpec = ""; + super.pathDepth = -1; // force this to be at the end of the sort order + this.specLength = 1; + this.group = PathSpecGroup.ROOT; + return; + } + + // The Default Path Spec + if("/".equals(servletPathSpec)) + { + super.pathSpec = "/"; + super.pathDepth = -1; // force this to be at the end of the sort order + this.specLength = 1; + this.group = PathSpecGroup.DEFAULT; + return; + } + + this.specLength = servletPathSpec.length(); + super.pathDepth = 0; + char lastChar = servletPathSpec.charAt(specLength - 1); + // prefix based + if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*')) + { + this.group = PathSpecGroup.PREFIX_GLOB; + } + // suffix based + else if (servletPathSpec.charAt(0) == '*') + { + this.group = PathSpecGroup.SUFFIX_GLOB; + } + else + { + this.group = PathSpecGroup.EXACT; + } + + for (int i = 0; i < specLength; i++) + { + int cp = servletPathSpec.codePointAt(i); + if (cp < 128) + { + char c = (char)cp; + switch (c) + { + case '/': + super.pathDepth++; + break; + } + } + } + + super.pathSpec = servletPathSpec; + } + + private void assertValidServletPathSpec(String servletPathSpec) + { + if ((servletPathSpec == null) || servletPathSpec.equals("")) + { + return; // empty path spec + } + + int len = servletPathSpec.length(); + // path spec must either start with '/' or '*.' + if (servletPathSpec.charAt(0) == '/') + { + // Prefix Based + if (len == 1) + { + return; // simple '/' path spec + } + int idx = servletPathSpec.indexOf('*'); + if (idx < 0) + { + return; // no hit on glob '*' + } + // only allowed to have '*' at the end of the path spec + if (idx != (len - 1)) + { + throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches: bad spec \""+ servletPathSpec +"\""); + } + } + else if (servletPathSpec.startsWith("*.")) + { + // Suffix Based + int idx = servletPathSpec.indexOf('/'); + // cannot have path separator + if (idx >= 0) + { + throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators: bad spec \""+ servletPathSpec +"\""); + } + + idx = servletPathSpec.indexOf('*',2); + // only allowed to have 1 glob '*', at the start of the path spec + if (idx >= 1) + { + throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*': bad spec \""+ servletPathSpec +"\""); + } + } + else + { + throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\": bad spec \""+ servletPathSpec +"\""); + } + } + + @Override + public String getPathInfo(String path) + { + // Path Info only valid for PREFIX_GLOB types + if (group == PathSpecGroup.PREFIX_GLOB) + { + if (path.length() == (specLength - 2)) + { + return null; + } + return path.substring(specLength - 2); + } + + return null; + } + + @Override + public String getPathMatch(String path) + { + switch (group) + { + case EXACT: + if (pathSpec.equals(path)) + { + return path; + } + else + { + return null; + } + case PREFIX_GLOB: + if (isWildcardMatch(path)) + { + return path.substring(0,specLength - 2); + } + else + { + return null; + } + case SUFFIX_GLOB: + if (path.regionMatches(path.length() - (specLength - 1),pathSpec,1,specLength - 1)) + { + return path; + } + else + { + return null; + } + case DEFAULT: + return path; + default: + return null; + } + } + + @Override + public String getRelativePath(String base, String path) + { + String info = getPathInfo(path); + if (info == null) + { + info = path; + } + + if (info.startsWith("./")) + { + info = info.substring(2); + } + if (base.endsWith(URIUtil.SLASH)) + { + if (info.startsWith(URIUtil.SLASH)) + { + path = base + info.substring(1); + } + else + { + path = base + info; + } + } + else if (info.startsWith(URIUtil.SLASH)) + { + path = base + info; + } + else + { + path = base + URIUtil.SLASH + info; + } + return path; + } + + private boolean isWildcardMatch(String path) + { + // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar" + int cpl = specLength - 2; + if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0,pathSpec,0,cpl))) + { + if ((path.length() == cpl) || ('/' == path.charAt(cpl))) + { + return true; + } + } + return false; + } + + @Override + public boolean matches(String path) + { + switch (group) + { + case EXACT: + return pathSpec.equals(path); + case PREFIX_GLOB: + return (!"/".equals(path) && isWildcardMatch(path)); + case SUFFIX_GLOB: + return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1); + case ROOT: + // Only "/" matches + return ("/".equals(path)); + case DEFAULT: + // If we reached this point, then everything matches + return true; + default: + return false; + } + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java new file mode 100644 index 0000000000..fe25230b77 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java @@ -0,0 +1,341 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * PathSpec for URI Template based declarations + * + * @see URI Templates (Level 1) + */ +public class UriTemplatePathSpec extends RegexPathSpec +{ + private static final Logger LOG = Log.getLogger(UriTemplatePathSpec.class); + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}"); + /** Reserved Symbols in URI Template variable */ + private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims + "!$&'()*+,;="; // sub-delims + /** Allowed Symbols in a URI Template variable */ + private static final String VARIABLE_SYMBOLS="-._"; + private static final Set FORBIDDEN_SEGMENTS; + + static + { + FORBIDDEN_SEGMENTS = new HashSet<>(); + FORBIDDEN_SEGMENTS.add("/./"); + FORBIDDEN_SEGMENTS.add("/../"); + FORBIDDEN_SEGMENTS.add("//"); + } + + private String variables[]; + + public UriTemplatePathSpec(String rawSpec) + { + super(); + Objects.requireNonNull(rawSpec,"Path Param Spec cannot be null"); + + if ("".equals(rawSpec) || "/".equals(rawSpec)) + { + super.pathSpec = "/"; + super.pattern = Pattern.compile("^/$"); + super.pathDepth = 1; + this.specLength = 1; + this.variables = new String[0]; + this.group = PathSpecGroup.EXACT; + return; + } + + if (rawSpec.charAt(0) != '/') + { + // path specs must start with '/' + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: path spec \""); + err.append(rawSpec); + err.append("\" must start with '/'"); + throw new IllegalArgumentException(err.toString()); + } + + for (String forbidden : FORBIDDEN_SEGMENTS) + { + if (rawSpec.contains(forbidden)) + { + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: segment "); + err.append(forbidden); + err.append(" is forbidden in path spec: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + } + + this.pathSpec = rawSpec; + + StringBuilder regex = new StringBuilder(); + regex.append('^'); + + List varNames = new ArrayList<>(); + // split up into path segments (ignoring the first slash that will always be empty) + String segments[] = rawSpec.substring(1).split("/"); + char segmentSignature[] = new char[segments.length]; + this.pathDepth = segments.length; + for (int i = 0; i < segments.length; i++) + { + String segment = segments[i]; + Matcher mat = VARIABLE_PATTERN.matcher(segment); + + if (mat.matches()) + { + // entire path segment is a variable. + String variable = mat.group(1); + if (varNames.contains(variable)) + { + // duplicate variable names + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: variable "); + err.append(variable); + err.append(" is duplicated in path spec: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + + assertIsValidVariableLiteral(variable); + + segmentSignature[i] = 'v'; // variable + // valid variable name + varNames.add(variable); + // build regex + regex.append("/([^/]+)"); + } + else if (mat.find(0)) + { + // variable exists as partial segment + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: variable "); + err.append(mat.group()); + err.append(" must exist as entire path segment: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0)) + { + // variable is split with a path separator + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: invalid path segment /"); + err.append(segment); + err.append("/ variable declaration incomplete: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + else if (segment.indexOf('*') >= 0) + { + // glob segment + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: path segment /"); + err.append(segment); + err.append("/ contains a wildcard symbol (not supported by this uri-template implementation): "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + else + { + // valid path segment + segmentSignature[i] = 'e'; // exact + // build regex + regex.append('/'); + // escape regex special characters + for (char c : segment.toCharArray()) + { + if ((c == '.') || (c == '[') || (c == ']') || (c == '\\')) + { + regex.append('\\'); + } + regex.append(c); + } + } + } + + // Handle trailing slash (which is not picked up during split) + if(rawSpec.charAt(rawSpec.length()-1) == '/') + { + regex.append('/'); + } + + regex.append('$'); + + this.pattern = Pattern.compile(regex.toString()); + + int varcount = varNames.size(); + this.variables = varNames.toArray(new String[varcount]); + + // Convert signature to group + String sig = String.valueOf(segmentSignature); + + if (Pattern.matches("^e*$",sig)) + { + this.group = PathSpecGroup.EXACT; + } + else if (Pattern.matches("^e*v+",sig)) + { + this.group = PathSpecGroup.PREFIX_GLOB; + } + else if (Pattern.matches("^v+e+",sig)) + { + this.group = PathSpecGroup.SUFFIX_GLOB; + } + else + { + this.group = PathSpecGroup.MIDDLE_GLOB; + } + } + + /** + * Validate variable literal name, per RFC6570, Section 2.1 Literals + * @param variable + * @param pathParamSpec + */ + private void assertIsValidVariableLiteral(String variable) + { + int len = variable.length(); + + int i = 0; + int codepoint; + boolean valid = (len > 0); // must not be zero length + + while (valid && i < len) + { + codepoint = variable.codePointAt(i); + i += Character.charCount(codepoint); + + // basic letters, digits, or symbols + if (isValidBasicLiteralCodepoint(codepoint)) + { + continue; + } + + // The ucschar and iprivate pieces + if (Character.isSupplementaryCodePoint(codepoint)) + { + continue; + } + + // pct-encoded + if (codepoint == '%') + { + if (i + 2 > len) + { + // invalid percent encoding, missing extra 2 chars + valid = false; + continue; + } + codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4; + codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++)); + + // validate basic literal + if (isValidBasicLiteralCodepoint(codepoint)) + { + continue; + } + } + + valid = false; + } + + if (!valid) + { + // invalid variable name + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: variable {"); + err.append(variable); + err.append("} an invalid variable name: "); + err.append(pathSpec); + throw new IllegalArgumentException(err.toString()); + } + } + + private boolean isValidBasicLiteralCodepoint(int codepoint) + { + // basic letters or digits + if((codepoint >= 'a' && codepoint <= 'z') || + (codepoint >= 'A' && codepoint <= 'Z') || + (codepoint >= '0' && codepoint <= '9')) + { + return true; + } + + // basic allowed symbols + if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0) + { + return true; // valid simple value + } + + // basic reserved symbols + if(VARIABLE_RESERVED.indexOf(codepoint) >= 0) + { + LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec); + return false; // valid simple value + } + + return false; + } + + public Map getPathParams(String path) + { + Matcher matcher = getMatcher(path); + if (matcher.matches()) + { + if (group == PathSpecGroup.EXACT) + { + return Collections.emptyMap(); + } + Map ret = new HashMap<>(); + int groupCount = matcher.groupCount(); + for (int i = 1; i <= groupCount; i++) + { + ret.put(this.variables[i - 1],matcher.group(i)); + } + return ret; + } + return null; + } + + public int getVariableCount() + { + return variables.length; + } + + public String[] getVariables() + { + return this.variables; + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java new file mode 100644 index 0000000000..06cd14bcce --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java @@ -0,0 +1,320 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Assert; +import org.junit.Test; + +public class PathMappingsTest +{ + private void assertMatch(PathMappings pathmap, String path, String expectedValue) + { + String msg = String.format(".getMatch(\"%s\")",path); + MappedResource match = pathmap.getMatch(path); + Assert.assertThat(msg,match,notNullValue()); + String actualMatch = match.getResource(); + Assert.assertEquals(msg,expectedValue,actualMatch); + } + + public void dumpMappings(PathMappings p) + { + for (MappedResource res : p) + { + System.out.printf(" %s%n",res); + } + } + + /** + * Test the match order rules with a mixed Servlet and regex path specs + *

+ *

    + *
  • Exact match
  • + *
  • Longest prefix match
  • + *
  • Longest suffix match
  • + *
+ */ + @Test + public void testMixedMatchOrder() + { + PathMappings p = new PathMappings<>(); + + p.put(new ServletPathSpec("/"),"default"); + p.put(new ServletPathSpec("/animal/bird/*"),"birds"); + p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); + p.put(new ServletPathSpec("/animal/*"),"animals"); + p.put(new RegexPathSpec("^/animal/.*/chat$"),"animalChat"); + p.put(new RegexPathSpec("^/animal/.*/cam$"),"animalCam"); + p.put(new RegexPathSpec("^/entrance/cam$"),"entranceCam"); + + // dumpMappings(p); + + assertMatch(p,"/animal/bird/eagle","birds"); + assertMatch(p,"/animal/fish/bass/sea","fishes"); + assertMatch(p,"/animal/peccary/javalina/evolution","animals"); + assertMatch(p,"/","default"); + assertMatch(p,"/animal/bird/eagle/chat","animalChat"); + assertMatch(p,"/animal/bird/penguin/chat","animalChat"); + assertMatch(p,"/animal/fish/trout/cam","animalCam"); + assertMatch(p,"/entrance/cam","entranceCam"); + } + + /** + * Test the match order rules imposed by the Servlet API. + *

+ *

    + *
  • Exact match
  • + *
  • Longest prefix match
  • + *
  • Longest suffix match
  • + *
  • default
  • + *
+ */ + @Test + public void testServletMatchOrder() + { + PathMappings p = new PathMappings<>(); + + p.put(new ServletPathSpec("/abs/path"),"abspath"); // 1 + p.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2 + p.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3 + p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4 + p.put(new ServletPathSpec("/animal/*"),"animals"); // 5 + p.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6 + p.put(new ServletPathSpec("*.gz"),"gzipped"); // 7 + p.put(new ServletPathSpec("/"),"default"); // 8 + // 9 was the old Jetty ":" spec delimited case (no longer valid) + p.put(new ServletPathSpec(""),"root"); // 10 + p.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11 + + // dumpMappings(p); + + // From old PathMapTest + assertMatch(p,"/abs/path","abspath"); + assertMatch(p,"/abs/path/xxx","default"); + assertMatch(p,"/abs/pith","default"); + assertMatch(p,"/abs/path/longer","longpath"); + assertMatch(p,"/abs/path/","default"); + assertMatch(p,"/abs/path/foo","default"); + assertMatch(p,"/animal/bird/eagle/bald","birds"); + assertMatch(p,"/animal/fish/shark/hammerhead","fishes"); + assertMatch(p,"/animal/insect/ladybug","animals"); + assertMatch(p,"/animal","animals"); + assertMatch(p,"/animal/","animals"); + assertMatch(p,"/animal/other","animals"); + assertMatch(p,"/animal/*","animals"); + assertMatch(p,"/downloads/distribution.tar.gz","tarball"); + assertMatch(p,"/downloads/script.gz","gzipped"); + assertMatch(p,"/animal/arhive.gz","animals"); + assertMatch(p,"/Other/path","default"); + assertMatch(p,"/\u20ACuro/path","money"); + assertMatch(p,"/","root"); + + // Extra tests + assertMatch(p,"/downloads/readme.txt","default"); + assertMatch(p,"/downloads/logs.tgz","default"); + assertMatch(p,"/main.css","default"); + } + + /** + * Test the match order rules with a mixed Servlet and URI Template path specs + *

+ *

    + *
  • Exact match
  • + *
  • Longest prefix match
  • + *
  • Longest suffix match
  • + *
+ */ + @Test + public void testMixedMatchUriOrder() + { + PathMappings p = new PathMappings<>(); + + p.put(new ServletPathSpec("/"),"default"); + p.put(new ServletPathSpec("/animal/bird/*"),"birds"); + p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); + p.put(new ServletPathSpec("/animal/*"),"animals"); + p.put(new UriTemplatePathSpec("/animal/{type}/{name}/chat"),"animalChat"); + p.put(new UriTemplatePathSpec("/animal/{type}/{name}/cam"),"animalCam"); + p.put(new UriTemplatePathSpec("/entrance/cam"),"entranceCam"); + + // dumpMappings(p); + + assertMatch(p,"/animal/bird/eagle","birds"); + assertMatch(p,"/animal/fish/bass/sea","fishes"); + assertMatch(p,"/animal/peccary/javalina/evolution","animals"); + assertMatch(p,"/","default"); + assertMatch(p,"/animal/bird/eagle/chat","animalChat"); + assertMatch(p,"/animal/bird/penguin/chat","animalChat"); + assertMatch(p,"/animal/fish/trout/cam","animalCam"); + assertMatch(p,"/entrance/cam","entranceCam"); + } + + /** + * Test the match order rules for URI Template based specs + *

+ *

    + *
  • Exact match
  • + *
  • Longest prefix match
  • + *
  • Longest suffix match
  • + *
+ */ + @Test + public void testUriTemplateMatchOrder() + { + PathMappings p = new PathMappings<>(); + + p.put(new UriTemplatePathSpec("/a/{var}/c"),"endpointA"); + p.put(new UriTemplatePathSpec("/a/b/c"),"endpointB"); + p.put(new UriTemplatePathSpec("/a/{var1}/{var2}"),"endpointC"); + p.put(new UriTemplatePathSpec("/{var1}/d"),"endpointD"); + p.put(new UriTemplatePathSpec("/b/{var2}"),"endpointE"); + + // dumpMappings(p); + + assertMatch(p,"/a/b/c","endpointB"); + assertMatch(p,"/a/d/c","endpointA"); + assertMatch(p,"/a/x/y","endpointC"); + + assertMatch(p,"/b/d","endpointE"); + } + + @Test + public void testPathMap() throws Exception + { + PathMappings p = new PathMappings<>(); + + p.put(new ServletPathSpec("/abs/path"), "1"); + p.put(new ServletPathSpec("/abs/path/longer"), "2"); + p.put(new ServletPathSpec("/animal/bird/*"), "3"); + p.put(new ServletPathSpec("/animal/fish/*"), "4"); + p.put(new ServletPathSpec("/animal/*"), "5"); + p.put(new ServletPathSpec("*.tar.gz"), "6"); + p.put(new ServletPathSpec("*.gz"), "7"); + p.put(new ServletPathSpec("/"), "8"); + // p.put(new ServletPathSpec("/XXX:/YYY"), "9"); // special syntax from Jetty 3.1.x + p.put(new ServletPathSpec(""), "10"); + p.put(new ServletPathSpec("/\u20ACuro/*"), "11"); + + assertEquals("pathMatch exact", "/Foo/bar", new ServletPathSpec("/Foo/bar").getPathMatch("/Foo/bar")); + assertEquals("pathMatch prefix", "/Foo", new ServletPathSpec("/Foo/*").getPathMatch("/Foo/bar")); + assertEquals("pathMatch prefix", "/Foo", new ServletPathSpec("/Foo/*").getPathMatch("/Foo/")); + assertEquals("pathMatch prefix", "/Foo", new ServletPathSpec("/Foo/*").getPathMatch("/Foo")); + assertEquals("pathMatch suffix", "/Foo/bar.ext", new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext")); + assertEquals("pathMatch default", "/Foo/bar.ext", new ServletPathSpec("/").getPathMatch("/Foo/bar.ext")); + + assertEquals("pathInfo exact", null, new ServletPathSpec("/Foo/bar").getPathInfo("/Foo/bar")); + assertEquals("pathInfo prefix", "/bar", new ServletPathSpec("/Foo/*").getPathInfo("/Foo/bar")); + assertEquals("pathInfo prefix", "/*", new ServletPathSpec("/Foo/*").getPathInfo("/Foo/*")); + assertEquals("pathInfo prefix", "/", new ServletPathSpec("/Foo/*").getPathInfo("/Foo/")); + assertEquals("pathInfo prefix", null, new ServletPathSpec("/Foo/*").getPathInfo("/Foo")); + assertEquals("pathInfo suffix", null, new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext")); + assertEquals("pathInfo default", null, new ServletPathSpec("/").getPathInfo("/Foo/bar.ext")); + + p.put(new ServletPathSpec("/*"), "0"); + + // assertEquals("Get absolute path", "1", p.get("/abs/path")); + assertEquals("Match absolute path", "/abs/path", p.getMatch("/abs/path").getPathSpec().pathSpec); + assertEquals("Match absolute path", "1", p.getMatch("/abs/path").getResource()); + assertEquals("Mismatch absolute path", "0", p.getMatch("/abs/path/xxx").getResource()); + assertEquals("Mismatch absolute path", "0", p.getMatch("/abs/pith").getResource()); + assertEquals("Match longer absolute path", "2", p.getMatch("/abs/path/longer").getResource()); + assertEquals("Not exact absolute path", "0", p.getMatch("/abs/path/").getResource()); + assertEquals("Not exact absolute path", "0", p.getMatch("/abs/path/xxx").getResource()); + + assertEquals("Match longest prefix", "3", p.getMatch("/animal/bird/eagle/bald").getResource()); + assertEquals("Match longest prefix", "4", p.getMatch("/animal/fish/shark/grey").getResource()); + assertEquals("Match longest prefix", "5", p.getMatch("/animal/insect/bug").getResource()); + assertEquals("mismatch exact prefix", "5", p.getMatch("/animal").getResource()); + assertEquals("mismatch exact prefix", "5", p.getMatch("/animal/").getResource()); + + assertEquals("Match longest suffix", "0", p.getMatch("/suffix/path.tar.gz").getResource()); + assertEquals("Match longest suffix", "0", p.getMatch("/suffix/path.gz").getResource()); + assertEquals("prefix rather than suffix", "5", p.getMatch("/animal/path.gz").getResource()); + + assertEquals("default", "0", p.getMatch("/Other/path").getResource()); + + assertEquals("pathMatch /*", "", new ServletPathSpec("/*").getPathMatch("/xxx/zzz")); + assertEquals("pathInfo /*", "/xxx/zzz", new ServletPathSpec("/*").getPathInfo("/xxx/zzz")); + + assertTrue("match /", new ServletPathSpec("/").matches("/anything")); + assertTrue("match /*", new ServletPathSpec("/*").matches("/anything")); + assertTrue("match /foo", new ServletPathSpec("/foo").matches("/foo")); + assertTrue("!match /foo", !new ServletPathSpec("/foo").matches("/bar")); + assertTrue("match /foo/*", new ServletPathSpec("/foo/*").matches("/foo")); + assertTrue("match /foo/*", new ServletPathSpec("/foo/*").matches("/foo/")); + assertTrue("match /foo/*", new ServletPathSpec("/foo/*").matches("/foo/anything")); + assertTrue("!match /foo/*", !new ServletPathSpec("/foo/*").matches("/bar")); + assertTrue("!match /foo/*", !new ServletPathSpec("/foo/*").matches("/bar/")); + assertTrue("!match /foo/*", !new ServletPathSpec("/foo/*").matches("/bar/anything")); + assertTrue("match *.foo", new ServletPathSpec("*.foo").matches("anything.foo")); + assertTrue("!match *.foo", !new ServletPathSpec("*.foo").matches("anything.bar")); + + assertEquals("match / with ''", "10", p.getMatch("/").getResource()); + + assertTrue("match \"\"", new ServletPathSpec("").matches("/")); + } + + /** + * See JIRA issue: JETTY-88. + * @throws Exception failed test + */ + @Test + public void testPathMappingsOnlyMatchOnDirectoryNames() throws Exception + { + ServletPathSpec spec = new ServletPathSpec("/xyz/*"); + + PathSpecAssert.assertMatch(spec, "/xyz"); + PathSpecAssert.assertMatch(spec, "/xyz/"); + PathSpecAssert.assertMatch(spec, "/xyz/123"); + PathSpecAssert.assertMatch(spec, "/xyz/123/"); + PathSpecAssert.assertMatch(spec, "/xyz/123.txt"); + PathSpecAssert.assertNotMatch(spec, "/xyz123"); + PathSpecAssert.assertNotMatch(spec, "/xyz123;jessionid=99"); + PathSpecAssert.assertNotMatch(spec, "/xyz123/"); + PathSpecAssert.assertNotMatch(spec, "/xyz123/456"); + PathSpecAssert.assertNotMatch(spec, "/xyz.123"); + PathSpecAssert.assertNotMatch(spec, "/xyz;123"); // as if the ; was encoded and part of the path + PathSpecAssert.assertNotMatch(spec, "/xyz?123"); // as if the ? was encoded and part of the path + } + + @Test + public void testPrecidenceVsOrdering() throws Exception + { + PathMappings p = new PathMappings<>(); + p.put(new ServletPathSpec("/dump/gzip/*"),"prefix"); + p.put(new ServletPathSpec("*.txt"),"suffix"); + + assertEquals(null,p.getMatch("/foo/bar")); + assertEquals("prefix",p.getMatch("/dump/gzip/something").getResource()); + assertEquals("suffix",p.getMatch("/foo/something.txt").getResource()); + assertEquals("prefix",p.getMatch("/dump/gzip/something.txt").getResource()); + + p = new PathMappings<>(); + p.put(new ServletPathSpec("*.txt"),"suffix"); + p.put(new ServletPathSpec("/dump/gzip/*"),"prefix"); + + assertEquals(null,p.getMatch("/foo/bar")); + assertEquals("prefix",p.getMatch("/dump/gzip/something").getResource()); + assertEquals("suffix",p.getMatch("/foo/something.txt").getResource()); + assertEquals("prefix",p.getMatch("/dump/gzip/something.txt").getResource()); + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java new file mode 100644 index 0000000000..57546e74bb --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java @@ -0,0 +1,37 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class PathSpecAssert +{ + public static void assertMatch(PathSpec spec, String path) + { + boolean match = spec.matches(path); + assertThat(spec.getClass().getSimpleName() + " '" + spec + "' should match path '" + path + "'", match, is(true)); + } + + public static void assertNotMatch(PathSpec spec, String path) + { + boolean match = spec.matches(path); + assertThat(spec.getClass().getSimpleName() + " '" + spec + "' should not match path '" + path + "'", match, is(false)); + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java new file mode 100644 index 0000000000..a44c4dca50 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java @@ -0,0 +1,135 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class RegexPathSpecTest +{ + public static void assertMatches(PathSpec spec, String path) + { + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + assertThat(msg,spec.matches(path),is(true)); + } + + public static void assertNotMatches(PathSpec spec, String path) + { + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + assertThat(msg,spec.matches(path),is(false)); + } + + @Test + public void testExactSpec() + { + RegexPathSpec spec = new RegexPathSpec("^/a$"); + assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",1,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group); + + assertMatches(spec,"/a"); + + assertNotMatches(spec,"/aa"); + assertNotMatches(spec,"/a/"); + } + + @Test + public void testMiddleSpec() + { + RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$"); + assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec()); + assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",3,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); + + assertMatches(spec,"/rest/api/list"); + assertMatches(spec,"/rest/1.0/list"); + assertMatches(spec,"/rest/2.0/list"); + assertMatches(spec,"/rest/accounts/list"); + + assertNotMatches(spec,"/a"); + assertNotMatches(spec,"/aa"); + assertNotMatches(spec,"/aa/bb"); + assertNotMatches(spec,"/rest/admin/delete"); + assertNotMatches(spec,"/rest/list"); + } + + @Test + public void testMiddleSpecNoGrouping() + { + RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$"); + assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec()); + assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",3,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); + + assertMatches(spec,"/rest/api/list"); + assertMatches(spec,"/rest/1.0/list"); + assertMatches(spec,"/rest/2.0/list"); + assertMatches(spec,"/rest/accounts/list"); + + assertNotMatches(spec,"/a"); + assertNotMatches(spec,"/aa"); + assertNotMatches(spec,"/aa/bb"); + assertNotMatches(spec,"/rest/admin/delete"); + assertNotMatches(spec,"/rest/list"); + } + + @Test + public void testPrefixSpec() + { + RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$"); + assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",2,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group); + + assertMatches(spec,"/a/"); + assertMatches(spec,"/a/b"); + assertMatches(spec,"/a/b/c/d/e"); + + assertNotMatches(spec,"/a"); + assertNotMatches(spec,"/aa"); + assertNotMatches(spec,"/aa/bb"); + } + + @Test + public void testSuffixSpec() + { + RegexPathSpec spec = new RegexPathSpec("^(.*).do$"); + assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec()); + assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",0,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group); + + assertMatches(spec,"/a.do"); + assertMatches(spec,"/a/b/c.do"); + assertMatches(spec,"/abcde.do"); + assertMatches(spec,"/abc/efg.do"); + + assertNotMatches(spec,"/a"); + assertNotMatches(spec,"/aa"); + assertNotMatches(spec,"/aa/bb"); + assertNotMatches(spec,"/aa/bb.do/more"); + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java new file mode 100644 index 0000000000..b1295a7d9c --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.List; + +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; + +/** + * Tests of {@link PathMappings#getMatches(String)} + */ +@RunWith(Parameterized.class) +public class ServletPathSpecMatchListTest +{ + @Parameters(name="{0} = {1}") + public static List testCases() + { + String data[][] = new String[][] { + // From old PathMapTest + { "All matches", "/animal/bird/path.tar.gz", "[/animal/bird/*=birds, /animal/*=animals, *.tar.gz=tarball, *.gz=gzipped, /=default]"}, + { "Dir matches", "/animal/fish/", "[/animal/fish/*=fishes, /animal/*=animals, /=default]"}, + { "Dir matches", "/animal/fish", "[/animal/fish/*=fishes, /animal/*=animals, /=default]"}, + { "Root matches", "/", "[=root, /=default]"}, + { "Dir matches", "", "[/=default]"} + }; + + return Arrays.asList(data); + } + + private static PathMappings mappings; + + static { + mappings = new PathMappings<>(); + + // From old PathMapTest + mappings.put(new ServletPathSpec("/abs/path"),"abspath"); // 1 + mappings.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2 + mappings.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3 + mappings.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4 + mappings.put(new ServletPathSpec("/animal/*"),"animals"); // 5 + mappings.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6 + mappings.put(new ServletPathSpec("*.gz"),"gzipped"); // 7 + mappings.put(new ServletPathSpec("/"),"default"); // 8 + // 9 was the old Jetty ":" spec delimited case (no longer valid) + mappings.put(new ServletPathSpec(""),"root"); // 10 + mappings.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11 + } + + @Parameter(0) + public String message; + + @Parameter(1) + public String inputPath; + + @Parameter(2) + public String expectedListing; + + @Test + public void testGetMatches() + { + List> matches = mappings.getMatches(inputPath); + + StringBuilder actual = new StringBuilder(); + actual.append('['); + boolean delim = false; + for (MappedResource res : matches) + { + if (delim) + actual.append(", "); + actual.append(res.getPathSpec().pathSpec).append('=').append(res.getResource()); + delim = true; + } + actual.append(']'); + + assertThat(message + " on [" + inputPath + "]",actual.toString(),is(expectedListing)); + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java new file mode 100644 index 0000000000..ea5dddb3f8 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java @@ -0,0 +1,103 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.List; + +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; + +/** + * Tests of {@link PathMappings#getMatch(String)}, with a focus on correct mapping selection order + */ +@RunWith(Parameterized.class) +public class ServletPathSpecOrderTest +{ + @Parameters(name="{0} = {1}") + public static List testCases() + { + String data[][] = new String[][] { + // From old PathMapTest + {"/abs/path","abspath"}, + {"/abs/path/xxx","default"}, + {"/abs/pith","default"}, + {"/abs/path/longer","longpath"}, + {"/abs/path/","default"}, + {"/abs/path/foo","default"}, + {"/animal/bird/eagle/bald","birds"}, + {"/animal/fish/shark/hammerhead","fishes"}, + {"/animal/insect/ladybug","animals"}, + {"/animal","animals"}, + {"/animal/","animals"}, + {"/animal/other","animals"}, + {"/animal/*","animals"}, + {"/downloads/distribution.tar.gz","tarball"}, + {"/downloads/script.gz","gzipped"}, + {"/animal/arhive.gz","animals"}, + {"/Other/path","default"}, + {"/\u20ACuro/path","money"}, + {"/","root"}, + + // Extra tests + {"/downloads/readme.txt","default"}, + {"/downloads/logs.tgz","default"}, + {"/main.css","default"} + }; + + return Arrays.asList(data); + } + + private static PathMappings mappings; + + static { + mappings = new PathMappings<>(); + + // From old PathMapTest + mappings.put(new ServletPathSpec("/abs/path"),"abspath"); // 1 + mappings.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2 + mappings.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3 + mappings.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4 + mappings.put(new ServletPathSpec("/animal/*"),"animals"); // 5 + mappings.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6 + mappings.put(new ServletPathSpec("*.gz"),"gzipped"); // 7 + mappings.put(new ServletPathSpec("/"),"default"); // 8 + // 9 was the old Jetty ":" spec delimited case (no longer valid) + mappings.put(new ServletPathSpec(""),"root"); // 10 + mappings.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11 + } + + @Parameter(0) + public String inputPath; + + @Parameter(1) + public String expectedResource; + + @Test + public void testMatch() + { + assertThat("Match on ["+ inputPath+ "]", mappings.getMatch(inputPath).getResource(), is(expectedResource)); + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java new file mode 100644 index 0000000000..7252a9c1b4 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java @@ -0,0 +1,188 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import org.junit.Test; + +public class ServletPathSpecTest +{ + private void assertBadServletPathSpec(String pathSpec) + { + try + { + new ServletPathSpec(pathSpec); + fail("Expected IllegalArgumentException for a bad servlet pathspec on: " + pathSpec); + } + catch (IllegalArgumentException e) + { + // expected path + System.out.println(e); + } + } + + private void assertMatches(ServletPathSpec spec, String path) + { + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + assertThat(msg,spec.matches(path),is(true)); + } + + private void assertNotMatches(ServletPathSpec spec, String path) + { + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + assertThat(msg,spec.matches(path),is(false)); + } + + @Test + public void testBadServletPathSpecA() + { + assertBadServletPathSpec("foo"); + } + + @Test + public void testBadServletPathSpecB() + { + assertBadServletPathSpec("/foo/*.do"); + } + + @Test + public void testBadServletPathSpecC() + { + assertBadServletPathSpec("foo/*.do"); + } + + @Test + public void testBadServletPathSpecD() + { + assertBadServletPathSpec("foo/*.*do"); + } + + @Test + public void testBadServletPathSpecE() + { + assertBadServletPathSpec("*do"); + } + + @Test + public void testDefaultPathSpec() + { + ServletPathSpec spec = new ServletPathSpec("/"); + assertEquals("Spec.pathSpec","/",spec.getPathSpec()); + assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); + } + + @Test + public void testExactPathSpec() + { + ServletPathSpec spec = new ServletPathSpec("/abs/path"); + assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec()); + assertEquals("Spec.pathDepth",2,spec.getPathDepth()); + + assertMatches(spec,"/abs/path"); + + assertNotMatches(spec,"/abs/path/"); + assertNotMatches(spec,"/abs/path/more"); + assertNotMatches(spec,"/foo"); + assertNotMatches(spec,"/foo/abs/path"); + assertNotMatches(spec,"/foo/abs/path/"); + } + + @Test + public void testGetPathInfo() + { + assertEquals("pathInfo exact",null,new ServletPathSpec("/Foo/bar").getPathInfo("/Foo/bar")); + assertEquals("pathInfo prefix","/bar",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/bar")); + assertEquals("pathInfo prefix","/*",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/*")); + assertEquals("pathInfo prefix","/",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/")); + assertEquals("pathInfo prefix",null,new ServletPathSpec("/Foo/*").getPathInfo("/Foo")); + assertEquals("pathInfo suffix",null,new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext")); + assertEquals("pathInfo default",null,new ServletPathSpec("/").getPathInfo("/Foo/bar.ext")); + + assertEquals("pathInfo default","/xxx/zzz",new ServletPathSpec("/*").getPathInfo("/xxx/zzz")); + } + + @Test + public void testNullPathSpec() + { + ServletPathSpec spec = new ServletPathSpec(null); + assertEquals("Spec.pathSpec","",spec.getPathSpec()); + assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); + } + + @Test + public void testRootPathSpec() + { + ServletPathSpec spec = new ServletPathSpec(""); + assertEquals("Spec.pathSpec","",spec.getPathSpec()); + assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); + } + + @Test + public void testPathMatch() + { + assertEquals("pathMatch exact","/Foo/bar",new ServletPathSpec("/Foo/bar").getPathMatch("/Foo/bar")); + assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/bar")); + assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/")); + assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo")); + assertEquals("pathMatch suffix","/Foo/bar.ext",new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext")); + assertEquals("pathMatch default","/Foo/bar.ext",new ServletPathSpec("/").getPathMatch("/Foo/bar.ext")); + + assertEquals("pathMatch default","",new ServletPathSpec("/*").getPathMatch("/xxx/zzz")); + } + + @Test + public void testPrefixPathSpec() + { + ServletPathSpec spec = new ServletPathSpec("/downloads/*"); + assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec()); + assertEquals("Spec.pathDepth",2,spec.getPathDepth()); + + assertMatches(spec,"/downloads/logo.jpg"); + assertMatches(spec,"/downloads/distribution.tar.gz"); + assertMatches(spec,"/downloads/distribution.tgz"); + assertMatches(spec,"/downloads/distribution.zip"); + + assertMatches(spec,"/downloads"); + + assertEquals("Spec.pathInfo","/",spec.getPathInfo("/downloads/")); + assertEquals("Spec.pathInfo","/distribution.zip",spec.getPathInfo("/downloads/distribution.zip")); + assertEquals("Spec.pathInfo","/dist/9.0/distribution.tar.gz",spec.getPathInfo("/downloads/dist/9.0/distribution.tar.gz")); + } + + @Test + public void testSuffixPathSpec() + { + ServletPathSpec spec = new ServletPathSpec("*.gz"); + assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec()); + assertEquals("Spec.pathDepth",0,spec.getPathDepth()); + + assertMatches(spec,"/downloads/distribution.tar.gz"); + assertMatches(spec,"/downloads/jetty.log.gz"); + + assertNotMatches(spec,"/downloads/distribution.zip"); + assertNotMatches(spec,"/downloads/distribution.tgz"); + assertNotMatches(spec,"/abs/path"); + + assertEquals("Spec.pathInfo",null,spec.getPathInfo("/downloads/distribution.tar.gz")); + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java new file mode 100644 index 0000000000..4136ba2f29 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java @@ -0,0 +1,87 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * Tests for bad path specs on ServerEndpoint Path Param / URI Template + */ +@RunWith(Parameterized.class) +public class UriTemplatePathSpecBadSpecsTest +{ + private static void bad(List data, String str) + { + data.add(new String[] + { str }); + } + + @Parameters + public static Collection data() + { + List data = new ArrayList<>(); + bad(data,"/a/b{var}"); // bad syntax - variable does not encompass whole path segment + bad(data,"a/{var}"); // bad syntax - no start slash + bad(data,"/a/{var/b}"); // path segment separator in variable name + bad(data,"/{var}/*"); // bad syntax - no globs allowed + bad(data,"/{var}.do"); // bad syntax - variable does not encompass whole path segment + bad(data,"/a/{var*}"); // use of glob character not allowed in variable name + bad(data,"/a/{}"); // bad syntax - no variable name + // MIGHT BE ALLOWED bad(data,"/a/{---}"); // no alpha in variable name + bad(data,"{var}"); // bad syntax - no start slash + bad(data,"/a/{my special variable}"); // bad syntax - space in variable name + bad(data,"/a/{var}/{var}"); // variable name duplicate + // MIGHT BE ALLOWED bad(data,"/a/{var}/{Var}/{vAR}"); // variable name duplicated (diff case) + bad(data,"/a/../../../{var}"); // path navigation not allowed + bad(data,"/a/./{var}"); // path navigation not allowed + bad(data,"/a//{var}"); // bad syntax - double path slash (no path segment) + return data; + } + + private String pathSpec; + + public UriTemplatePathSpecBadSpecsTest(String pathSpec) + { + this.pathSpec = pathSpec; + } + + @Test + public void testBadPathSpec() + { + try + { + new UriTemplatePathSpec(this.pathSpec); + fail("Expected IllegalArgumentException for a bad PathParam pathspec on: " + pathSpec); + } + catch (IllegalArgumentException e) + { + // expected path + System.out.println(e.getMessage()); + } + } +} diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java new file mode 100644 index 0000000000..7908344e09 --- /dev/null +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java @@ -0,0 +1,284 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.Map; + +import org.junit.Test; + +/** + * Tests for URI Template Path Specs + */ +public class UriTemplatePathSpecTest +{ + private void assertDetectedVars(UriTemplatePathSpec spec, String... expectedVars) + { + String prefix = String.format("Spec(\"%s\")",spec.getPathSpec()); + assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount()); + assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length); + for (int i = 0; i < expectedVars.length; i++) + { + assertEquals(String.format("%s.variable[%d]",prefix,i),expectedVars[i],spec.getVariables()[i]); + } + } + + private void assertMatches(PathSpec spec, String path) + { + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + assertThat(msg,spec.matches(path),is(true)); + } + + private void assertNotMatches(PathSpec spec, String path) + { + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + assertThat(msg,spec.matches(path),is(false)); + } + + @Test + public void testDefaultPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/"); + assertEquals("Spec.pathSpec","/",spec.getPathSpec()); + assertEquals("Spec.pattern","^/$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",1,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); + + assertEquals("Spec.variableCount",0,spec.getVariableCount()); + assertEquals("Spec.variable.length",0,spec.getVariables().length); + } + + @Test + public void testExactOnePathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/a"); + assertEquals("Spec.pathSpec","/a",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",1,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); + + assertMatches(spec,"/a"); + assertMatches(spec,"/a?type=other"); + assertNotMatches(spec,"/a/b"); + assertNotMatches(spec,"/a/"); + + assertEquals("Spec.variableCount",0,spec.getVariableCount()); + assertEquals("Spec.variable.length",0,spec.getVariables().length); + } + + @Test + public void testExactPathSpec_TestWebapp() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/deep.thought/"); + assertEquals("Spec.pathSpec","/deep.thought/",spec.getPathSpec()); + assertEquals("Spec.pattern","^/deep\\.thought/$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",1,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); + + assertMatches(spec,"/deep.thought/"); + assertNotMatches(spec,"/deep.thought"); + + assertEquals("Spec.variableCount",0,spec.getVariableCount()); + assertEquals("Spec.variable.length",0,spec.getVariables().length); + } + + @Test + public void testExactTwoPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/b"); + assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",2,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); + + assertEquals("Spec.variableCount",0,spec.getVariableCount()); + assertEquals("Spec.variable.length",0,spec.getVariables().length); + + assertMatches(spec,"/a/b"); + + assertNotMatches(spec,"/a/b/"); + assertNotMatches(spec,"/a/"); + assertNotMatches(spec,"/a/bb"); + } + + @Test + public void testMiddleVarPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var}/c"); + assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",3,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); + + assertDetectedVars(spec,"var"); + + assertMatches(spec,"/a/b/c"); + assertMatches(spec,"/a/zz/c"); + assertMatches(spec,"/a/hello+world/c"); + assertNotMatches(spec,"/a/bc"); + assertNotMatches(spec,"/a/b/"); + assertNotMatches(spec,"/a/b"); + + Map mapped = spec.getPathParams("/a/b/c"); + assertThat("Spec.pathParams",mapped,notNullValue()); + assertThat("Spec.pathParams.size",mapped.size(),is(1)); + assertEquals("Spec.pathParams[var]","b",mapped.get("var")); + } + + @Test + public void testOneVarPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{foo}"); + assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",2,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); + + assertDetectedVars(spec,"foo"); + + assertMatches(spec,"/a/b"); + assertNotMatches(spec,"/a/"); + assertNotMatches(spec,"/a"); + + Map mapped = spec.getPathParams("/a/b"); + assertThat("Spec.pathParams",mapped,notNullValue()); + assertThat("Spec.pathParams.size",mapped.size(),is(1)); + assertEquals("Spec.pathParams[foo]","b",mapped.get("foo")); + } + + @Test + public void testOneVarSuffixPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var}/b/c"); + assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec()); + assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",3,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup()); + + assertDetectedVars(spec,"var"); + + assertMatches(spec,"/a/b/c"); + assertMatches(spec,"/az/b/c"); + assertMatches(spec,"/hello+world/b/c"); + assertNotMatches(spec,"/a/bc"); + assertNotMatches(spec,"/a/b/"); + assertNotMatches(spec,"/a/b"); + + Map mapped = spec.getPathParams("/a/b/c"); + assertThat("Spec.pathParams",mapped,notNullValue()); + assertThat("Spec.pathParams.size",mapped.size(),is(1)); + assertEquals("Spec.pathParams[var]","a",mapped.get("var")); + } + + @Test + public void testTwoVarComplexInnerPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/c/{var2}/e"); + assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",5,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); + + assertDetectedVars(spec,"var1","var2"); + + assertMatches(spec,"/a/b/c/d/e"); + assertNotMatches(spec,"/a/bc/d/e"); + assertNotMatches(spec,"/a/b/d/e"); + assertNotMatches(spec,"/a/b//d/e"); + + Map mapped = spec.getPathParams("/a/b/c/d/e"); + assertThat("Spec.pathParams",mapped,notNullValue()); + assertThat("Spec.pathParams.size",mapped.size(),is(2)); + assertEquals("Spec.pathParams[var1]","b",mapped.get("var1")); + assertEquals("Spec.pathParams[var2]","d",mapped.get("var2")); + } + + @Test + public void testTwoVarComplexOuterPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}/b/{var2}/{var3}"); + assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec()); + assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",4,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); + + assertDetectedVars(spec,"var1","var2","var3"); + + assertMatches(spec,"/a/b/c/d"); + assertNotMatches(spec,"/a/bc/d/e"); + assertNotMatches(spec,"/a/c/d/e"); + assertNotMatches(spec,"/a//d/e"); + + Map mapped = spec.getPathParams("/a/b/c/d"); + assertThat("Spec.pathParams",mapped,notNullValue()); + assertThat("Spec.pathParams.size",mapped.size(),is(3)); + assertEquals("Spec.pathParams[var1]","a",mapped.get("var1")); + assertEquals("Spec.pathParams[var2]","c",mapped.get("var2")); + assertEquals("Spec.pathParams[var3]","d",mapped.get("var3")); + } + + @Test + public void testTwoVarPrefixPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/{var2}"); + assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec()); + assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",3,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); + + assertDetectedVars(spec,"var1","var2"); + + assertMatches(spec,"/a/b/c"); + assertNotMatches(spec,"/a/bc"); + assertNotMatches(spec,"/a/b/"); + assertNotMatches(spec,"/a/b"); + + Map mapped = spec.getPathParams("/a/b/c"); + assertThat("Spec.pathParams",mapped,notNullValue()); + assertThat("Spec.pathParams.size",mapped.size(),is(2)); + assertEquals("Spec.pathParams[var1]","b",mapped.get("var1")); + assertEquals("Spec.pathParams[var2]","c",mapped.get("var2")); + } + + @Test + public void testVarOnlyPathSpec() + { + UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}"); + assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec()); + assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern()); + assertEquals("Spec.pathDepth",1,spec.getPathDepth()); + assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); + + assertDetectedVars(spec,"var1"); + + assertMatches(spec,"/a"); + assertNotMatches(spec,"/"); + assertNotMatches(spec,"/a/b"); + assertNotMatches(spec,"/a/b/c"); + + Map mapped = spec.getPathParams("/a"); + assertThat("Spec.pathParams",mapped,notNullValue()); + assertThat("Spec.pathParams.size",mapped.size(),is(1)); + assertEquals("Spec.pathParams[var1]","a",mapped.get("var1")); + } +} -- cgit v1.2.3 From 77d4b54082f6789168ea7ef1523e8e1b924bb560 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 14 Dec 2015 11:22:24 -0700 Subject: 482042 - New API, Allow customization of ServletHandler path mapping + Swapping out PathMap for PathMappings in ServletHandler Conflicts: jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java --- .../eclipse/jetty/http/pathmap/PathMappings.java | 20 ++++++-- .../org/eclipse/jetty/http/pathmap/PathSpec.java | 2 +- .../jetty/http/pathmap/ServletPathSpec.java | 2 +- .../jetty/http/pathmap/PathMappingsTest.java | 57 ++++------------------ .../jetty/http/pathmap/RegexPathSpecTest.java | 14 +++--- .../jetty/http/pathmap/ServletPathSpecTest.java | 16 +++--- .../http/pathmap/UriTemplatePathSpecTest.java | 28 +++++------ .../org/eclipse/jetty/servlet/DefaultServlet.java | 7 +-- .../java/org/eclipse/jetty/servlet/Invoker.java | 8 +-- .../org/eclipse/jetty/servlet/ServletHandler.java | 24 +++++---- 10 files changed, 80 insertions(+), 98 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java index e0d975ca7d..348a4b4284 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java @@ -44,6 +44,7 @@ public class PathMappings implements Iterable>, Dumpable private static final Logger LOG = Log.getLogger(PathMappings.class); private List> mappings = new ArrayList>(); private MappedResource defaultResource = null; + private MappedResource rootResource = null; @Override public String dump() @@ -105,6 +106,11 @@ public class PathMappings implements Iterable>, Dumpable public MappedResource getMatch(String path) { + if (path.equals("/") && rootResource != null) + { + return rootResource; + } + int len = mappings.size(); for (int i = 0; i < len; i++) { @@ -123,14 +129,22 @@ public class PathMappings implements Iterable>, Dumpable return mappings.iterator(); } + @SuppressWarnings("incomplete-switch") public void put(PathSpec pathSpec, E resource) { MappedResource entry = new MappedResource<>(pathSpec,resource); - if (pathSpec.group == PathSpecGroup.DEFAULT) + switch (pathSpec.group) { - defaultResource = entry; + case DEFAULT: + defaultResource = entry; + break; + case ROOT: + rootResource = entry; + break; } - // TODO: warning on replacement of existing mapping? + + // TODO: add warning when replacing an existing pathspec? + mappings.add(entry); if (LOG.isDebugEnabled()) LOG.debug("Added {} to {}",entry,this); diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java index 122211a558..b9878472bb 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java @@ -119,7 +119,7 @@ public abstract class PathSpec implements Comparable * * @return the as-provided path spec */ - public String getPathSpec() + public String getDeclaration() { return pathSpec; } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java index 55399748e2..44f8a5fae2 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java @@ -245,7 +245,7 @@ public class ServletPathSpec extends PathSpec case EXACT: return pathSpec.equals(path); case PREFIX_GLOB: - return (!"/".equals(path) && isWildcardMatch(path)); + return isWildcardMatch(path); case SUFFIX_GLOB: return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1); case ROOT: diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java index 06cd14bcce..36df42e553 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java @@ -77,61 +77,22 @@ public class PathMappingsTest assertMatch(p,"/animal/fish/trout/cam","animalCam"); assertMatch(p,"/entrance/cam","entranceCam"); } - + /** - * Test the match order rules imposed by the Servlet API. - *

- *

    - *
  • Exact match
  • - *
  • Longest prefix match
  • - *
  • Longest suffix match
  • - *
  • default
  • - *
+ * Test the match order rules imposed by the Servlet API (default vs any) */ @Test - public void testServletMatchOrder() + public void testServletMatchDefault() { PathMappings p = new PathMappings<>(); - p.put(new ServletPathSpec("/abs/path"),"abspath"); // 1 - p.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2 - p.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3 - p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4 - p.put(new ServletPathSpec("/animal/*"),"animals"); // 5 - p.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6 - p.put(new ServletPathSpec("*.gz"),"gzipped"); // 7 - p.put(new ServletPathSpec("/"),"default"); // 8 - // 9 was the old Jetty ":" spec delimited case (no longer valid) - p.put(new ServletPathSpec(""),"root"); // 10 - p.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11 - - // dumpMappings(p); + p.put(new ServletPathSpec("/"),"default"); + p.put(new ServletPathSpec("/*"),"any"); - // From old PathMapTest - assertMatch(p,"/abs/path","abspath"); - assertMatch(p,"/abs/path/xxx","default"); - assertMatch(p,"/abs/pith","default"); - assertMatch(p,"/abs/path/longer","longpath"); - assertMatch(p,"/abs/path/","default"); - assertMatch(p,"/abs/path/foo","default"); - assertMatch(p,"/animal/bird/eagle/bald","birds"); - assertMatch(p,"/animal/fish/shark/hammerhead","fishes"); - assertMatch(p,"/animal/insect/ladybug","animals"); - assertMatch(p,"/animal","animals"); - assertMatch(p,"/animal/","animals"); - assertMatch(p,"/animal/other","animals"); - assertMatch(p,"/animal/*","animals"); - assertMatch(p,"/downloads/distribution.tar.gz","tarball"); - assertMatch(p,"/downloads/script.gz","gzipped"); - assertMatch(p,"/animal/arhive.gz","animals"); - assertMatch(p,"/Other/path","default"); - assertMatch(p,"/\u20ACuro/path","money"); - assertMatch(p,"/","root"); - - // Extra tests - assertMatch(p,"/downloads/readme.txt","default"); - assertMatch(p,"/downloads/logs.tgz","default"); - assertMatch(p,"/main.css","default"); + assertMatch(p,"/abs/path","any"); + assertMatch(p,"/abs/path/xxx","any"); + assertMatch(p,"/animal/bird/eagle/bald","any"); + assertMatch(p,"/","any"); } /** diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java index a44c4dca50..f78cd75040 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java @@ -28,13 +28,13 @@ public class RegexPathSpecTest { public static void assertMatches(PathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(true)); } public static void assertNotMatches(PathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -42,7 +42,7 @@ public class RegexPathSpecTest public void testExactSpec() { RegexPathSpec spec = new RegexPathSpec("^/a$"); - assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/a$",spec.getDeclaration()); assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group); @@ -57,7 +57,7 @@ public class RegexPathSpecTest public void testMiddleSpec() { RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$"); - assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getDeclaration()); assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); @@ -78,7 +78,7 @@ public class RegexPathSpecTest public void testMiddleSpecNoGrouping() { RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$"); - assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getDeclaration()); assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); @@ -99,7 +99,7 @@ public class RegexPathSpecTest public void testPrefixSpec() { RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$"); - assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group); @@ -117,7 +117,7 @@ public class RegexPathSpecTest public void testSuffixSpec() { RegexPathSpec spec = new RegexPathSpec("^(.*).do$"); - assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^(.*).do$",spec.getDeclaration()); assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",0,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java index 7252a9c1b4..fb8e65b25a 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java @@ -43,13 +43,13 @@ public class ServletPathSpecTest private void assertMatches(ServletPathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(true)); } private void assertNotMatches(ServletPathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -87,7 +87,7 @@ public class ServletPathSpecTest public void testDefaultPathSpec() { ServletPathSpec spec = new ServletPathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/",spec.getDeclaration()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -95,7 +95,7 @@ public class ServletPathSpecTest public void testExactPathSpec() { ServletPathSpec spec = new ServletPathSpec("/abs/path"); - assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/abs/path",spec.getDeclaration()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertMatches(spec,"/abs/path"); @@ -125,7 +125,7 @@ public class ServletPathSpecTest public void testNullPathSpec() { ServletPathSpec spec = new ServletPathSpec(null); - assertEquals("Spec.pathSpec","",spec.getPathSpec()); + assertEquals("Spec.pathSpec","",spec.getDeclaration()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -133,7 +133,7 @@ public class ServletPathSpecTest public void testRootPathSpec() { ServletPathSpec spec = new ServletPathSpec(""); - assertEquals("Spec.pathSpec","",spec.getPathSpec()); + assertEquals("Spec.pathSpec","",spec.getDeclaration()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -154,7 +154,7 @@ public class ServletPathSpecTest public void testPrefixPathSpec() { ServletPathSpec spec = new ServletPathSpec("/downloads/*"); - assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/downloads/*",spec.getDeclaration()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertMatches(spec,"/downloads/logo.jpg"); @@ -173,7 +173,7 @@ public class ServletPathSpecTest public void testSuffixPathSpec() { ServletPathSpec spec = new ServletPathSpec("*.gz"); - assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec()); + assertEquals("Spec.pathSpec","*.gz",spec.getDeclaration()); assertEquals("Spec.pathDepth",0,spec.getPathDepth()); assertMatches(spec,"/downloads/distribution.tar.gz"); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java index 7908344e09..50dcab923c 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java @@ -34,7 +34,7 @@ public class UriTemplatePathSpecTest { private void assertDetectedVars(UriTemplatePathSpec spec, String... expectedVars) { - String prefix = String.format("Spec(\"%s\")",spec.getPathSpec()); + String prefix = String.format("Spec(\"%s\")",spec.getDeclaration()); assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount()); assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length); for (int i = 0; i < expectedVars.length; i++) @@ -45,13 +45,13 @@ public class UriTemplatePathSpecTest private void assertMatches(PathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(true)); } private void assertNotMatches(PathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -59,7 +59,7 @@ public class UriTemplatePathSpecTest public void testDefaultPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/",spec.getDeclaration()); assertEquals("Spec.pattern","^/$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -72,7 +72,7 @@ public class UriTemplatePathSpecTest public void testExactOnePathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a"); - assertEquals("Spec.pathSpec","/a",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a",spec.getDeclaration()); assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -90,7 +90,7 @@ public class UriTemplatePathSpecTest public void testExactPathSpec_TestWebapp() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/deep.thought/"); - assertEquals("Spec.pathSpec","/deep.thought/",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/deep.thought/",spec.getDeclaration()); assertEquals("Spec.pattern","^/deep\\.thought/$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -106,7 +106,7 @@ public class UriTemplatePathSpecTest public void testExactTwoPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/b"); - assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/b",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -125,7 +125,7 @@ public class UriTemplatePathSpecTest public void testMiddleVarPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var}/c"); - assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{var}/c",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -149,7 +149,7 @@ public class UriTemplatePathSpecTest public void testOneVarPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{foo}"); - assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{foo}",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); @@ -170,7 +170,7 @@ public class UriTemplatePathSpecTest public void testOneVarSuffixPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var}/b/c"); - assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/{var}/b/c",spec.getDeclaration()); assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup()); @@ -194,7 +194,7 @@ public class UriTemplatePathSpecTest public void testTwoVarComplexInnerPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/c/{var2}/e"); - assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",5,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -217,7 +217,7 @@ public class UriTemplatePathSpecTest public void testTwoVarComplexOuterPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}/b/{var2}/{var3}"); - assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getDeclaration()); assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",4,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -241,7 +241,7 @@ public class UriTemplatePathSpecTest public void testTwoVarPrefixPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/{var2}"); - assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); @@ -264,7 +264,7 @@ public class UriTemplatePathSpecTest public void testVarOnlyPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}"); - assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/{var1}",spec.getDeclaration()); assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 56df44c2de..9ed32be55b 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -47,6 +47,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PathMap.MappedEntry; +import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.io.WriterOutputStream; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.InclusiveByteRange; @@ -691,9 +692,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null) { - MappedEntry entry=_servletHandler.getHolderEntry(welcome_in_context); - if (entry!=null && entry.getValue()!=_defaultHolder && - (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context)))) + MappedResource entry=_servletHandler.getHolderEntry(welcome_in_context); + if (entry!=null && entry.getResource()!=_defaultHolder && + (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context)))) welcome_servlet=welcome_in_context; } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java index 999a086346..0edd706a70 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java @@ -32,6 +32,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.PathMap.MappedEntry; +import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpChannel; @@ -72,7 +74,7 @@ public class Invoker extends HttpServlet private ContextHandler _contextHandler; private ServletHandler _servletHandler; - private Map.Entry _invokerEntry; + private MappedResource _invokerEntry; private Map _parameters; private boolean _nonContextServlets; private boolean _verbose; @@ -171,12 +173,12 @@ public class Invoker extends HttpServlet // Check for existing mapping (avoid threaded race). String path=URIUtil.addPaths(servlet_path,servlet); - Map.Entry entry = _servletHandler.getHolderEntry(path); + MappedResource entry = _servletHandler.getHolderEntry(path); if (entry!=null && !entry.equals(_invokerEntry)) { // Use the holder - holder=(ServletHolder)entry.getValue(); + holder=(ServletHolder)entry.getResource(); } else { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index c3b152c6c3..e767851454 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -52,7 +52,10 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; -import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.http.pathmap.MappedResource; +import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.http.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.security.IdentityService; @@ -121,7 +124,8 @@ public class ServletHandler extends ScopedHandler private MultiMap _filterNameMappings; private final Map _servletNameMap=new HashMap<>(); - private PathMap _servletPathMap; + // private PathMap _servletPathMap; + private PathMappings _servletPathMap; private ListenerHolder[] _listeners=new ListenerHolder[0]; @@ -383,7 +387,7 @@ public class ServletHandler extends ScopedHandler * @param pathInContext Path within _context. * @return PathMap Entries pathspec to ServletHolder */ - public PathMap.MappedEntry getHolderEntry(String pathInContext) + public MappedResource getHolderEntry(String pathInContext) { if (_servletPathMap==null) return null; @@ -470,14 +474,14 @@ public class ServletHandler extends ScopedHandler if (target.startsWith("/")) { // Look for the servlet by path - PathMap.MappedEntry entry=getHolderEntry(target); + MappedResource entry=getHolderEntry(target); if (entry!=null) { - servlet_holder=entry.getValue(); + PathSpec pathSpec = entry.getPathSpec(); + servlet_holder=entry.getResource(); - String servlet_path_spec= entry.getKey(); - String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target); - String path_info=PathMap.pathInfo(servlet_path_spec,target); + String servlet_path=pathSpec.getPathMatch(target); + String path_info=pathSpec.getPathInfo(target); if (DispatcherType.INCLUDE.equals(type)) { @@ -1432,7 +1436,7 @@ public class ServletHandler extends ScopedHandler } else { - PathMap pm = new PathMap<>(); + PathMappings pm = new PathMappings<>(); Map servletPathMappings = new HashMap(); //create a map of paths to set of ServletMappings that define that mapping @@ -1495,7 +1499,7 @@ public class ServletHandler extends ScopedHandler if (LOG.isDebugEnabled()) LOG.debug("Chose path={} mapped to servlet={} from default={}", pathSpec, finalMapping.getServletName(), finalMapping.isDefault()); servletPathMappings.put(pathSpec, finalMapping); - pm.put(pathSpec,_servletNameMap.get(finalMapping.getServletName())); + pm.put(new ServletPathSpec(pathSpec),_servletNameMap.get(finalMapping.getServletName())); } _servletPathMap=pm; -- cgit v1.2.3 From 6e0ad429d9f8bbf2124ee0581ce9a2394222bf96 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 14 Dec 2015 14:58:12 -0700 Subject: 484350 - Allow GzipHandler path include/exclude to use regex + Overhauled IncludeExclude to use java 8 predicate + Introduced PathSpecSet to standardize path IncludeExclude + GzipHandler now uses PathSpecSet for paths Conflicts: jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java --- .../main/java/org/eclipse/jetty/http/PathMap.java | 10 +- .../eclipse/jetty/http/pathmap/PathSpecSet.java | 223 +++++++++++++++++++++ .../eclipse/jetty/servlets/gzip/GzipHandler.java | 45 ++++- .../jetty/servlets/gzip/GzipHandlerTest.java | 16 ++ .../org/eclipse/jetty/util/IncludeExclude.java | 70 +++++-- .../java/org/eclipse/jetty/util/Predicate.java | 31 +++ .../main/java/org/eclipse/jetty/util/RegexSet.java | 8 +- .../org/eclipse/jetty/util/IncludeExcludeTest.java | 11 +- 8 files changed, 378 insertions(+), 36 deletions(-) create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 482c92b9da..807d5966e1 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -29,6 +29,7 @@ import java.util.StringTokenizer; import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.IncludeExclude; +import org.eclipse.jetty.util.Predicate; import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.URIUtil; @@ -573,7 +574,7 @@ public class PathMap extends HashMap } } - public static class PathSet extends AbstractSet implements IncludeExclude.MatchSet + public static class PathSet extends AbstractSet implements Predicate { private final PathMap _map = new PathMap<>(); @@ -607,13 +608,16 @@ public class PathMap extends HashMap return _map.containsKey(o); } + @Override + public boolean test(String s) + { + return _map.containsMatch(s); + } public boolean containsMatch(String s) { return _map.containsMatch(s); } - - @Override public boolean matches(String item) { return _map.containsMatch(item); diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java new file mode 100644 index 0000000000..330b55ab25 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java @@ -0,0 +1,223 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.http.pathmap; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.jetty.util.Predicate; + +/** + * A Set of PathSpec strings. + *

+ * Used by {@link org.eclipse.jetty.util.IncludeExclude} logic + */ +public class PathSpecSet implements Set, Predicate +{ + private final Set specs = new TreeSet<>(); + + @Override + public boolean test(String s) + { + for (PathSpec spec : specs) + { + if (spec.matches(s)) + { + return true; + } + } + return false; + } + + @Override + public boolean isEmpty() + { + return specs.isEmpty(); + } + + @Override + public Iterator iterator() + { + return new Iterator() + { + private Iterator iter = specs.iterator(); + + @Override + public boolean hasNext() + { + return iter.hasNext(); + } + + @Override + public String next() + { + PathSpec spec = iter.next(); + if (spec == null) + { + return null; + } + return spec.getDeclaration(); + } + + @Override + public void remove() + { + throw new UnsupportedOperationException("Remove not supported by this Iterator"); + } + }; + } + + @Override + public int size() + { + return specs.size(); + } + + @Override + public boolean contains(Object o) + { + if (o instanceof PathSpec) + { + return specs.contains(o); + } + if (o instanceof String) + { + return specs.contains(toPathSpec((String)o)); + } + return false; + } + + private PathSpec asPathSpec(Object o) + { + if (o == null) + { + return null; + } + if (o instanceof PathSpec) + { + return (PathSpec)o; + } + if (o instanceof String) + { + return toPathSpec((String)o); + } + return toPathSpec(o.toString()); + } + + private PathSpec toPathSpec(String rawSpec) + { + if ((rawSpec == null) || (rawSpec.length() < 1)) + { + throw new RuntimeException("Path Spec String must start with '^', '/', or '*.': got [" + rawSpec + "]"); + } + if (rawSpec.charAt(0) == '^') + { + return new RegexPathSpec(rawSpec); + } + else + { + return new ServletPathSpec(rawSpec); + } + } + + @Override + public Object[] toArray() + { + return toArray(new String[specs.size()]); + } + + @Override + public T[] toArray(T[] a) + { + int i = 0; + for (PathSpec spec : specs) + { + a[i++] = (T)spec.getDeclaration(); + } + return a; + } + + @Override + public boolean add(String e) + { + return specs.add(toPathSpec(e)); + } + + @Override + public boolean remove(Object o) + { + return specs.remove(asPathSpec(o)); + } + + @Override + public boolean containsAll(Collection coll) + { + for (Object o : coll) + { + if (!specs.contains(asPathSpec(o))) + return false; + } + return true; + } + + @Override + public boolean addAll(Collection coll) + { + boolean ret = false; + + for (String s : coll) + { + ret |= add(s); + } + + return ret; + } + + @Override + public boolean retainAll(Collection coll) + { + List collSpecs = new ArrayList<>(); + for (Object o : coll) + { + collSpecs.add(asPathSpec(o)); + } + return specs.retainAll(collSpecs); + } + + @Override + public boolean removeAll(Collection coll) + { + List collSpecs = new ArrayList<>(); + for (Object o : coll) + { + collSpecs.add(asPathSpec(o)); + } + return specs.removeAll(collSpecs); + } + + @Override + public void clear() + { + specs.clear(); + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java index 7a843a5879..f1924e0d6c 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java @@ -36,7 +36,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.http.pathmap.PathSpecSet; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.util.IncludeExclude; @@ -73,7 +73,7 @@ public class GzipHandler extends HandlerWrapper private final IncludeExclude _agentPatterns=new IncludeExclude<>(RegexSet.class); private final IncludeExclude _methods = new IncludeExclude<>(); - private final IncludeExclude _paths = new IncludeExclude<>(PathMap.PathSet.class); + private final IncludeExclude _paths = new IncludeExclude(PathSpecSet.class); private final IncludeExclude _mimeTypes = new IncludeExclude<>(); /* ------------------------------------------------------------ */ @@ -136,9 +136,27 @@ public class GzipHandler extends HandlerWrapper /* ------------------------------------------------------------ */ /** + * Add path to excluded paths list. + *

+ * There are 2 syntaxes supported, Servlet url-pattern based, and + * Regex based. This means that the initial characters on the path spec + * line are very strict, and determine the behavior of the path matching. + *

    + *
  • If the spec starts with '^' the spec is assumed to be + * a regex based path spec and will match with normal Java regex rules.
  • + *
  • If the spec starts with '/' then spec is assumed to be + * a Servlet url-pattern rules path spec for either an exact match + * or prefix based match.
  • + *
  • If the spec starts with '*.' then spec is assumed to be + * a Servlet url-pattern rules path spec for a suffix based match.
  • + *
  • All other syntaxes are unsupported
  • + *
+ *

+ * Note: inclusion takes precedence over exclude. + * * @param pathspecs Path specs (as per servlet spec) to exclude. If a * ServletContext is available, the paths are relative to the context path, - * otherwise they are absolute. + * otherwise they are absolute.
* For backward compatibility the pathspecs may be comma separated strings, but this * will not be supported in future versions. */ @@ -183,12 +201,27 @@ public class GzipHandler extends HandlerWrapper /* ------------------------------------------------------------ */ /** - * Add path specs to include. Inclusion takes precedence over exclusion. + * Add path specs to include. + *

+ * There are 2 syntaxes supported, Servlet url-pattern based, and + * Regex based. This means that the initial characters on the path spec + * line are very strict, and determine the behavior of the path matching. + *

    + *
  • If the spec starts with '^' the spec is assumed to be + * a regex based path spec and will match with normal Java regex rules.
  • + *
  • If the spec starts with '/' then spec is assumed to be + * a Servlet url-pattern rules path spec for either an exact match + * or prefix based match.
  • + *
  • If the spec starts with '*.' then spec is assumed to be + * a Servlet url-pattern rules path spec for a suffix based match.
  • + *
  • All other syntaxes are unsupported
  • + *
+ *

+ * Note: inclusion takes precedence over exclude. + * * @param pathspecs Path specs (as per servlet spec) to include. If a * ServletContext is available, the paths are relative to the context path, * otherwise they are absolute - * For backward compatibility the pathspecs may be comma separated strings, but this - * will not be supported in future versions. */ public void addIncludedPaths(String... pathspecs) { diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java index 1982a1c278..2d7183c742 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java @@ -18,7 +18,10 @@ package org.eclipse.jetty.servlets.gzip; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; @@ -26,6 +29,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.util.Arrays; import java.util.zip.GZIPInputStream; import javax.servlet.ServletException; @@ -204,4 +208,16 @@ public class GzipHandlerTest assertEquals(__icontent, testOut.toString("UTF8")); } + + @Test + public void testAddGetPaths() + { + GzipHandler gzip = new GzipHandler(); + gzip.addIncludedPaths("/foo"); + gzip.addIncludedPaths("^/bar.*$"); + + String[] includedPaths = gzip.getIncludedPaths(); + assertThat("Included Paths.size", includedPaths.length, is(2)); + assertThat("Included Paths", Arrays.asList(includedPaths), contains("/foo","^/bar.*$")); + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java index b5af37356e..96a8791382 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java @@ -33,51 +33,81 @@ import java.util.Set; */ public class IncludeExclude { - public interface MatchSet extends Set - { - public boolean matches(ITEM item); - } + private final Set _includes; + private final Predicate _includePredicate; + private final Set _excludes; + private final Predicate _excludePredicate; - @SuppressWarnings("serial") - protected static class ContainsMatchSet extends HashSet implements MatchSet + private static class SetContainsPredicate implements Predicate { + private final Set set; + + public SetContainsPredicate(Set set) + { + this.set = set; + } + @Override - public boolean matches(ITEM item) + public boolean test(ITEM item) { - return contains(item); + return set.contains(item); } } - private final MatchSet _includes; - private final MatchSet _excludes; - /** * Default constructor over {@link HashSet} */ public IncludeExclude() { - _includes = new ContainsMatchSet(); - _excludes = new ContainsMatchSet(); + this(HashSet.class); } /** * Construct an IncludeExclude * @param setClass The type of {@link Set} to using internally - * @param matcher A function to test if a passed ITEM is matched by the passed SET, or null to use {@link Set#contains(Object)} + * @param predicate A predicate function to test if a passed ITEM is matched by the passed SET} */ - public IncludeExclude(Class> setClass) + public > IncludeExclude(Class setClass) { try { _includes = setClass.newInstance(); _excludes = setClass.newInstance(); + + if(_includes instanceof Predicate) { + _includePredicate = (Predicate)_includes; + } else { + _includePredicate = new SetContainsPredicate<>(_includes); + } + + if(_excludes instanceof Predicate) { + _excludePredicate = (Predicate)_excludes; + } else { + _excludePredicate = new SetContainsPredicate<>(_excludes); + } } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } - + + /** + * Construct an IncludeExclude + * + * @param includeSet the Set of items that represent the included space + * @param includePredicate the Predicate for included item testing (null for simple {@link Set#contains(Object)} test) + * @param excludeSet the Set of items that represent the excluded space + * @param excludePredicate the Predicate for excluded item testing (null for simple {@link Set#contains(Object)} test) + */ + public > IncludeExclude(Set includeSet, Predicate includePredicate, Set excludeSet, Predicate excludePredicate) + { + _includes = includeSet; + _includePredicate = includePredicate; + _excludes = excludeSet; + _excludePredicate = excludePredicate; + } + public void include(ITEM element) { _includes.add(element); @@ -102,11 +132,11 @@ public class IncludeExclude public boolean matches(ITEM e) { - if (_includes.size()>0 && !_includes.matches(e)) + if (!_includes.isEmpty() && !_includePredicate.test(e)) return false; - return !_excludes.matches(e); + return !_excludePredicate.test(e); } - + public int size() { return _includes.size()+_excludes.size(); @@ -131,6 +161,6 @@ public class IncludeExclude @Override public String toString() { - return String.format("%s@%x{i=%s,e=%s}",this.getClass().getSimpleName(),hashCode(),_includes,_excludes); + return String.format("%s@%x{i=%s,ip=%s,e=%s,ep=%s}",this.getClass().getSimpleName(),hashCode(),_includes,_includePredicate,_excludes,_excludePredicate); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java new file mode 100644 index 0000000000..33a8eb41a8 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +/** + * Temporary implementation of Java 8's java.util.function.Predicate + *

+ * To be removed for Java 8 only versions of Jetty. + * + * @param the item to test + */ +public interface Predicate +{ + boolean test(ITEM item); +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java index 288e5154f2..3f738d226c 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; *

* Provides the efficient {@link #matches(String)} method to check for a match against all the combined Regex's */ -public class RegexSet extends AbstractSet implements IncludeExclude.MatchSet +public class RegexSet extends AbstractSet implements Predicate { private final Set _patterns=new HashSet(); private final Set _unmodifiable=Collections.unmodifiableSet(_patterns); @@ -95,6 +95,12 @@ public class RegexSet extends AbstractSet implements IncludeExclude.Matc builder.append(")$"); _pattern = Pattern.compile(builder.toString()); } + + @Override + public boolean test(String s) + { + return _pattern!=null && _pattern.matcher(s).matches(); + } public boolean matches(String s) { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java index c2b4d61559..4b7dde734e 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java @@ -20,7 +20,9 @@ package org.eclipse.jetty.util; import org.junit.Test; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class IncludeExcludeTest { @@ -29,8 +31,8 @@ public class IncludeExcludeTest { IncludeExclude ie = new IncludeExclude<>(); - assertEquals(0,ie.size()); - assertEquals(true,ie.matches("foo")); + assertThat("Empty IncludeExclude", ie.size(), is(0)); + assertThat("Matches 'foo'",ie.matches("foo"),is(true)); } @Test @@ -40,7 +42,7 @@ public class IncludeExcludeTest ie.include("foo"); ie.include("bar"); - assertEquals(2,ie.size()); + assertThat("IncludeExclude.size", ie.size(), is(2)); assertEquals(false,ie.matches("")); assertEquals(true,ie.matches("foo")); assertEquals(true,ie.matches("bar")); @@ -146,8 +148,5 @@ public class IncludeExcludeTest assertEquals(true,ie.matches("foobar")); assertEquals(true,ie.matches("Ant")); - } - - } -- cgit v1.2.3 From abe5d090bbbd359b579cdf19dd52cf98b150d0d1 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Mon, 14 Dec 2015 15:03:31 -0500 Subject: [447816] - ServletHolder#compareTo not transitive Updating compareTo to properly order when one _className is null and the other is not. Signed-off-by: Matt Gilman --- .../org/eclipse/jetty/servlet/ServletHolder.java | 18 ++++++- .../eclipse/jetty/servlet/ServletHolderTest.java | 55 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index 1ae25d8a66..1180bfa19b 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -208,15 +208,29 @@ public class ServletHolder extends Holder implements UserIdentity.Scope { if (sh==this) return 0; + if (sh._initOrder<_initOrder) return 1; + if (sh._initOrder>_initOrder) return -1; - int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0; + // consider _className, need to position properly when one is configured but not the other + int c; + if (_className==null && sh._className==null) + c=0; + else if (_className==null) + c=-1; + else if (sh._className==null) + c=1; + else + c=_className.compareTo(sh._className); + + // if _initOrder and _className are the same, consider the _name if (c==0) c=_name.compareTo(sh._name); - return c; + + return c; } /* ------------------------------------------------------------ */ diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java new file mode 100644 index 0000000000..7ec8c17277 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java @@ -0,0 +1,55 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.servlet; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class ServletHolderTest { + + @Test + public void testTransitiveCompareTo() throws Exception + { + // example of jsp-file referenced in web.xml + final ServletHolder one = new ServletHolder(); + one.setInitOrder(-1); + one.setName("Login"); + one.setClassName(null); + + // example of pre-compiled jsp + final ServletHolder two = new ServletHolder(); + two.setInitOrder(-1); + two.setName("org.my.package.jsp.WEB_002dINF.pages.precompiled_002dpage_jsp"); + two.setClassName("org.my.package.jsp.WEB_002dINF.pages.precompiled_002dpage_jsp"); + + // example of servlet referenced in web.xml + final ServletHolder three = new ServletHolder(); + three.setInitOrder(-1); + three.setName("Download"); + three.setClassName("org.my.package.web.DownloadServlet"); + + // verify compareTo transitivity + Assert.assertTrue(one.compareTo(two) < 0); + Assert.assertTrue(two.compareTo(three) < 0); + Assert.assertTrue(one.compareTo(three) < 0); + } +} -- cgit v1.2.3 From 4f9d785b462fb0d491623e2b62867fbbf172daa7 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 16 Dec 2015 10:08:02 -0700 Subject: Revert "482042 - New API, Allow customization of ServletHandler path mapping" This reverts commit 77d4b54082f6789168ea7ef1523e8e1b924bb560. --- .../eclipse/jetty/http/pathmap/PathMappings.java | 20 ++------ .../org/eclipse/jetty/http/pathmap/PathSpec.java | 2 +- .../jetty/http/pathmap/ServletPathSpec.java | 2 +- .../jetty/http/pathmap/PathMappingsTest.java | 57 ++++++++++++++++++---- .../jetty/http/pathmap/RegexPathSpecTest.java | 14 +++--- .../jetty/http/pathmap/ServletPathSpecTest.java | 16 +++--- .../http/pathmap/UriTemplatePathSpecTest.java | 28 +++++------ .../org/eclipse/jetty/servlet/DefaultServlet.java | 7 ++- .../java/org/eclipse/jetty/servlet/Invoker.java | 8 ++- .../org/eclipse/jetty/servlet/ServletHandler.java | 24 ++++----- 10 files changed, 98 insertions(+), 80 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java index 348a4b4284..e0d975ca7d 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java @@ -44,7 +44,6 @@ public class PathMappings implements Iterable>, Dumpable private static final Logger LOG = Log.getLogger(PathMappings.class); private List> mappings = new ArrayList>(); private MappedResource defaultResource = null; - private MappedResource rootResource = null; @Override public String dump() @@ -106,11 +105,6 @@ public class PathMappings implements Iterable>, Dumpable public MappedResource getMatch(String path) { - if (path.equals("/") && rootResource != null) - { - return rootResource; - } - int len = mappings.size(); for (int i = 0; i < len; i++) { @@ -129,22 +123,14 @@ public class PathMappings implements Iterable>, Dumpable return mappings.iterator(); } - @SuppressWarnings("incomplete-switch") public void put(PathSpec pathSpec, E resource) { MappedResource entry = new MappedResource<>(pathSpec,resource); - switch (pathSpec.group) + if (pathSpec.group == PathSpecGroup.DEFAULT) { - case DEFAULT: - defaultResource = entry; - break; - case ROOT: - rootResource = entry; - break; + defaultResource = entry; } - - // TODO: add warning when replacing an existing pathspec? - + // TODO: warning on replacement of existing mapping? mappings.add(entry); if (LOG.isDebugEnabled()) LOG.debug("Added {} to {}",entry,this); diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java index b9878472bb..122211a558 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java @@ -119,7 +119,7 @@ public abstract class PathSpec implements Comparable * * @return the as-provided path spec */ - public String getDeclaration() + public String getPathSpec() { return pathSpec; } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java index 44f8a5fae2..55399748e2 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java @@ -245,7 +245,7 @@ public class ServletPathSpec extends PathSpec case EXACT: return pathSpec.equals(path); case PREFIX_GLOB: - return isWildcardMatch(path); + return (!"/".equals(path) && isWildcardMatch(path)); case SUFFIX_GLOB: return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1); case ROOT: diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java index 36df42e553..06cd14bcce 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java @@ -77,22 +77,61 @@ public class PathMappingsTest assertMatch(p,"/animal/fish/trout/cam","animalCam"); assertMatch(p,"/entrance/cam","entranceCam"); } - + /** - * Test the match order rules imposed by the Servlet API (default vs any) + * Test the match order rules imposed by the Servlet API. + *

+ *

    + *
  • Exact match
  • + *
  • Longest prefix match
  • + *
  • Longest suffix match
  • + *
  • default
  • + *
*/ @Test - public void testServletMatchDefault() + public void testServletMatchOrder() { PathMappings p = new PathMappings<>(); - p.put(new ServletPathSpec("/"),"default"); - p.put(new ServletPathSpec("/*"),"any"); + p.put(new ServletPathSpec("/abs/path"),"abspath"); // 1 + p.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2 + p.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3 + p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4 + p.put(new ServletPathSpec("/animal/*"),"animals"); // 5 + p.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6 + p.put(new ServletPathSpec("*.gz"),"gzipped"); // 7 + p.put(new ServletPathSpec("/"),"default"); // 8 + // 9 was the old Jetty ":" spec delimited case (no longer valid) + p.put(new ServletPathSpec(""),"root"); // 10 + p.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11 - assertMatch(p,"/abs/path","any"); - assertMatch(p,"/abs/path/xxx","any"); - assertMatch(p,"/animal/bird/eagle/bald","any"); - assertMatch(p,"/","any"); + // dumpMappings(p); + + // From old PathMapTest + assertMatch(p,"/abs/path","abspath"); + assertMatch(p,"/abs/path/xxx","default"); + assertMatch(p,"/abs/pith","default"); + assertMatch(p,"/abs/path/longer","longpath"); + assertMatch(p,"/abs/path/","default"); + assertMatch(p,"/abs/path/foo","default"); + assertMatch(p,"/animal/bird/eagle/bald","birds"); + assertMatch(p,"/animal/fish/shark/hammerhead","fishes"); + assertMatch(p,"/animal/insect/ladybug","animals"); + assertMatch(p,"/animal","animals"); + assertMatch(p,"/animal/","animals"); + assertMatch(p,"/animal/other","animals"); + assertMatch(p,"/animal/*","animals"); + assertMatch(p,"/downloads/distribution.tar.gz","tarball"); + assertMatch(p,"/downloads/script.gz","gzipped"); + assertMatch(p,"/animal/arhive.gz","animals"); + assertMatch(p,"/Other/path","default"); + assertMatch(p,"/\u20ACuro/path","money"); + assertMatch(p,"/","root"); + + // Extra tests + assertMatch(p,"/downloads/readme.txt","default"); + assertMatch(p,"/downloads/logs.tgz","default"); + assertMatch(p,"/main.css","default"); } /** diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java index f78cd75040..a44c4dca50 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java @@ -28,13 +28,13 @@ public class RegexPathSpecTest { public static void assertMatches(PathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); assertThat(msg,spec.matches(path),is(true)); } public static void assertNotMatches(PathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -42,7 +42,7 @@ public class RegexPathSpecTest public void testExactSpec() { RegexPathSpec spec = new RegexPathSpec("^/a$"); - assertEquals("Spec.pathSpec","^/a$",spec.getDeclaration()); + assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec()); assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group); @@ -57,7 +57,7 @@ public class RegexPathSpecTest public void testMiddleSpec() { RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$"); - assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getDeclaration()); + assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec()); assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); @@ -78,7 +78,7 @@ public class RegexPathSpecTest public void testMiddleSpecNoGrouping() { RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$"); - assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getDeclaration()); + assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec()); assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); @@ -99,7 +99,7 @@ public class RegexPathSpecTest public void testPrefixSpec() { RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$"); - assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getDeclaration()); + assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec()); assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group); @@ -117,7 +117,7 @@ public class RegexPathSpecTest public void testSuffixSpec() { RegexPathSpec spec = new RegexPathSpec("^(.*).do$"); - assertEquals("Spec.pathSpec","^(.*).do$",spec.getDeclaration()); + assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec()); assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",0,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java index fb8e65b25a..7252a9c1b4 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java @@ -43,13 +43,13 @@ public class ServletPathSpecTest private void assertMatches(ServletPathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); assertThat(msg,spec.matches(path),is(true)); } private void assertNotMatches(ServletPathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -87,7 +87,7 @@ public class ServletPathSpecTest public void testDefaultPathSpec() { ServletPathSpec spec = new ServletPathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/",spec.getPathSpec()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -95,7 +95,7 @@ public class ServletPathSpecTest public void testExactPathSpec() { ServletPathSpec spec = new ServletPathSpec("/abs/path"); - assertEquals("Spec.pathSpec","/abs/path",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertMatches(spec,"/abs/path"); @@ -125,7 +125,7 @@ public class ServletPathSpecTest public void testNullPathSpec() { ServletPathSpec spec = new ServletPathSpec(null); - assertEquals("Spec.pathSpec","",spec.getDeclaration()); + assertEquals("Spec.pathSpec","",spec.getPathSpec()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -133,7 +133,7 @@ public class ServletPathSpecTest public void testRootPathSpec() { ServletPathSpec spec = new ServletPathSpec(""); - assertEquals("Spec.pathSpec","",spec.getDeclaration()); + assertEquals("Spec.pathSpec","",spec.getPathSpec()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -154,7 +154,7 @@ public class ServletPathSpecTest public void testPrefixPathSpec() { ServletPathSpec spec = new ServletPathSpec("/downloads/*"); - assertEquals("Spec.pathSpec","/downloads/*",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertMatches(spec,"/downloads/logo.jpg"); @@ -173,7 +173,7 @@ public class ServletPathSpecTest public void testSuffixPathSpec() { ServletPathSpec spec = new ServletPathSpec("*.gz"); - assertEquals("Spec.pathSpec","*.gz",spec.getDeclaration()); + assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec()); assertEquals("Spec.pathDepth",0,spec.getPathDepth()); assertMatches(spec,"/downloads/distribution.tar.gz"); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java index 50dcab923c..7908344e09 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java @@ -34,7 +34,7 @@ public class UriTemplatePathSpecTest { private void assertDetectedVars(UriTemplatePathSpec spec, String... expectedVars) { - String prefix = String.format("Spec(\"%s\")",spec.getDeclaration()); + String prefix = String.format("Spec(\"%s\")",spec.getPathSpec()); assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount()); assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length); for (int i = 0; i < expectedVars.length; i++) @@ -45,13 +45,13 @@ public class UriTemplatePathSpecTest private void assertMatches(PathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); assertThat(msg,spec.matches(path),is(true)); } private void assertNotMatches(PathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -59,7 +59,7 @@ public class UriTemplatePathSpecTest public void testDefaultPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/",spec.getPathSpec()); assertEquals("Spec.pattern","^/$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -72,7 +72,7 @@ public class UriTemplatePathSpecTest public void testExactOnePathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a"); - assertEquals("Spec.pathSpec","/a",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/a",spec.getPathSpec()); assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -90,7 +90,7 @@ public class UriTemplatePathSpecTest public void testExactPathSpec_TestWebapp() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/deep.thought/"); - assertEquals("Spec.pathSpec","/deep.thought/",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/deep.thought/",spec.getPathSpec()); assertEquals("Spec.pattern","^/deep\\.thought/$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -106,7 +106,7 @@ public class UriTemplatePathSpecTest public void testExactTwoPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/b"); - assertEquals("Spec.pathSpec","/a/b",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec()); assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -125,7 +125,7 @@ public class UriTemplatePathSpecTest public void testMiddleVarPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var}/c"); - assertEquals("Spec.pathSpec","/a/{var}/c",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec()); assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -149,7 +149,7 @@ public class UriTemplatePathSpecTest public void testOneVarPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{foo}"); - assertEquals("Spec.pathSpec","/a/{foo}",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec()); assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); @@ -170,7 +170,7 @@ public class UriTemplatePathSpecTest public void testOneVarSuffixPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var}/b/c"); - assertEquals("Spec.pathSpec","/{var}/b/c",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec()); assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup()); @@ -194,7 +194,7 @@ public class UriTemplatePathSpecTest public void testTwoVarComplexInnerPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/c/{var2}/e"); - assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec()); assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",5,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -217,7 +217,7 @@ public class UriTemplatePathSpecTest public void testTwoVarComplexOuterPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}/b/{var2}/{var3}"); - assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec()); assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",4,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -241,7 +241,7 @@ public class UriTemplatePathSpecTest public void testTwoVarPrefixPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/{var2}"); - assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec()); assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); @@ -264,7 +264,7 @@ public class UriTemplatePathSpecTest public void testVarOnlyPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}"); - assertEquals("Spec.pathSpec","/{var1}",spec.getDeclaration()); + assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec()); assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 9ed32be55b..56df44c2de 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -47,7 +47,6 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PathMap.MappedEntry; -import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.io.WriterOutputStream; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.InclusiveByteRange; @@ -692,9 +691,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null) { - MappedResource entry=_servletHandler.getHolderEntry(welcome_in_context); - if (entry!=null && entry.getResource()!=_defaultHolder && - (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context)))) + MappedEntry entry=_servletHandler.getHolderEntry(welcome_in_context); + if (entry!=null && entry.getValue()!=_defaultHolder && + (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context)))) welcome_servlet=welcome_in_context; } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java index 0edd706a70..999a086346 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java @@ -32,8 +32,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.PathMap.MappedEntry; -import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpChannel; @@ -74,7 +72,7 @@ public class Invoker extends HttpServlet private ContextHandler _contextHandler; private ServletHandler _servletHandler; - private MappedResource _invokerEntry; + private Map.Entry _invokerEntry; private Map _parameters; private boolean _nonContextServlets; private boolean _verbose; @@ -173,12 +171,12 @@ public class Invoker extends HttpServlet // Check for existing mapping (avoid threaded race). String path=URIUtil.addPaths(servlet_path,servlet); - MappedResource entry = _servletHandler.getHolderEntry(path); + Map.Entry entry = _servletHandler.getHolderEntry(path); if (entry!=null && !entry.equals(_invokerEntry)) { // Use the holder - holder=(ServletHolder)entry.getResource(); + holder=(ServletHolder)entry.getValue(); } else { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index e767851454..c3b152c6c3 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -52,10 +52,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; -import org.eclipse.jetty.http.pathmap.MappedResource; -import org.eclipse.jetty.http.pathmap.PathMappings; -import org.eclipse.jetty.http.pathmap.PathSpec; -import org.eclipse.jetty.http.pathmap.ServletPathSpec; +import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.security.IdentityService; @@ -124,8 +121,7 @@ public class ServletHandler extends ScopedHandler private MultiMap _filterNameMappings; private final Map _servletNameMap=new HashMap<>(); - // private PathMap _servletPathMap; - private PathMappings _servletPathMap; + private PathMap _servletPathMap; private ListenerHolder[] _listeners=new ListenerHolder[0]; @@ -387,7 +383,7 @@ public class ServletHandler extends ScopedHandler * @param pathInContext Path within _context. * @return PathMap Entries pathspec to ServletHolder */ - public MappedResource getHolderEntry(String pathInContext) + public PathMap.MappedEntry getHolderEntry(String pathInContext) { if (_servletPathMap==null) return null; @@ -474,14 +470,14 @@ public class ServletHandler extends ScopedHandler if (target.startsWith("/")) { // Look for the servlet by path - MappedResource entry=getHolderEntry(target); + PathMap.MappedEntry entry=getHolderEntry(target); if (entry!=null) { - PathSpec pathSpec = entry.getPathSpec(); - servlet_holder=entry.getResource(); + servlet_holder=entry.getValue(); - String servlet_path=pathSpec.getPathMatch(target); - String path_info=pathSpec.getPathInfo(target); + String servlet_path_spec= entry.getKey(); + String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target); + String path_info=PathMap.pathInfo(servlet_path_spec,target); if (DispatcherType.INCLUDE.equals(type)) { @@ -1436,7 +1432,7 @@ public class ServletHandler extends ScopedHandler } else { - PathMappings pm = new PathMappings<>(); + PathMap pm = new PathMap<>(); Map servletPathMappings = new HashMap(); //create a map of paths to set of ServletMappings that define that mapping @@ -1499,7 +1495,7 @@ public class ServletHandler extends ScopedHandler if (LOG.isDebugEnabled()) LOG.debug("Chose path={} mapped to servlet={} from default={}", pathSpec, finalMapping.getServletName(), finalMapping.isDefault()); servletPathMappings.put(pathSpec, finalMapping); - pm.put(new ServletPathSpec(pathSpec),_servletNameMap.get(finalMapping.getServletName())); + pm.put(pathSpec,_servletNameMap.get(finalMapping.getServletName())); } _servletPathMap=pm; -- cgit v1.2.3 From e6901b26467d80d9101c74f3d26cfed89f0453d7 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 16 Dec 2015 11:06:17 -0700 Subject: 484349 - Promote WebSocket PathMappings / PathSpec to Jetty Http + More testing, more improvements --- .../eclipse/jetty/http/pathmap/PathMappings.java | 20 ++++++-- .../org/eclipse/jetty/http/pathmap/PathSpec.java | 2 +- .../jetty/http/pathmap/ServletPathSpec.java | 2 +- .../jetty/http/pathmap/PathMappingsTest.java | 57 ++++------------------ .../jetty/http/pathmap/RegexPathSpecTest.java | 14 +++--- .../jetty/http/pathmap/ServletPathSpecTest.java | 16 +++--- .../http/pathmap/UriTemplatePathSpecTest.java | 28 +++++------ 7 files changed, 57 insertions(+), 82 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java index e0d975ca7d..348a4b4284 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java @@ -44,6 +44,7 @@ public class PathMappings implements Iterable>, Dumpable private static final Logger LOG = Log.getLogger(PathMappings.class); private List> mappings = new ArrayList>(); private MappedResource defaultResource = null; + private MappedResource rootResource = null; @Override public String dump() @@ -105,6 +106,11 @@ public class PathMappings implements Iterable>, Dumpable public MappedResource getMatch(String path) { + if (path.equals("/") && rootResource != null) + { + return rootResource; + } + int len = mappings.size(); for (int i = 0; i < len; i++) { @@ -123,14 +129,22 @@ public class PathMappings implements Iterable>, Dumpable return mappings.iterator(); } + @SuppressWarnings("incomplete-switch") public void put(PathSpec pathSpec, E resource) { MappedResource entry = new MappedResource<>(pathSpec,resource); - if (pathSpec.group == PathSpecGroup.DEFAULT) + switch (pathSpec.group) { - defaultResource = entry; + case DEFAULT: + defaultResource = entry; + break; + case ROOT: + rootResource = entry; + break; } - // TODO: warning on replacement of existing mapping? + + // TODO: add warning when replacing an existing pathspec? + mappings.add(entry); if (LOG.isDebugEnabled()) LOG.debug("Added {} to {}",entry,this); diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java index 122211a558..b9878472bb 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java @@ -119,7 +119,7 @@ public abstract class PathSpec implements Comparable * * @return the as-provided path spec */ - public String getPathSpec() + public String getDeclaration() { return pathSpec; } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java index 55399748e2..44f8a5fae2 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java @@ -245,7 +245,7 @@ public class ServletPathSpec extends PathSpec case EXACT: return pathSpec.equals(path); case PREFIX_GLOB: - return (!"/".equals(path) && isWildcardMatch(path)); + return isWildcardMatch(path); case SUFFIX_GLOB: return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1); case ROOT: diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java index 06cd14bcce..36df42e553 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java @@ -77,61 +77,22 @@ public class PathMappingsTest assertMatch(p,"/animal/fish/trout/cam","animalCam"); assertMatch(p,"/entrance/cam","entranceCam"); } - + /** - * Test the match order rules imposed by the Servlet API. - *

- *

    - *
  • Exact match
  • - *
  • Longest prefix match
  • - *
  • Longest suffix match
  • - *
  • default
  • - *
+ * Test the match order rules imposed by the Servlet API (default vs any) */ @Test - public void testServletMatchOrder() + public void testServletMatchDefault() { PathMappings p = new PathMappings<>(); - p.put(new ServletPathSpec("/abs/path"),"abspath"); // 1 - p.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2 - p.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3 - p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4 - p.put(new ServletPathSpec("/animal/*"),"animals"); // 5 - p.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6 - p.put(new ServletPathSpec("*.gz"),"gzipped"); // 7 - p.put(new ServletPathSpec("/"),"default"); // 8 - // 9 was the old Jetty ":" spec delimited case (no longer valid) - p.put(new ServletPathSpec(""),"root"); // 10 - p.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11 - - // dumpMappings(p); + p.put(new ServletPathSpec("/"),"default"); + p.put(new ServletPathSpec("/*"),"any"); - // From old PathMapTest - assertMatch(p,"/abs/path","abspath"); - assertMatch(p,"/abs/path/xxx","default"); - assertMatch(p,"/abs/pith","default"); - assertMatch(p,"/abs/path/longer","longpath"); - assertMatch(p,"/abs/path/","default"); - assertMatch(p,"/abs/path/foo","default"); - assertMatch(p,"/animal/bird/eagle/bald","birds"); - assertMatch(p,"/animal/fish/shark/hammerhead","fishes"); - assertMatch(p,"/animal/insect/ladybug","animals"); - assertMatch(p,"/animal","animals"); - assertMatch(p,"/animal/","animals"); - assertMatch(p,"/animal/other","animals"); - assertMatch(p,"/animal/*","animals"); - assertMatch(p,"/downloads/distribution.tar.gz","tarball"); - assertMatch(p,"/downloads/script.gz","gzipped"); - assertMatch(p,"/animal/arhive.gz","animals"); - assertMatch(p,"/Other/path","default"); - assertMatch(p,"/\u20ACuro/path","money"); - assertMatch(p,"/","root"); - - // Extra tests - assertMatch(p,"/downloads/readme.txt","default"); - assertMatch(p,"/downloads/logs.tgz","default"); - assertMatch(p,"/main.css","default"); + assertMatch(p,"/abs/path","any"); + assertMatch(p,"/abs/path/xxx","any"); + assertMatch(p,"/animal/bird/eagle/bald","any"); + assertMatch(p,"/","any"); } /** diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java index a44c4dca50..f78cd75040 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java @@ -28,13 +28,13 @@ public class RegexPathSpecTest { public static void assertMatches(PathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(true)); } public static void assertNotMatches(PathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -42,7 +42,7 @@ public class RegexPathSpecTest public void testExactSpec() { RegexPathSpec spec = new RegexPathSpec("^/a$"); - assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/a$",spec.getDeclaration()); assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group); @@ -57,7 +57,7 @@ public class RegexPathSpecTest public void testMiddleSpec() { RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$"); - assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getDeclaration()); assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); @@ -78,7 +78,7 @@ public class RegexPathSpecTest public void testMiddleSpecNoGrouping() { RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$"); - assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getDeclaration()); assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); @@ -99,7 +99,7 @@ public class RegexPathSpecTest public void testPrefixSpec() { RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$"); - assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group); @@ -117,7 +117,7 @@ public class RegexPathSpecTest public void testSuffixSpec() { RegexPathSpec spec = new RegexPathSpec("^(.*).do$"); - assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec()); + assertEquals("Spec.pathSpec","^(.*).do$",spec.getDeclaration()); assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",0,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java index 7252a9c1b4..fb8e65b25a 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java @@ -43,13 +43,13 @@ public class ServletPathSpecTest private void assertMatches(ServletPathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(true)); } private void assertNotMatches(ServletPathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -87,7 +87,7 @@ public class ServletPathSpecTest public void testDefaultPathSpec() { ServletPathSpec spec = new ServletPathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/",spec.getDeclaration()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -95,7 +95,7 @@ public class ServletPathSpecTest public void testExactPathSpec() { ServletPathSpec spec = new ServletPathSpec("/abs/path"); - assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/abs/path",spec.getDeclaration()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertMatches(spec,"/abs/path"); @@ -125,7 +125,7 @@ public class ServletPathSpecTest public void testNullPathSpec() { ServletPathSpec spec = new ServletPathSpec(null); - assertEquals("Spec.pathSpec","",spec.getPathSpec()); + assertEquals("Spec.pathSpec","",spec.getDeclaration()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -133,7 +133,7 @@ public class ServletPathSpecTest public void testRootPathSpec() { ServletPathSpec spec = new ServletPathSpec(""); - assertEquals("Spec.pathSpec","",spec.getPathSpec()); + assertEquals("Spec.pathSpec","",spec.getDeclaration()); assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); } @@ -154,7 +154,7 @@ public class ServletPathSpecTest public void testPrefixPathSpec() { ServletPathSpec spec = new ServletPathSpec("/downloads/*"); - assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/downloads/*",spec.getDeclaration()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertMatches(spec,"/downloads/logo.jpg"); @@ -173,7 +173,7 @@ public class ServletPathSpecTest public void testSuffixPathSpec() { ServletPathSpec spec = new ServletPathSpec("*.gz"); - assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec()); + assertEquals("Spec.pathSpec","*.gz",spec.getDeclaration()); assertEquals("Spec.pathDepth",0,spec.getPathDepth()); assertMatches(spec,"/downloads/distribution.tar.gz"); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java index 7908344e09..50dcab923c 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java @@ -34,7 +34,7 @@ public class UriTemplatePathSpecTest { private void assertDetectedVars(UriTemplatePathSpec spec, String... expectedVars) { - String prefix = String.format("Spec(\"%s\")",spec.getPathSpec()); + String prefix = String.format("Spec(\"%s\")",spec.getDeclaration()); assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount()); assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length); for (int i = 0; i < expectedVars.length; i++) @@ -45,13 +45,13 @@ public class UriTemplatePathSpecTest private void assertMatches(PathSpec spec, String path) { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(true)); } private void assertNotMatches(PathSpec spec, String path) { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); + String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getDeclaration(),path); assertThat(msg,spec.matches(path),is(false)); } @@ -59,7 +59,7 @@ public class UriTemplatePathSpecTest public void testDefaultPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/",spec.getDeclaration()); assertEquals("Spec.pattern","^/$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -72,7 +72,7 @@ public class UriTemplatePathSpecTest public void testExactOnePathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a"); - assertEquals("Spec.pathSpec","/a",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a",spec.getDeclaration()); assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -90,7 +90,7 @@ public class UriTemplatePathSpecTest public void testExactPathSpec_TestWebapp() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/deep.thought/"); - assertEquals("Spec.pathSpec","/deep.thought/",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/deep.thought/",spec.getDeclaration()); assertEquals("Spec.pattern","^/deep\\.thought/$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -106,7 +106,7 @@ public class UriTemplatePathSpecTest public void testExactTwoPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/b"); - assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/b",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); @@ -125,7 +125,7 @@ public class UriTemplatePathSpecTest public void testMiddleVarPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var}/c"); - assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{var}/c",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -149,7 +149,7 @@ public class UriTemplatePathSpecTest public void testOneVarPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{foo}"); - assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{foo}",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",2,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); @@ -170,7 +170,7 @@ public class UriTemplatePathSpecTest public void testOneVarSuffixPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var}/b/c"); - assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/{var}/b/c",spec.getDeclaration()); assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup()); @@ -194,7 +194,7 @@ public class UriTemplatePathSpecTest public void testTwoVarComplexInnerPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/c/{var2}/e"); - assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",5,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -217,7 +217,7 @@ public class UriTemplatePathSpecTest public void testTwoVarComplexOuterPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}/b/{var2}/{var3}"); - assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getDeclaration()); assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",4,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); @@ -241,7 +241,7 @@ public class UriTemplatePathSpecTest public void testTwoVarPrefixPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/{var2}"); - assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getDeclaration()); assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",3,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); @@ -264,7 +264,7 @@ public class UriTemplatePathSpecTest public void testVarOnlyPathSpec() { UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}"); - assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec()); + assertEquals("Spec.pathSpec","/{var1}",spec.getDeclaration()); assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern()); assertEquals("Spec.pathDepth",1,spec.getPathDepth()); assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); -- cgit v1.2.3 From 7c5bec1b48db95854965c13c203a0e23373c88d6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 17 Dec 2015 10:15:57 -0700 Subject: Using Java 8 Predicate Function + Removing jetty-util (java 7) holdover Predicate.java + Converting use to (java 8) java.util.function.Predicate --- .../main/java/org/eclipse/jetty/http/PathMap.java | 4 +-- .../eclipse/jetty/http/pathmap/PathSpecSet.java | 3 +-- .../org/eclipse/jetty/util/IncludeExclude.java | 1 + .../java/org/eclipse/jetty/util/Predicate.java | 31 ---------------------- .../main/java/org/eclipse/jetty/util/RegexSet.java | 1 + 5 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 41cc11f1d8..457679434c 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -26,11 +26,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; -import java.util.function.BiFunction; +import java.util.function.Predicate; import org.eclipse.jetty.util.ArrayTernaryTrie; -import org.eclipse.jetty.util.IncludeExclude; -import org.eclipse.jetty.util.Predicate; import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.URIUtil; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java index 330b55ab25..75fc421403 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java @@ -24,8 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; - -import org.eclipse.jetty.util.Predicate; +import java.util.function.Predicate; /** * A Set of PathSpec strings. diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java index 96a8791382..39d80e2291 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.util; import java.util.HashSet; import java.util.Set; +import java.util.function.Predicate; /** Utility class to maintain a set of inclusions and exclusions. diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java deleted file mode 100644 index 33a8eb41a8..0000000000 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java +++ /dev/null @@ -1,31 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.util; - -/** - * Temporary implementation of Java 8's java.util.function.Predicate - *

- * To be removed for Java 8 only versions of Jetty. - * - * @param the item to test - */ -public interface Predicate -{ - boolean test(ITEM item); -} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java index 3f738d226c..ca5a0305d8 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.function.Predicate; import java.util.regex.Pattern; /** -- cgit v1.2.3 From 3bec195d080a32f8410816b605136da9fc9ff391 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 17 Dec 2015 11:42:27 -0700 Subject: 484440 - Swap WebSocket PathMappings for new jetty-http PathMappings + Deprecated jetty-server ServletPathSpec and RegexPathSpec + Moved all other code to using new jetty-http versions --- .../main/java/org/eclipse/jetty/http/PathMap.java | 4 +- .../jetty/websocket/jsr356/server/JsrCreator.java | 8 +- .../jsr356/server/JsrHandshakeRequest.java | 2 +- .../server/PathParamServerEndpointConfig.java | 4 +- .../websocket/jsr356/server/ServerContainer.java | 4 +- .../jsr356/server/pathmap/WebSocketPathSpec.java | 347 --------------------- .../websocket/jsr356/server/DummyCreator.java | 4 +- .../jsr356/server/pathmap/PathMappingsTest.java | 110 ------- .../pathmap/WebSocketPathSpecBadSpecsTest.java | 87 ------ .../server/pathmap/WebSocketPathSpecTest.java | 286 ----------------- .../websocket/server/MappedWebSocketCreator.java | 4 +- .../websocket/server/WebSocketUpgradeFilter.java | 6 +- .../server/WebSocketUpgradeHandlerWrapper.java | 6 +- .../websocket/server/pathmap/PathMappings.java | 190 ----------- .../jetty/websocket/server/pathmap/PathSpec.java | 167 ---------- .../websocket/server/pathmap/PathSpecGroup.java | 82 ----- .../websocket/server/pathmap/RegexPathSpec.java | 157 +--------- .../websocket/server/pathmap/ServletPathSpec.java | 272 +--------------- .../server/pathmap/PathMappingsBenchmarkTest.java | 226 -------------- .../websocket/server/pathmap/PathMappingsTest.java | 119 ------- .../server/pathmap/RegexPathSpecTest.java | 135 -------- .../server/pathmap/ServletPathSpecTest.java | 188 ----------- 22 files changed, 34 insertions(+), 2374 deletions(-) delete mode 100644 jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java delete mode 100644 jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java delete mode 100644 jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java delete mode 100644 jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java delete mode 100644 jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java delete mode 100644 jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java delete mode 100644 jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java delete mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java delete mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java delete mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java delete mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 457679434c..438eada2e0 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -71,8 +71,10 @@ import org.eclipse.jetty.util.URIUtil; * This class is not synchronized. If concurrent modifications are * possible then it should be synchronized at a higher level. * - * @param the Map.Entry value type + * @param the Map.Entry value type + * @deprecated replaced with {@link org.eclipse.jetty.http.pathmap.PathMappings} (this class will be removed in Jetty 10) */ +@Deprecated public class PathMap extends HashMap { /* ------------------------------------------------------------ */ diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java index 60016f4d1d..6954cb01d2 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java @@ -29,6 +29,8 @@ import javax.websocket.Extension.Parameter; import javax.websocket.server.ServerEndpointConfig; import javax.websocket.server.ServerEndpointConfig.Configurator; +import org.eclipse.jetty.http.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -37,8 +39,6 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.websocket.jsr356.JsrExtension; import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; -import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; @@ -148,10 +148,10 @@ public class JsrCreator implements WebSocketCreator // Do not decorate here (let the Connection and Session start first) // This will allow CDI to see Session for injection into Endpoint classes. PathSpec pathSpec = hsreq.getRequestPathSpec(); - if (pathSpec instanceof WebSocketPathSpec) + if (pathSpec instanceof UriTemplatePathSpec) { // We have a PathParam path spec - WebSocketPathSpec wspathSpec = (WebSocketPathSpec)pathSpec; + UriTemplatePathSpec wspathSpec = (UriTemplatePathSpec)pathSpec; String requestPath = req.getRequestPath(); // Wrap the config with the path spec information config = new PathParamServerEndpointConfig(containerScope,config,wspathSpec,requestPath); diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java index 6db246d801..023b676531 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java @@ -25,7 +25,7 @@ import java.util.Map; import javax.websocket.server.HandshakeRequest; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; public class JsrHandshakeRequest implements HandshakeRequest diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java index d709a0039a..a4bcb3b8e1 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java @@ -23,8 +23,8 @@ import java.util.Map; import javax.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; -import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec; /** * Wrapper for a {@link ServerEndpointConfig} where there PathParm information from the incoming request. @@ -33,7 +33,7 @@ public class PathParamServerEndpointConfig extends BasicServerEndpointConfig imp { private final Map pathParamMap; - public PathParamServerEndpointConfig(WebSocketContainerScope containerScope, ServerEndpointConfig config, WebSocketPathSpec pathSpec, String requestPath) + public PathParamServerEndpointConfig(WebSocketContainerScope containerScope, ServerEndpointConfig config, UriTemplatePathSpec pathSpec, String requestPath) { super(containerScope, config); diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java index 7fdba0426d..dba7a88567 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java @@ -27,6 +27,7 @@ import javax.websocket.Endpoint; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.common.events.EventDriverFactory; @@ -35,7 +36,6 @@ import org.eclipse.jetty.websocket.jsr356.JsrSessionFactory; import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner; import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; -import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec; import org.eclipse.jetty.websocket.server.MappedWebSocketCreator; import org.eclipse.jetty.websocket.server.WebSocketServerFactory; @@ -99,7 +99,7 @@ public class ServerContainer extends ClientContainer implements javax.websocket. private void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException { JsrCreator creator = new JsrCreator(this,metadata,webSocketServerFactory.getExtensionFactory()); - mappedCreator.addMapping(new WebSocketPathSpec(metadata.getPath()),creator); + mappedCreator.addMapping(new UriTemplatePathSpec(metadata.getPath()),creator); } @Override diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java deleted file mode 100644 index 014466a60e..0000000000 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java +++ /dev/null @@ -1,347 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.jsr356.server.pathmap; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.websocket.server.PathParam; -import javax.websocket.server.ServerEndpoint; - -import org.eclipse.jetty.util.TypeUtil; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup; -import org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec; - -/** - * PathSpec for WebSocket @{@link ServerEndpoint} declarations with support for URI templates and @{@link PathParam} annotations - * - * @see javax.websocket spec (JSR-356) Section 3.1.1 URI Mapping - * @see URI Templates (Level 1) - */ -public class WebSocketPathSpec extends RegexPathSpec -{ - private static final Logger LOG = Log.getLogger(WebSocketPathSpec.class); - - private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}"); - /** Reserved Symbols in URI Template variable */ - private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims - "!$&'()*+,;="; // sub-delims - /** Allowed Symboles in a URI Template variable */ - private static final String VARIABLE_SYMBOLS="-._"; - private static final Set FORBIDDEN_SEGMENTS; - - static - { - FORBIDDEN_SEGMENTS = new HashSet<>(); - FORBIDDEN_SEGMENTS.add("/./"); - FORBIDDEN_SEGMENTS.add("/../"); - FORBIDDEN_SEGMENTS.add("//"); - } - - private String variables[]; - - public WebSocketPathSpec(String pathParamSpec) - { - super(); - Objects.requireNonNull(pathParamSpec,"Path Param Spec cannot be null"); - - if ("".equals(pathParamSpec) || "/".equals(pathParamSpec)) - { - super.pathSpec = "/"; - super.pattern = Pattern.compile("^/$"); - super.pathDepth = 1; - this.specLength = 1; - this.variables = new String[0]; - this.group = PathSpecGroup.EXACT; - return; - } - - if (pathParamSpec.charAt(0) != '/') - { - // path specs must start with '/' - StringBuilder err = new StringBuilder(); - err.append("Syntax Error: path spec \""); - err.append(pathParamSpec); - err.append("\" must start with '/'"); - throw new IllegalArgumentException(err.toString()); - } - - for (String forbidden : FORBIDDEN_SEGMENTS) - { - if (pathParamSpec.contains(forbidden)) - { - StringBuilder err = new StringBuilder(); - err.append("Syntax Error: segment "); - err.append(forbidden); - err.append(" is forbidden in path spec: "); - err.append(pathParamSpec); - throw new IllegalArgumentException(err.toString()); - } - } - - this.pathSpec = pathParamSpec; - - StringBuilder regex = new StringBuilder(); - regex.append('^'); - - List varNames = new ArrayList<>(); - // split up into path segments (ignoring the first slash that will always be empty) - String segments[] = pathParamSpec.substring(1).split("/"); - char segmentSignature[] = new char[segments.length]; - this.pathDepth = segments.length; - for (int i = 0; i < segments.length; i++) - { - String segment = segments[i]; - Matcher mat = VARIABLE_PATTERN.matcher(segment); - - if (mat.matches()) - { - // entire path segment is a variable. - String variable = mat.group(1); - if (varNames.contains(variable)) - { - // duplicate variable names - StringBuilder err = new StringBuilder(); - err.append("Syntax Error: variable "); - err.append(variable); - err.append(" is duplicated in path spec: "); - err.append(pathParamSpec); - throw new IllegalArgumentException(err.toString()); - } - - assertIsValidVariableLiteral(variable); - - segmentSignature[i] = 'v'; // variable - // valid variable name - varNames.add(variable); - // build regex - regex.append("/([^/]+)"); - } - else if (mat.find(0)) - { - // variable exists as partial segment - StringBuilder err = new StringBuilder(); - err.append("Syntax Error: variable "); - err.append(mat.group()); - err.append(" must exist as entire path segment: "); - err.append(pathParamSpec); - throw new IllegalArgumentException(err.toString()); - } - else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0)) - { - // variable is split with a path separator - StringBuilder err = new StringBuilder(); - err.append("Syntax Error: invalid path segment /"); - err.append(segment); - err.append("/ variable declaration incomplete: "); - err.append(pathParamSpec); - throw new IllegalArgumentException(err.toString()); - } - else if (segment.indexOf('*') >= 0) - { - // glob segment - StringBuilder err = new StringBuilder(); - err.append("Syntax Error: path segment /"); - err.append(segment); - err.append("/ contains a wildcard symbol (not supported by javax.websocket): "); - err.append(pathParamSpec); - throw new IllegalArgumentException(err.toString()); - } - else - { - // valid path segment - segmentSignature[i] = 'e'; // exact - // build regex - regex.append('/'); - // escape regex special characters - for (char c : segment.toCharArray()) - { - if ((c == '.') || (c == '[') || (c == ']') || (c == '\\')) - { - regex.append('\\'); - } - regex.append(c); - } - } - } - - // Handle trailing slash (which is not picked up during split) - if(pathParamSpec.charAt(pathParamSpec.length()-1) == '/') - { - regex.append('/'); - } - - regex.append('$'); - - this.pattern = Pattern.compile(regex.toString()); - - int varcount = varNames.size(); - this.variables = varNames.toArray(new String[varcount]); - - // Convert signature to group - String sig = String.valueOf(segmentSignature); - - if (Pattern.matches("^e*$",sig)) - { - this.group = PathSpecGroup.EXACT; - } - else if (Pattern.matches("^e*v+",sig)) - { - this.group = PathSpecGroup.PREFIX_GLOB; - } - else if (Pattern.matches("^v+e+",sig)) - { - this.group = PathSpecGroup.SUFFIX_GLOB; - } - else - { - this.group = PathSpecGroup.MIDDLE_GLOB; - } - } - - /** - * Validate variable literal name, per RFC6570, Section 2.1 Literals - * @param variable - * @param pathParamSpec - */ - private void assertIsValidVariableLiteral(String variable) - { - int len = variable.length(); - - int i = 0; - int codepoint; - boolean valid = (len > 0); // must not be zero length - - while (valid && i < len) - { - codepoint = variable.codePointAt(i); - i += Character.charCount(codepoint); - - // basic letters, digits, or symbols - if (isValidBasicLiteralCodepoint(codepoint)) - { - continue; - } - - // The ucschar and iprivate pieces - if (Character.isSupplementaryCodePoint(codepoint)) - { - continue; - } - - // pct-encoded - if (codepoint == '%') - { - if (i + 2 > len) - { - // invalid percent encoding, missing extra 2 chars - valid = false; - continue; - } - codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4; - codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++)); - - // validate basic literal - if (isValidBasicLiteralCodepoint(codepoint)) - { - continue; - } - } - - valid = false; - } - - if (!valid) - { - // invalid variable name - StringBuilder err = new StringBuilder(); - err.append("Syntax Error: variable {"); - err.append(variable); - err.append("} an invalid variable name: "); - err.append(pathSpec); - throw new IllegalArgumentException(err.toString()); - } - } - - private boolean isValidBasicLiteralCodepoint(int codepoint) - { - // basic letters or digits - if((codepoint >= 'a' && codepoint <= 'z') || - (codepoint >= 'A' && codepoint <= 'Z') || - (codepoint >= '0' && codepoint <= '9')) - { - return true; - } - - // basic allowed symbols - if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0) - { - return true; // valid simple value - } - - // basic reserved symbols - if(VARIABLE_RESERVED.indexOf(codepoint) >= 0) - { - LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec); - return false; // valid simple value - } - - return false; - } - - public Map getPathParams(String path) - { - Matcher matcher = getMatcher(path); - if (matcher.matches()) - { - if (group == PathSpecGroup.EXACT) - { - return Collections.emptyMap(); - } - Map ret = new HashMap<>(); - int groupCount = matcher.groupCount(); - for (int i = 1; i <= groupCount; i++) - { - ret.put(this.variables[i - 1],matcher.group(i)); - } - return ret; - } - return null; - } - - public int getVariableCount() - { - return variables.length; - } - - public String[] getVariables() - { - return this.variables; - } -} diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java index d3e5553766..e1d5ca7710 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java @@ -18,9 +18,9 @@ package org.eclipse.jetty.websocket.jsr356.server; +import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.websocket.server.MappedWebSocketCreator; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; public class DummyCreator implements MappedWebSocketCreator diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java deleted file mode 100644 index 3196b0c5d5..0000000000 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java +++ /dev/null @@ -1,110 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.jsr356.server.pathmap; - -import static org.hamcrest.Matchers.notNullValue; - -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; -import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec; -import org.junit.Assert; -import org.junit.Test; - -public class PathMappingsTest -{ - private void assertMatch(PathMappings pathmap, String path, String expectedValue) - { - String msg = String.format(".getMatch(\"%s\")",path); - MappedResource match = pathmap.getMatch(path); - Assert.assertThat(msg,match,notNullValue()); - String actualMatch = match.getResource(); - Assert.assertEquals(msg,expectedValue,actualMatch); - } - - public void dumpMappings(PathMappings p) - { - for (MappedResource res : p) - { - System.out.printf(" %s%n",res); - } - } - - /** - * Test the match order rules with a mixed Servlet and WebSocket path specs - *

- *

    - *
  • Exact match
  • - *
  • Longest prefix match
  • - *
  • Longest suffix match
  • - *
- */ - @Test - public void testMixedMatchOrder() - { - PathMappings p = new PathMappings<>(); - - p.put(new ServletPathSpec("/"),"default"); - p.put(new ServletPathSpec("/animal/bird/*"),"birds"); - p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); - p.put(new ServletPathSpec("/animal/*"),"animals"); - p.put(new WebSocketPathSpec("/animal/{type}/{name}/chat"),"animalChat"); - p.put(new WebSocketPathSpec("/animal/{type}/{name}/cam"),"animalCam"); - p.put(new WebSocketPathSpec("/entrance/cam"),"entranceCam"); - - // dumpMappings(p); - - assertMatch(p,"/animal/bird/eagle","birds"); - assertMatch(p,"/animal/fish/bass/sea","fishes"); - assertMatch(p,"/animal/peccary/javalina/evolution","animals"); - assertMatch(p,"/","default"); - assertMatch(p,"/animal/bird/eagle/chat","animalChat"); - assertMatch(p,"/animal/bird/penguin/chat","animalChat"); - assertMatch(p,"/animal/fish/trout/cam","animalCam"); - assertMatch(p,"/entrance/cam","entranceCam"); - } - - /** - * Test the match order rules imposed by the WebSocket API (JSR-356) - *

- *

    - *
  • Exact match
  • - *
  • Longest prefix match
  • - *
  • Longest suffix match
  • - *
- */ - @Test - public void testWebsocketMatchOrder() - { - PathMappings p = new PathMappings<>(); - - p.put(new WebSocketPathSpec("/a/{var}/c"),"endpointA"); - p.put(new WebSocketPathSpec("/a/b/c"),"endpointB"); - p.put(new WebSocketPathSpec("/a/{var1}/{var2}"),"endpointC"); - p.put(new WebSocketPathSpec("/{var1}/d"),"endpointD"); - p.put(new WebSocketPathSpec("/b/{var2}"),"endpointE"); - - // dumpMappings(p); - - assertMatch(p,"/a/b/c","endpointB"); - assertMatch(p,"/a/d/c","endpointA"); - assertMatch(p,"/a/x/y","endpointC"); - - assertMatch(p,"/b/d","endpointE"); - } -} diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java deleted file mode 100644 index efc6af2268..0000000000 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java +++ /dev/null @@ -1,87 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.jsr356.server.pathmap; - -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -/** - * Tests for bad path specs on ServerEndpoint Path Param / URI Template - */ -@RunWith(Parameterized.class) -public class WebSocketPathSpecBadSpecsTest -{ - private static void bad(List data, String str) - { - data.add(new String[] - { str }); - } - - @Parameters - public static Collection data() - { - List data = new ArrayList<>(); - bad(data,"/a/b{var}"); // bad syntax - variable does not encompass whole path segment - bad(data,"a/{var}"); // bad syntax - no start slash - bad(data,"/a/{var/b}"); // path segment separator in variable name - bad(data,"/{var}/*"); // bad syntax - no globs allowed - bad(data,"/{var}.do"); // bad syntax - variable does not encompass whole path segment - bad(data,"/a/{var*}"); // use of glob character not allowed in variable name - bad(data,"/a/{}"); // bad syntax - no variable name - // MIGHT BE ALLOWED bad(data,"/a/{---}"); // no alpha in variable name - bad(data,"{var}"); // bad syntax - no start slash - bad(data,"/a/{my special variable}"); // bad syntax - space in variable name - bad(data,"/a/{var}/{var}"); // variable name duplicate - // MIGHT BE ALLOWED bad(data,"/a/{var}/{Var}/{vAR}"); // variable name duplicated (diff case) - bad(data,"/a/../../../{var}"); // path navigation not allowed - bad(data,"/a/./{var}"); // path navigation not allowed - bad(data,"/a//{var}"); // bad syntax - double path slash (no path segment) - return data; - } - - private String pathSpec; - - public WebSocketPathSpecBadSpecsTest(String pathSpec) - { - this.pathSpec = pathSpec; - } - - @Test - public void testBadPathSpec() - { - try - { - new WebSocketPathSpec(this.pathSpec); - fail("Expected IllegalArgumentException for a bad PathParam pathspec on: " + pathSpec); - } - catch (IllegalArgumentException e) - { - // expected path - System.out.println(e.getMessage()); - } - } -} diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java deleted file mode 100644 index 23b87e71f2..0000000000 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java +++ /dev/null @@ -1,286 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.jsr356.server.pathmap; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.util.Map; - -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; -import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup; -import org.junit.Test; - -/** - * Tests for ServerEndpoint Path Param / URI Template Path Specs - */ -public class WebSocketPathSpecTest -{ - private void assertDetectedVars(WebSocketPathSpec spec, String... expectedVars) - { - String prefix = String.format("Spec(\"%s\")",spec.getPathSpec()); - assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount()); - assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length); - for (int i = 0; i < expectedVars.length; i++) - { - assertEquals(String.format("%s.variable[%d]",prefix,i),expectedVars[i],spec.getVariables()[i]); - } - } - - private void assertMatches(PathSpec spec, String path) - { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); - assertThat(msg,spec.matches(path),is(true)); - } - - private void assertNotMatches(PathSpec spec, String path) - { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); - assertThat(msg,spec.matches(path),is(false)); - } - - @Test - public void testDefaultPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); - assertEquals("Spec.pattern","^/$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",1,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); - - assertEquals("Spec.variableCount",0,spec.getVariableCount()); - assertEquals("Spec.variable.length",0,spec.getVariables().length); - } - - @Test - public void testExactOnePathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/a"); - assertEquals("Spec.pathSpec","/a",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",1,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); - - assertMatches(spec,"/a"); - assertMatches(spec,"/a?type=other"); - assertNotMatches(spec,"/a/b"); - assertNotMatches(spec,"/a/"); - - assertEquals("Spec.variableCount",0,spec.getVariableCount()); - assertEquals("Spec.variable.length",0,spec.getVariables().length); - } - - @Test - public void testExactPathSpec_TestWebapp() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/javax.websocket/"); - assertEquals("Spec.pathSpec","/javax.websocket/",spec.getPathSpec()); - assertEquals("Spec.pattern","^/javax\\.websocket/$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",1,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); - - assertMatches(spec,"/javax.websocket/"); - assertNotMatches(spec,"/javax.websocket"); - - assertEquals("Spec.variableCount",0,spec.getVariableCount()); - assertEquals("Spec.variable.length",0,spec.getVariables().length); - } - - @Test - public void testExactTwoPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/a/b"); - assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",2,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup()); - - assertEquals("Spec.variableCount",0,spec.getVariableCount()); - assertEquals("Spec.variable.length",0,spec.getVariables().length); - - assertMatches(spec,"/a/b"); - - assertNotMatches(spec,"/a/b/"); - assertNotMatches(spec,"/a/"); - assertNotMatches(spec,"/a/bb"); - } - - @Test - public void testMiddleVarPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var}/c"); - assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",3,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); - - assertDetectedVars(spec,"var"); - - assertMatches(spec,"/a/b/c"); - assertMatches(spec,"/a/zz/c"); - assertMatches(spec,"/a/hello+world/c"); - assertNotMatches(spec,"/a/bc"); - assertNotMatches(spec,"/a/b/"); - assertNotMatches(spec,"/a/b"); - - Map mapped = spec.getPathParams("/a/b/c"); - assertThat("Spec.pathParams",mapped,notNullValue()); - assertThat("Spec.pathParams.size",mapped.size(),is(1)); - assertEquals("Spec.pathParams[var]","b",mapped.get("var")); - } - - @Test - public void testOneVarPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/a/{foo}"); - assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",2,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); - - assertDetectedVars(spec,"foo"); - - assertMatches(spec,"/a/b"); - assertNotMatches(spec,"/a/"); - assertNotMatches(spec,"/a"); - - Map mapped = spec.getPathParams("/a/b"); - assertThat("Spec.pathParams",mapped,notNullValue()); - assertThat("Spec.pathParams.size",mapped.size(),is(1)); - assertEquals("Spec.pathParams[foo]","b",mapped.get("foo")); - } - - @Test - public void testOneVarSuffixPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/{var}/b/c"); - assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec()); - assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",3,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup()); - - assertDetectedVars(spec,"var"); - - assertMatches(spec,"/a/b/c"); - assertMatches(spec,"/az/b/c"); - assertMatches(spec,"/hello+world/b/c"); - assertNotMatches(spec,"/a/bc"); - assertNotMatches(spec,"/a/b/"); - assertNotMatches(spec,"/a/b"); - - Map mapped = spec.getPathParams("/a/b/c"); - assertThat("Spec.pathParams",mapped,notNullValue()); - assertThat("Spec.pathParams.size",mapped.size(),is(1)); - assertEquals("Spec.pathParams[var]","a",mapped.get("var")); - } - - @Test - public void testTwoVarComplexInnerPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var1}/c/{var2}/e"); - assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",5,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); - - assertDetectedVars(spec,"var1","var2"); - - assertMatches(spec,"/a/b/c/d/e"); - assertNotMatches(spec,"/a/bc/d/e"); - assertNotMatches(spec,"/a/b/d/e"); - assertNotMatches(spec,"/a/b//d/e"); - - Map mapped = spec.getPathParams("/a/b/c/d/e"); - assertThat("Spec.pathParams",mapped,notNullValue()); - assertThat("Spec.pathParams.size",mapped.size(),is(2)); - assertEquals("Spec.pathParams[var1]","b",mapped.get("var1")); - assertEquals("Spec.pathParams[var2]","d",mapped.get("var2")); - } - - @Test - public void testTwoVarComplexOuterPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/{var1}/b/{var2}/{var3}"); - assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec()); - assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",4,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup()); - - assertDetectedVars(spec,"var1","var2","var3"); - - assertMatches(spec,"/a/b/c/d"); - assertNotMatches(spec,"/a/bc/d/e"); - assertNotMatches(spec,"/a/c/d/e"); - assertNotMatches(spec,"/a//d/e"); - - Map mapped = spec.getPathParams("/a/b/c/d"); - assertThat("Spec.pathParams",mapped,notNullValue()); - assertThat("Spec.pathParams.size",mapped.size(),is(3)); - assertEquals("Spec.pathParams[var1]","a",mapped.get("var1")); - assertEquals("Spec.pathParams[var2]","c",mapped.get("var2")); - assertEquals("Spec.pathParams[var3]","d",mapped.get("var3")); - } - - @Test - public void testTwoVarPrefixPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var1}/{var2}"); - assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",3,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); - - assertDetectedVars(spec,"var1","var2"); - - assertMatches(spec,"/a/b/c"); - assertNotMatches(spec,"/a/bc"); - assertNotMatches(spec,"/a/b/"); - assertNotMatches(spec,"/a/b"); - - Map mapped = spec.getPathParams("/a/b/c"); - assertThat("Spec.pathParams",mapped,notNullValue()); - assertThat("Spec.pathParams.size",mapped.size(),is(2)); - assertEquals("Spec.pathParams[var1]","b",mapped.get("var1")); - assertEquals("Spec.pathParams[var2]","c",mapped.get("var2")); - } - - @Test - public void testVarOnlyPathSpec() - { - WebSocketPathSpec spec = new WebSocketPathSpec("/{var1}"); - assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec()); - assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",1,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup()); - - assertDetectedVars(spec,"var1"); - - assertMatches(spec,"/a"); - assertNotMatches(spec,"/"); - assertNotMatches(spec,"/a/b"); - assertNotMatches(spec,"/a/b/c"); - - Map mapped = spec.getPathParams("/a"); - assertThat("Spec.pathParams",mapped,notNullValue()); - assertThat("Spec.pathParams.size",mapped.size(),is(1)); - assertEquals("Spec.pathParams[var1]","a",mapped.get("var1")); - } -} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java index c8d51924fe..8b668f1826 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java @@ -18,8 +18,8 @@ package org.eclipse.jetty.websocket.server; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; /** diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java index 9b9b113eba..bbd60a06fd 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java @@ -33,6 +33,9 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.pathmap.MappedResource; +import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.servlet.FilterHolder; @@ -44,9 +47,6 @@ import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; /** diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java index 97251ddd46..b35b7da0a9 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java @@ -24,13 +24,13 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.pathmap.MappedResource; +import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements MappedWebSocketCreator diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java deleted file mode 100644 index c8c256d517..0000000000 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java +++ /dev/null @@ -1,190 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.server.pathmap; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import org.eclipse.jetty.util.annotation.ManagedAttribute; -import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.component.Dumpable; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; - -/** - * Path Mappings of PathSpec to Resource. - *

- * Sorted into search order upon entry into the Set - * - * @param the type of mapping endpoint - */ -@ManagedObject("Path Mappings") -public class PathMappings implements Iterable>, Dumpable -{ - @ManagedObject("Mapped Resource") - public static class MappedResource implements Comparable> - { - private final PathSpec pathSpec; - private final E resource; - - public MappedResource(PathSpec pathSpec, E resource) - { - this.pathSpec = pathSpec; - this.resource = resource; - } - - /** - * Comparison is based solely on the pathSpec - */ - @Override - public int compareTo(MappedResource other) - { - return this.pathSpec.compareTo(other.pathSpec); - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (obj == null) - { - return false; - } - if (getClass() != obj.getClass()) - { - return false; - } - MappedResource other = (MappedResource)obj; - if (pathSpec == null) - { - if (other.pathSpec != null) - { - return false; - } - } - else if (!pathSpec.equals(other.pathSpec)) - { - return false; - } - return true; - } - - @ManagedAttribute(value = "path spec", readonly = true) - public PathSpec getPathSpec() - { - return pathSpec; - } - - @ManagedAttribute(value = "resource", readonly = true) - public E getResource() - { - return resource; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode()); - return result; - } - - @Override - public String toString() - { - return String.format("MappedResource[pathSpec=%s,resource=%s]",pathSpec,resource); - } - } - - private static final Logger LOG = Log.getLogger(PathMappings.class); - private List> mappings = new ArrayList>(); - private MappedResource defaultResource = null; - - @Override - public String dump() - { - return ContainerLifeCycle.dump(this); - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - ContainerLifeCycle.dump(out,indent,mappings); - } - - @ManagedAttribute(value = "mappings", readonly = true) - public List> getMappings() - { - return mappings; - } - - public void reset() - { - mappings.clear(); - } - - public MappedResource getMatch(String path) - { - int len = mappings.size(); - for (int i = 0; i < len; i++) - { - MappedResource mr = mappings.get(i); - if (mr.getPathSpec().matches(path)) - { - return mr; - } - } - return defaultResource; - } - - @Override - public Iterator> iterator() - { - return mappings.iterator(); - } - - public void put(PathSpec pathSpec, E resource) - { - MappedResource entry = new MappedResource<>(pathSpec,resource); - if (pathSpec.group == PathSpecGroup.DEFAULT) - { - defaultResource = entry; - } - // TODO: warning on replacement of existing mapping? - mappings.add(entry); - if (LOG.isDebugEnabled()) - LOG.debug("Added {} to {}",entry,this); - Collections.sort(mappings); - } - - @Override - public String toString() - { - return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size()); - } -} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java deleted file mode 100644 index 2e8999c01c..0000000000 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java +++ /dev/null @@ -1,167 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.server.pathmap; - -/** - * The base PathSpec, what all other path specs are based on - */ -public abstract class PathSpec implements Comparable -{ - protected String pathSpec; - protected PathSpecGroup group; - protected int pathDepth; - protected int specLength; - - @Override - public int compareTo(PathSpec other) - { - // Grouping (increasing) - int diff = this.group.ordinal() - other.group.ordinal(); - if (diff != 0) - { - return diff; - } - - // Spec Length (decreasing) - diff = other.specLength - this.specLength; - if (diff != 0) - { - return diff; - } - - // Path Spec Name (alphabetical) - return this.pathSpec.compareTo(other.pathSpec); - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (obj == null) - { - return false; - } - if (getClass() != obj.getClass()) - { - return false; - } - PathSpec other = (PathSpec)obj; - if (pathSpec == null) - { - if (other.pathSpec != null) - { - return false; - } - } - else if (!pathSpec.equals(other.pathSpec)) - { - return false; - } - return true; - } - - public PathSpecGroup getGroup() - { - return group; - } - - /** - * Get the number of path elements that this path spec declares. - *

- * This is used to determine longest match logic. - * - * @return the depth of the path segments that this spec declares - */ - public int getPathDepth() - { - return pathDepth; - } - - /** - * Return the portion of the path that is after the path spec. - * - * @param path - * the path to match against - * @return the path info portion of the string - */ - public abstract String getPathInfo(String path); - - /** - * Return the portion of the path that matches a path spec. - * - * @param path - * the path to match against - * @return the match, or null if no match at all - */ - public abstract String getPathMatch(String path); - - /** - * The as-provided path spec. - * - * @return the as-provided path spec - */ - public String getPathSpec() - { - return pathSpec; - } - - /** - * Get the relative path. - * - * @param base - * the base the path is relative to - * @param path - * the additional path - * @return the base plus path with pathSpec portion removed - */ - public abstract String getRelativePath(String base, String path); - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode()); - return result; - } - - /** - * Test to see if the provided path matches this path spec - * - * @param path - * the path to test - * @return true if the path matches this path spec, false otherwise - */ - public abstract boolean matches(String path); - - @Override - public String toString() - { - StringBuilder str = new StringBuilder(); - str.append(this.getClass().getSimpleName()).append("[\""); - str.append(pathSpec); - str.append("\",pathDepth=").append(pathDepth); - str.append(",group=").append(group); - str.append("]"); - return str.toString(); - } -} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java deleted file mode 100644 index 287d02df04..0000000000 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java +++ /dev/null @@ -1,82 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.server.pathmap; - -/** - * Types of path spec groups. - *

- * This is used to facilitate proper pathspec search order. - *

- * Search Order: {@link PathSpecGroup#ordinal()} [increasin], {@link PathSpec#specLength} [decreasing], {@link PathSpec#pathSpec} [natural sort order] - */ -public enum PathSpecGroup -{ - // NOTE: Order of enums determines order of Groups. - - /** - * For exactly defined path specs, no glob. - */ - EXACT, - /** - * For path specs that have a hardcoded prefix and suffix with wildcard glob in the middle. - * - *

-     *   "^/downloads/[^/]*.zip$"  - regex spec
-     *   "/a/{var}/c"              - websocket spec
-     * 
- * - * Note: there is no known servlet spec variant of this kind of path spec - */ - MIDDLE_GLOB, - /** - * For path specs that have a hardcoded prefix and a trailing wildcard glob. - *

- * - *

-     *   "/downloads/*"          - servlet spec
-     *   "/api/*"                - servlet spec
-     *   "^/rest/.*$"            - regex spec
-     *   "/bookings/{guest-id}"  - websocket spec
-     *   "/rewards/{vip-level}"  - websocket spec
-     * 
- */ - PREFIX_GLOB, - /** - * For path specs that have a wildcard glob with a hardcoded suffix - * - *
-     *   "*.do"        - servlet spec
-     *   "*.css"       - servlet spec
-     *   "^.*\.zip$"   - regex spec
-     * 
- * - * Note: there is no known websocket spec variant of this kind of path spec - */ - SUFFIX_GLOB, - /** - * The default spec for accessing the Root and/or Default behavior. - * - *
-     *   "/"           - servlet spec   (Default Servlet)
-     *   "/"           - websocket spec (Root Context)
-     *   "^/$"         - regex spec     (Root Context)
-     * 
- */ - DEFAULT; -} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java index cf5f75809a..a74c4d06a3 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java @@ -18,159 +18,14 @@ package org.eclipse.jetty.websocket.server.pathmap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class RegexPathSpec extends PathSpec +/** + * @deprecated moved to jetty-http {@link org.eclipse.jetty.http.pathmap.RegexPathSpec} (this facade will be removed in Jetty 9.4) + */ +@Deprecated +public class RegexPathSpec extends org.eclipse.jetty.http.pathmap.RegexPathSpec { - protected Pattern pattern; - - protected RegexPathSpec() - { - super(); - } - public RegexPathSpec(String regex) { - super.pathSpec = regex; - boolean inGrouping = false; - this.pathDepth = 0; - this.specLength = pathSpec.length(); - // build up a simple signature we can use to identify the grouping - StringBuilder signature = new StringBuilder(); - for (char c : pathSpec.toCharArray()) - { - switch (c) - { - case '[': - inGrouping = true; - break; - case ']': - inGrouping = false; - signature.append('g'); // glob - break; - case '*': - signature.append('g'); // glob - break; - case '/': - if (!inGrouping) - { - this.pathDepth++; - } - break; - default: - if (!inGrouping) - { - if (Character.isLetterOrDigit(c)) - { - signature.append('l'); // literal (exact) - } - } - break; - } - } - this.pattern = Pattern.compile(pathSpec); - - // Figure out the grouping based on the signature - String sig = signature.toString(); - - if (Pattern.matches("^l*$",sig)) - { - this.group = PathSpecGroup.EXACT; - } - else if (Pattern.matches("^l*g+",sig)) - { - this.group = PathSpecGroup.PREFIX_GLOB; - } - else if (Pattern.matches("^g+l+$",sig)) - { - this.group = PathSpecGroup.SUFFIX_GLOB; - } - else - { - this.group = PathSpecGroup.MIDDLE_GLOB; - } - } - - public Matcher getMatcher(String path) - { - return this.pattern.matcher(path); - } - - @Override - public String getPathInfo(String path) - { - // Path Info only valid for PREFIX_GLOB types - if (group == PathSpecGroup.PREFIX_GLOB) - { - Matcher matcher = getMatcher(path); - if (matcher.matches()) - { - if (matcher.groupCount() >= 1) - { - String pathInfo = matcher.group(1); - if ("".equals(pathInfo)) - { - return "/"; - } - else - { - return pathInfo; - } - } - } - } - return null; - } - - @Override - public String getPathMatch(String path) - { - Matcher matcher = getMatcher(path); - if (matcher.matches()) - { - if (matcher.groupCount() >= 1) - { - int idx = matcher.start(1); - if (idx > 0) - { - if (path.charAt(idx - 1) == '/') - { - idx--; - } - return path.substring(0,idx); - } - } - return path; - } - return null; - } - - public Pattern getPattern() - { - return this.pattern; - } - - @Override - public String getRelativePath(String base, String path) - { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean matches(final String path) - { - int idx = path.indexOf('?'); - if (idx >= 0) - { - // match only non-query part - return getMatcher(path.substring(0,idx)).matches(); - } - else - { - // match entire path - return getMatcher(path).matches(); - } + super(regex); } } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java index b09b15b790..b24f3d8b0c 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java @@ -18,274 +18,14 @@ package org.eclipse.jetty.websocket.server.pathmap; -import org.eclipse.jetty.util.URIUtil; - -public class ServletPathSpec extends PathSpec +/** + * @deprecated moved to jetty-http {@link org.eclipse.jetty.http.pathmap.ServletPathSpec} (this facade will be removed in Jetty 9.4) + */ +@Deprecated +public class ServletPathSpec extends org.eclipse.jetty.http.pathmap.ServletPathSpec { - public static final String PATH_SPEC_SEPARATORS = ":,"; - - /** - * Get multi-path spec splits. - * - * @param servletPathSpec - * the path spec that might contain multiple declared path specs - * @return the individual path specs found. - */ - public static ServletPathSpec[] getMultiPathSpecs(String servletPathSpec) - { - String pathSpecs[] = servletPathSpec.split(PATH_SPEC_SEPARATORS); - int len = pathSpecs.length; - ServletPathSpec sps[] = new ServletPathSpec[len]; - for (int i = 0; i < len; i++) - { - sps[i] = new ServletPathSpec(pathSpecs[i]); - } - return sps; - } - public ServletPathSpec(String servletPathSpec) { - super(); - assertValidServletPathSpec(servletPathSpec); - - // The Path Spec for Default Servlet - if ((servletPathSpec == null) || (servletPathSpec.length() == 0) || "/".equals(servletPathSpec)) - { - super.pathSpec = "/"; - super.pathDepth = -1; // force this to be last in sort order - this.specLength = 1; - this.group = PathSpecGroup.DEFAULT; - return; - } - - this.specLength = servletPathSpec.length(); - super.pathDepth = 0; - char lastChar = servletPathSpec.charAt(specLength - 1); - // prefix based - if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*')) - { - this.group = PathSpecGroup.PREFIX_GLOB; - } - // suffix based - else if (servletPathSpec.charAt(0) == '*') - { - this.group = PathSpecGroup.SUFFIX_GLOB; - } - else - { - this.group = PathSpecGroup.EXACT; - } - - for (int i = 0; i < specLength; i++) - { - int cp = servletPathSpec.codePointAt(i); - if (cp < 128) - { - char c = (char)cp; - switch (c) - { - case '/': - super.pathDepth++; - break; - } - } - } - - super.pathSpec = servletPathSpec; - } - - private void assertValidServletPathSpec(String servletPathSpec) - { - if ((servletPathSpec == null) || servletPathSpec.equals("")) - { - return; // empty path spec - } - - // Ensure we don't have path spec separators here in our single path spec. - for (char c : PATH_SPEC_SEPARATORS.toCharArray()) - { - if (servletPathSpec.indexOf(c) >= 0) - { - throw new IllegalArgumentException("Servlet Spec 12.2 violation: encountered Path Spec Separator [" + PATH_SPEC_SEPARATORS - + "] within specified path spec. did you forget to split this path spec up?"); - } - } - - int len = servletPathSpec.length(); - // path spec must either start with '/' or '*.' - if (servletPathSpec.charAt(0) == '/') - { - // Prefix Based - if (len == 1) - { - return; // simple '/' path spec - } - int idx = servletPathSpec.indexOf('*'); - if (idx < 0) - { - return; // no hit on glob '*' - } - // only allowed to have '*' at the end of the path spec - if (idx != (len - 1)) - { - throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches"); - } - } - else if (servletPathSpec.startsWith("*.")) - { - // Suffix Based - int idx = servletPathSpec.indexOf('/'); - // cannot have path separator - if (idx >= 0) - { - throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators"); - } - - idx = servletPathSpec.indexOf('*',2); - // only allowed to have 1 glob '*', at the start of the path spec - if (idx >= 1) - { - throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*'"); - } - } - else - { - throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\""); - } - } - - @Override - public String getPathInfo(String path) - { - // Path Info only valid for PREFIX_GLOB types - if (group == PathSpecGroup.PREFIX_GLOB) - { - if (path.length() == (specLength - 2)) - { - return null; - } - return path.substring(specLength - 2); - } - - return null; - } - - @Override - public String getPathMatch(String path) - { - switch (group) - { - case EXACT: - if (pathSpec.equals(path)) - { - return path; - } - else - { - return null; - } - case PREFIX_GLOB: - if (isWildcardMatch(path)) - { - return path.substring(0,specLength - 2); - } - else - { - return null; - } - case SUFFIX_GLOB: - if (path.regionMatches(path.length() - (specLength - 1),pathSpec,1,specLength - 1)) - { - return path; - } - else - { - return null; - } - case DEFAULT: - return path; - default: - return null; - } - } - - @Override - public String getRelativePath(String base, String path) - { - String info = getPathInfo(path); - if (info == null) - { - info = path; - } - - if (info.startsWith("./")) - { - info = info.substring(2); - } - if (base.endsWith(URIUtil.SLASH)) - { - if (info.startsWith(URIUtil.SLASH)) - { - path = base + info.substring(1); - } - else - { - path = base + info; - } - } - else if (info.startsWith(URIUtil.SLASH)) - { - path = base + info; - } - else - { - path = base + URIUtil.SLASH + info; - } - return path; - } - - private boolean isExactMatch(String path) - { - if (group == PathSpecGroup.EXACT) - { - if (pathSpec.equals(path)) - { - return true; - } - return (path.charAt(path.length() - 1) == '/') && (path.equals(pathSpec + '/')); - } - return false; - } - - private boolean isWildcardMatch(String path) - { - // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar" - int cpl = specLength - 2; - if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0,pathSpec,0,cpl))) - { - if ((path.length() == cpl) || ('/' == path.charAt(cpl))) - { - return true; - } - } - return false; - } - - @Override - public boolean matches(String path) - { - switch (group) - { - case EXACT: - return isExactMatch(path); - case PREFIX_GLOB: - return isWildcardMatch(path); - case SUFFIX_GLOB: - return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1); - case DEFAULT: - return true; - default: - return false; - } + super(servletPathSpec); } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java deleted file mode 100644 index 49b846c7cc..0000000000 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java +++ /dev/null @@ -1,226 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.server.pathmap; - -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.http.PathMap; -import org.eclipse.jetty.toolchain.test.AdvancedRunner; -import org.eclipse.jetty.toolchain.test.annotation.Stress; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AdvancedRunner.class) -public class PathMappingsBenchmarkTest -{ - public static abstract class AbstractPathMapThread extends Thread - { - private int iterations; - private CyclicBarrier barrier; - @SuppressWarnings("unused") - private long success; - @SuppressWarnings("unused") - private long error; - - public AbstractPathMapThread(int iterations, CyclicBarrier barrier) - { - this.iterations = iterations; - this.barrier = barrier; - } - - public abstract String getMatchedResource(String path); - - @Override - public void run() - { - int llen = LOOKUPS.length; - String path; - String expectedResource; - String matchedResource; - await(barrier); - for (int iter = 0; iter < iterations; iter++) - { - for (int li = 0; li < llen; li++) - { - path = LOOKUPS[li][0]; - expectedResource = LOOKUPS[li][1]; - matchedResource = getMatchedResource(path); - if (matchedResource.equals(expectedResource)) - { - success++; - } - else - { - error++; - } - } - } - await(barrier); - } - } - - public static class PathMapMatchThread extends AbstractPathMapThread - { - private PathMap pathmap; - - public PathMapMatchThread(PathMap pathmap, int iters, CyclicBarrier barrier) - { - super(iters,barrier); - this.pathmap = pathmap; - } - - @Override - public String getMatchedResource(String path) - { - return pathmap.getMatch(path).getValue(); - } - } - - public static class PathMatchThread extends AbstractPathMapThread - { - private PathMappings pathmap; - - public PathMatchThread(PathMappings pathmap, int iters, CyclicBarrier barrier) - { - super(iters,barrier); - this.pathmap = pathmap; - } - - @Override - public String getMatchedResource(String path) - { - return pathmap.getMatch(path).getResource(); - } - } - - private static final Logger LOG = Log.getLogger(PathMappingsBenchmarkTest.class); - private static final String[][] LOOKUPS; - private int runs = 20; - private int threads = 200; - private int iters = 10000; - - static - { - LOOKUPS = new String[][] - { - // @formatter:off - { "/abs/path", "path" }, - { "/abs/path/longer","longpath" }, - { "/abs/path/foo","default" }, - { "/main.css","default" }, - { "/downloads/script.gz","gzipped" }, - { "/downloads/distribution.tar.gz","tarball" }, - { "/downloads/readme.txt","default" }, - { "/downloads/logs.tgz","default" }, - { "/animal/horse/mustang","animals" }, - { "/animal/bird/eagle/bald","birds" }, - { "/animal/fish/shark/hammerhead","fishes" }, - { "/animal/insect/ladybug","animals" }, - // @formatter:on - }; - } - - private static void await(CyclicBarrier barrier) - { - try - { - barrier.await(); - } - catch (Exception x) - { - throw new RuntimeException(x); - } - } - - @Stress("High CPU") - @Test - public void testServletPathMap() - { - // Setup (old) PathMap - - PathMap p = new PathMap<>(); - - p.put("/abs/path","path"); - p.put("/abs/path/longer","longpath"); - p.put("/animal/bird/*","birds"); - p.put("/animal/fish/*","fishes"); - p.put("/animal/*","animals"); - p.put("*.tar.gz","tarball"); - p.put("*.gz","gzipped"); - p.put("/","default"); - - final CyclicBarrier barrier = new CyclicBarrier(threads + 1); - - for (int r = 0; r < runs; r++) - { - for (int t = 0; t < threads; t++) - { - PathMapMatchThread thread = new PathMapMatchThread(p,iters,barrier); - thread.start(); - } - await(barrier); - long begin = System.nanoTime(); - await(barrier); - long end = System.nanoTime(); - long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin); - int totalMatches = threads * iters * LOOKUPS.length; - LOG.info("jetty-http/PathMap (Servlet only) threads:{}/iters:{}/total-matches:{} => {} ms",threads,iters,totalMatches,elapsed); - } - } - - @Stress("High CPU") - @Test - public void testServletPathMappings() - { - // Setup (new) PathMappings - - PathMappings p = new PathMappings<>(); - - p.put(new ServletPathSpec("/abs/path"),"path"); - p.put(new ServletPathSpec("/abs/path/longer"),"longpath"); - p.put(new ServletPathSpec("/animal/bird/*"),"birds"); - p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); - p.put(new ServletPathSpec("/animal/*"),"animals"); - p.put(new ServletPathSpec("*.tar.gz"),"tarball"); - p.put(new ServletPathSpec("*.gz"),"gzipped"); - p.put(new ServletPathSpec("/"),"default"); - - final CyclicBarrier barrier = new CyclicBarrier(threads + 1); - - for (int r = 0; r < runs; r++) - { - for (int t = 0; t < threads; t++) - { - PathMatchThread thread = new PathMatchThread(p,iters,barrier); - thread.start(); - } - await(barrier); - long begin = System.nanoTime(); - await(barrier); - long end = System.nanoTime(); - long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin); - int totalMatches = threads * iters * LOOKUPS.length; - LOG.info("jetty-websocket/PathMappings (Servlet only) threads:{}/iters:{}/total-matches:{} => {} ms",threads,iters,totalMatches,elapsed); - - } - } -} diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java deleted file mode 100644 index bac59379d5..0000000000 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java +++ /dev/null @@ -1,119 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.server.pathmap; - -import static org.hamcrest.Matchers.notNullValue; - -import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; -import org.junit.Assert; -import org.junit.Test; - -public class PathMappingsTest -{ - private void assertMatch(PathMappings pathmap, String path, String expectedValue) - { - String msg = String.format(".getMatch(\"%s\")",path); - MappedResource match = pathmap.getMatch(path); - Assert.assertThat(msg,match,notNullValue()); - String actualMatch = match.getResource(); - Assert.assertEquals(msg,expectedValue,actualMatch); - } - - public void dumpMappings(PathMappings p) - { - for (MappedResource res : p) - { - System.out.printf(" %s%n",res); - } - } - - /** - * Test the match order rules with a mixed Servlet and WebSocket path specs - *

- *

    - *
  • Exact match
  • - *
  • Longest prefix match
  • - *
  • Longest suffix match
  • - *
- */ - @Test - public void testMixedMatchOrder() - { - PathMappings p = new PathMappings<>(); - - p.put(new ServletPathSpec("/"),"default"); - p.put(new ServletPathSpec("/animal/bird/*"),"birds"); - p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); - p.put(new ServletPathSpec("/animal/*"),"animals"); - p.put(new RegexPathSpec("^/animal/.*/chat$"),"animalChat"); - p.put(new RegexPathSpec("^/animal/.*/cam$"),"animalCam"); - p.put(new RegexPathSpec("^/entrance/cam$"),"entranceCam"); - - // dumpMappings(p); - - assertMatch(p,"/animal/bird/eagle","birds"); - assertMatch(p,"/animal/fish/bass/sea","fishes"); - assertMatch(p,"/animal/peccary/javalina/evolution","animals"); - assertMatch(p,"/","default"); - assertMatch(p,"/animal/bird/eagle/chat","animalChat"); - assertMatch(p,"/animal/bird/penguin/chat","animalChat"); - assertMatch(p,"/animal/fish/trout/cam","animalCam"); - assertMatch(p,"/entrance/cam","entranceCam"); - } - - /** - * Test the match order rules imposed by the Servlet API. - *

- *

    - *
  • Exact match
  • - *
  • Longest prefix match
  • - *
  • Longest suffix match
  • - *
  • default
  • - *
- */ - @Test - public void testServletMatchOrder() - { - PathMappings p = new PathMappings<>(); - - p.put(new ServletPathSpec("/abs/path"),"path"); - p.put(new ServletPathSpec("/abs/path/longer"),"longpath"); - p.put(new ServletPathSpec("/animal/bird/*"),"birds"); - p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); - p.put(new ServletPathSpec("/animal/*"),"animals"); - p.put(new ServletPathSpec("*.tar.gz"),"tarball"); - p.put(new ServletPathSpec("*.gz"),"gzipped"); - p.put(new ServletPathSpec("/"),"default"); - - // dumpMappings(p); - - assertMatch(p,"/abs/path","path"); - assertMatch(p,"/abs/path/longer","longpath"); - assertMatch(p,"/abs/path/foo","default"); - assertMatch(p,"/main.css","default"); - assertMatch(p,"/downloads/script.gz","gzipped"); - assertMatch(p,"/downloads/distribution.tar.gz","tarball"); - assertMatch(p,"/downloads/readme.txt","default"); - assertMatch(p,"/downloads/logs.tgz","default"); - assertMatch(p,"/animal/horse/mustang","animals"); - assertMatch(p,"/animal/bird/eagle/bald","birds"); - assertMatch(p,"/animal/fish/shark/hammerhead","fishes"); - assertMatch(p,"/animal/insect/ladybug","animals"); - } -} diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java deleted file mode 100644 index 94d16e25dc..0000000000 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java +++ /dev/null @@ -1,135 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.server.pathmap; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import org.junit.Test; - -public class RegexPathSpecTest -{ - public static void assertMatches(PathSpec spec, String path) - { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); - assertThat(msg,spec.matches(path),is(true)); - } - - public static void assertNotMatches(PathSpec spec, String path) - { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); - assertThat(msg,spec.matches(path),is(false)); - } - - @Test - public void testExactSpec() - { - RegexPathSpec spec = new RegexPathSpec("^/a$"); - assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",1,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group); - - assertMatches(spec,"/a"); - - assertNotMatches(spec,"/aa"); - assertNotMatches(spec,"/a/"); - } - - @Test - public void testMiddleSpec() - { - RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$"); - assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec()); - assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",3,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); - - assertMatches(spec,"/rest/api/list"); - assertMatches(spec,"/rest/1.0/list"); - assertMatches(spec,"/rest/2.0/list"); - assertMatches(spec,"/rest/accounts/list"); - - assertNotMatches(spec,"/a"); - assertNotMatches(spec,"/aa"); - assertNotMatches(spec,"/aa/bb"); - assertNotMatches(spec,"/rest/admin/delete"); - assertNotMatches(spec,"/rest/list"); - } - - @Test - public void testMiddleSpecNoGrouping() - { - RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$"); - assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec()); - assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",3,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group); - - assertMatches(spec,"/rest/api/list"); - assertMatches(spec,"/rest/1.0/list"); - assertMatches(spec,"/rest/2.0/list"); - assertMatches(spec,"/rest/accounts/list"); - - assertNotMatches(spec,"/a"); - assertNotMatches(spec,"/aa"); - assertNotMatches(spec,"/aa/bb"); - assertNotMatches(spec,"/rest/admin/delete"); - assertNotMatches(spec,"/rest/list"); - } - - @Test - public void testPrefixSpec() - { - RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$"); - assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec()); - assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",2,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group); - - assertMatches(spec,"/a/"); - assertMatches(spec,"/a/b"); - assertMatches(spec,"/a/b/c/d/e"); - - assertNotMatches(spec,"/a"); - assertNotMatches(spec,"/aa"); - assertNotMatches(spec,"/aa/bb"); - } - - @Test - public void testSuffixSpec() - { - RegexPathSpec spec = new RegexPathSpec("^(.*).do$"); - assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec()); - assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern()); - assertEquals("Spec.pathDepth",0,spec.getPathDepth()); - assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group); - - assertMatches(spec,"/a.do"); - assertMatches(spec,"/a/b/c.do"); - assertMatches(spec,"/abcde.do"); - assertMatches(spec,"/abc/efg.do"); - - assertNotMatches(spec,"/a"); - assertNotMatches(spec,"/aa"); - assertNotMatches(spec,"/aa/bb"); - assertNotMatches(spec,"/aa/bb.do/more"); - } -} diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java deleted file mode 100644 index b0b8e29c44..0000000000 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java +++ /dev/null @@ -1,188 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.websocket.server.pathmap; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -import org.junit.Test; - -public class ServletPathSpecTest -{ - private void assertBadServletPathSpec(String pathSpec) - { - try - { - new ServletPathSpec(pathSpec); - fail("Expected IllegalArgumentException for a bad servlet pathspec on: " + pathSpec); - } - catch (IllegalArgumentException e) - { - // expected path - System.out.println(e); - } - } - - private void assertMatches(ServletPathSpec spec, String path) - { - String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); - assertThat(msg,spec.matches(path),is(true)); - } - - private void assertNotMatches(ServletPathSpec spec, String path) - { - String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path); - assertThat(msg,spec.matches(path),is(false)); - } - - @Test - public void testBadServletPathSpecA() - { - assertBadServletPathSpec("foo"); - } - - @Test - public void testBadServletPathSpecB() - { - assertBadServletPathSpec("/foo/*.do"); - } - - @Test - public void testBadServletPathSpecC() - { - assertBadServletPathSpec("foo/*.do"); - } - - @Test - public void testBadServletPathSpecD() - { - assertBadServletPathSpec("foo/*.*do"); - } - - @Test - public void testBadServletPathSpecE() - { - assertBadServletPathSpec("*do"); - } - - @Test - public void testDefaultPathSpec() - { - ServletPathSpec spec = new ServletPathSpec("/"); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); - assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); - } - - @Test - public void testEmptyPathSpec() - { - ServletPathSpec spec = new ServletPathSpec(""); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); - assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); - } - - @Test - public void testExactPathSpec() - { - ServletPathSpec spec = new ServletPathSpec("/abs/path"); - assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec()); - assertEquals("Spec.pathDepth",2,spec.getPathDepth()); - - assertMatches(spec,"/abs/path"); - assertMatches(spec,"/abs/path/"); - - assertNotMatches(spec,"/abs/path/more"); - assertNotMatches(spec,"/foo"); - assertNotMatches(spec,"/foo/abs/path"); - assertNotMatches(spec,"/foo/abs/path/"); - } - - @Test - public void testGetPathInfo() - { - assertEquals("pathInfo exact",null,new ServletPathSpec("/Foo/bar").getPathInfo("/Foo/bar")); - assertEquals("pathInfo prefix","/bar",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/bar")); - assertEquals("pathInfo prefix","/*",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/*")); - assertEquals("pathInfo prefix","/",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/")); - assertEquals("pathInfo prefix",null,new ServletPathSpec("/Foo/*").getPathInfo("/Foo")); - assertEquals("pathInfo suffix",null,new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext")); - assertEquals("pathInfo default",null,new ServletPathSpec("/").getPathInfo("/Foo/bar.ext")); - - assertEquals("pathInfo default","/xxx/zzz",new ServletPathSpec("/*").getPathInfo("/xxx/zzz")); - } - - @Test - public void testNullPathSpec() - { - ServletPathSpec spec = new ServletPathSpec(null); - assertEquals("Spec.pathSpec","/",spec.getPathSpec()); - assertEquals("Spec.pathDepth",-1,spec.getPathDepth()); - } - - @Test - public void testPathMatch() - { - assertEquals("pathMatch exact","/Foo/bar",new ServletPathSpec("/Foo/bar").getPathMatch("/Foo/bar")); - assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/bar")); - assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/")); - assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo")); - assertEquals("pathMatch suffix","/Foo/bar.ext",new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext")); - assertEquals("pathMatch default","/Foo/bar.ext",new ServletPathSpec("/").getPathMatch("/Foo/bar.ext")); - - assertEquals("pathMatch default","",new ServletPathSpec("/*").getPathMatch("/xxx/zzz")); - } - - @Test - public void testPrefixPathSpec() - { - ServletPathSpec spec = new ServletPathSpec("/downloads/*"); - assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec()); - assertEquals("Spec.pathDepth",2,spec.getPathDepth()); - - assertMatches(spec,"/downloads/logo.jpg"); - assertMatches(spec,"/downloads/distribution.tar.gz"); - assertMatches(spec,"/downloads/distribution.tgz"); - assertMatches(spec,"/downloads/distribution.zip"); - - assertMatches(spec,"/downloads"); - - assertEquals("Spec.pathInfo","/",spec.getPathInfo("/downloads/")); - assertEquals("Spec.pathInfo","/distribution.zip",spec.getPathInfo("/downloads/distribution.zip")); - assertEquals("Spec.pathInfo","/dist/9.0/distribution.tar.gz",spec.getPathInfo("/downloads/dist/9.0/distribution.tar.gz")); - } - - @Test - public void testSuffixPathSpec() - { - ServletPathSpec spec = new ServletPathSpec("*.gz"); - assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec()); - assertEquals("Spec.pathDepth",0,spec.getPathDepth()); - - assertMatches(spec,"/downloads/distribution.tar.gz"); - assertMatches(spec,"/downloads/jetty.log.gz"); - - assertNotMatches(spec,"/downloads/distribution.zip"); - assertNotMatches(spec,"/downloads/distribution.tgz"); - assertNotMatches(spec,"/abs/path"); - - assertEquals("Spec.pathInfo",null,spec.getPathInfo("/downloads/distribution.tar.gz")); - } -} -- cgit v1.2.3 From a4cf7d654bf839129d0cb0e9c5bf7f2585e5afe6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 14 Dec 2015 11:22:24 -0700 Subject: 482042 - New API, Allow customization of ServletHandler path mapping + Swapping out PathMap for PathMappings in ServletHandler Conflicts: jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java Conflicts: jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java --- .../src/main/java/org/eclipse/jetty/servlet/ServletHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index ebafc965b2..d7accdf6d6 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -53,6 +53,8 @@ import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.ServletPathSpec; +import org.eclipse.jetty.http.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.server.Request; -- cgit v1.2.3 From 5f27a62aae0bccb5b2630015a5d14d77d2cdf91d Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 17 Dec 2015 15:31:50 -0700 Subject: 484397 - Unavoidable NullPointerException in onMessage-Handler for PongMessages --- .../java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java index 961329a113..e3458b7762 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java @@ -22,6 +22,8 @@ import java.nio.ByteBuffer; import javax.websocket.PongMessage; +import org.eclipse.jetty.util.BufferUtil; + public class JsrPongMessage implements PongMessage { private final ByteBuffer data; @@ -34,6 +36,10 @@ public class JsrPongMessage implements PongMessage @Override public ByteBuffer getApplicationData() { + if (data == null) + { + return BufferUtil.EMPTY_BUFFER; + } return data.slice(); } } -- cgit v1.2.3 From 1543601968a6901717a9a403c4894a4280ffcc2f Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 17 Dec 2015 16:15:06 -0700 Subject: 484612 - Restore WebSocket Session.close() sending 1000/Normal status code --- .../main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index e0e2c91d08..c923a0f1aa 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -94,7 +94,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc @Override public void close() { - connection.close(); + /* This is assumed to always be a NORMAL closure, no reason phrase */ + connection.close(StatusCode.NORMAL, null); } @Override -- cgit v1.2.3 From ecbfe7c1d08936052da021f644d19c4b0db9fe65 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 18 Dec 2015 11:52:56 +1100 Subject: 484603 HashLoginService does not stop its PropertyUserStore --- .../src/main/java/org/eclipse/jetty/security/HashLoginService.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java index e9f18b59c2..a4bf649410 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java @@ -56,7 +56,6 @@ public class HashLoginService extends MappedLoginService implements UserListener private PropertyUserStore _propertyUserStore; private String _config; private Resource _configResource; - private Scanner _scanner; private boolean hotReload = false; // default is not to reload @@ -254,9 +253,9 @@ public class HashLoginService extends MappedLoginService implements UserListener protected void doStop() throws Exception { super.doStop(); - if (_scanner != null) - _scanner.stop(); - _scanner = null; + if (_propertyUserStore != null) + _propertyUserStore.stop(); + _propertyUserStore = null; } /* ------------------------------------------------------------ */ -- cgit v1.2.3 From 316a2e866190c90e4dc4fc37c99ccce04ad57c6f Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 18 Dec 2015 12:35:42 +1100 Subject: 484603 HashLoginService does not stop its PropertyUserStore --- .../src/main/java/org/eclipse/jetty/security/HashLoginService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java index 108ca0ad6f..ae5efe4ee0 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java @@ -199,5 +199,8 @@ public class HashLoginService extends AbstractLoginService protected void doStop() throws Exception { super.doStop(); + if (_propertyUserStore != null) + _propertyUserStore.stop(); + _propertyUserStore = null; } } -- cgit v1.2.3 From 5cd676581c178dc3611298e611c62fc51ed1b6cf Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 18 Dec 2015 12:36:27 +1100 Subject: 484622 - Improve handling of Direct and Mapped buffers for static content ResourceHttpContent now applies a maxBufferSize that is passed through the call to getContent ResourceCache now accounts for the exact memory usage of content, which may have an indirect buffer plus either a direct or mapped buffer. Thus content size may be 0, 1 or 2 times the file size. Some more limited unit tests --- .../java/org/eclipse/jetty/http/HttpContent.java | 9 ++- .../eclipse/jetty/http/ResourceHttpContent.java | 4 +- .../java/org/eclipse/jetty/server/HttpOutput.java | 1 + .../org/eclipse/jetty/server/ResourceCache.java | 58 ++++++++++------- .../jetty/server/ResourceContentFactory.java | 36 +++++------ .../eclipse/jetty/server/ResourceCacheTest.java | 73 +++++++++++++++++----- .../org/eclipse/jetty/servlet/DefaultServlet.java | 13 +++- .../eclipse/jetty/servlet/DefaultServletTest.java | 43 +++++++++++++ .../java/org/eclipse/jetty/servlets/DoSFilter.java | 16 +++-- .../java/org/eclipse/jetty/util/BufferUtil.java | 45 ++++++++++++- .../org/eclipse/jetty/util/BufferUtilTest.java | 42 +++++++++++++ 11 files changed, 263 insertions(+), 77 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java index 7ae190aab2..ec8ab2dc63 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java @@ -68,6 +68,13 @@ public interface HttpContent public interface Factory { - HttpContent getContent(String path) throws IOException; + /** + * @param path The path within the context to the resource + * @param maxBuffer The maximum buffer to allocated for this request. For cached content, a larger buffer may have + * previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls. + * @return A {@link HttpContent} + * @throws IOException + */ + HttpContent getContent(String path,int maxBuffer) throws IOException; } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java index 30d7ba5c26..c583ce2e50 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java @@ -125,7 +125,7 @@ public class ResourceHttpContent implements HttpContent @Override public ByteBuffer getDirectBuffer() { - if (_resource.length()<=0 || _maxBuffer<_resource.length()) + if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length()) return null; try { @@ -155,7 +155,7 @@ public class ResourceHttpContent implements HttpContent @Override public ByteBuffer getIndirectBuffer() { - if (_resource.length()<=0 || _maxBuffer<_resource.length()) + if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length()) return null; try { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index ca35951889..e1b8b6b133 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -765,6 +765,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable } break; } + ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null; if (buffer == null) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java index a209bf109f..6141c693a1 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java @@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.Comparator; import java.util.SortedSet; @@ -172,7 +173,7 @@ public class ResourceCache implements HttpContent.Factory public HttpContent lookup(String pathInContext) throws IOException { - return getContent(pathInContext); + return getContent(pathInContext,_maxCachedFileSize); } /* ------------------------------------------------------------ */ @@ -180,6 +181,8 @@ public class ResourceCache implements HttpContent.Factory * Get either a valid entry object or create a new one if possible. * * @param pathInContext The key into the cache + * @param maxBuffer The maximum buffer to allocated for this request. For cached content, a larger buffer may have + * previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls. * @return The entry matching pathInContext, or a new entry * if no matching entry was found. If the content exists but is not cachable, * then a {@link ResourceHttpContent} instance is return. If @@ -187,7 +190,7 @@ public class ResourceCache implements HttpContent.Factory * @throws IOException Problem loading the resource */ @Override - public HttpContent getContent(String pathInContext) + public HttpContent getContent(String pathInContext,int maxBufferSize) throws IOException { // Is the content in this cache? @@ -197,14 +200,14 @@ public class ResourceCache implements HttpContent.Factory // try loading the content from our factory. Resource resource=_factory.getResource(pathInContext); - HttpContent loaded = load(pathInContext,resource); + HttpContent loaded = load(pathInContext,resource,maxBufferSize); if (loaded!=null) return loaded; // Is the content in the parent cache? if (_parent!=null) { - HttpContent httpContent=_parent.lookup(pathInContext); + HttpContent httpContent=_parent.getContent(pathInContext,maxBufferSize); if (httpContent!=null) return httpContent; } @@ -225,14 +228,13 @@ public class ResourceCache implements HttpContent.Factory long len = resource.length(); // Will it fit in the cache? - return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize); + return (len>0 && (_useFileMappedBuffer || (len<_maxCachedFileSize && len<_maxCacheSize))); } /* ------------------------------------------------------------ */ - private HttpContent load(String pathInContext, Resource resource) + private HttpContent load(String pathInContext, Resource resource, int maxBufferSize) throws IOException { - if (resource==null || !resource.exists()) return null; @@ -256,7 +258,6 @@ public class ResourceCache implements HttpContent.Factory if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()=resource.lastModified()) - return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(),contentGz); + return new ResourceHttpContent(resource,mt,maxBufferSize,contentGz); // Is there a gzip resource? Resource resourceGz=_factory.getResource(pathInContextGz); if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()_maxCachedFiles) + shrinkCache(); + _lastAccessed=System.currentTimeMillis(); _etag=ResourceCache.this._etags?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null; @@ -487,8 +488,14 @@ public class ResourceCache implements HttpContent.Factory /* ------------------------------------------------------------ */ protected void invalidate() { - // Invalidate it - _cachedSize.addAndGet(-_contentLengthValue); + ByteBuffer indirect=_indirectBuffer.get(); + if (indirect!=null && _indirectBuffer.compareAndSet(indirect,null)) + _cachedSize.addAndGet(-BufferUtil.length(indirect)); + + ByteBuffer direct=_directBuffer.get(); + if (direct!=null && !BufferUtil.isMappedBuffer(direct) && _directBuffer.compareAndSet(direct,null)) + _cachedSize.addAndGet(-BufferUtil.length(direct)); + _cachedFiles.decrementAndGet(); _resource.close(); } @@ -507,7 +514,6 @@ public class ResourceCache implements HttpContent.Factory return _lastModified==null?null:_lastModified.getValue(); } - /* ------------------------------------------------------------ */ @Override public HttpField getContentType() @@ -569,7 +575,11 @@ public class ResourceCache implements HttpContent.Factory if (buffer2==null) LOG.warn("Could not load "+this); else if (_indirectBuffer.compareAndSet(null,buffer2)) + { buffer=buffer2; + if (_cachedSize.addAndGet(BufferUtil.length(buffer))>_maxCacheSize) + shrinkCache(); + } else buffer=_indirectBuffer.get(); } @@ -578,7 +588,6 @@ public class ResourceCache implements HttpContent.Factory return buffer.slice(); } - /* ------------------------------------------------------------ */ @Override public ByteBuffer getDirectBuffer() @@ -591,7 +600,12 @@ public class ResourceCache implements HttpContent.Factory if (buffer2==null) LOG.warn("Could not load "+this); else if (_directBuffer.compareAndSet(null,buffer2)) + { buffer=buffer2; + + if (!BufferUtil.isMappedBuffer(buffer) && _cachedSize.addAndGet(BufferUtil.length(buffer))>_maxCacheSize) + shrinkCache(); + } else buffer=_directBuffer.get(); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java index 2e0edde673..11d8810734 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java @@ -27,55 +27,47 @@ import org.eclipse.jetty.http.ResourceHttpContent; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; + +/** + * A HttpContent.Factory for transient content. The HttpContent's created by + * this factory are not intended to be cached, so memory limits for individual + * HttpOutput streams are enforced. + */ public class ResourceContentFactory implements Factory { private final ResourceFactory _factory; private final MimeTypes _mimeTypes; - private final int _maxBufferSize; private final boolean _gzip; - /* ------------------------------------------------------------ */ - public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, int maxBufferSize, boolean gzip) + public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, boolean gzip) { _factory=factory; _mimeTypes=mimeTypes; - _maxBufferSize=maxBufferSize; _gzip=gzip; } /* ------------------------------------------------------------ */ - /** Get a Entry from the cache. - * Get either a valid entry object or create a new one if possible. - * - * @param pathInContext The key into the cache - * @return The entry matching pathInContext, or a new entry - * if no matching entry was found. If the content exists but is not cachable, - * then a {@link ResourceHttpContent} instance is return. If - * the resource does not exist, then null is returned. - * @throws IOException Problem loading the resource - */ @Override - public HttpContent getContent(String pathInContext) + public HttpContent getContent(String pathInContext,int maxBufferSize) throws IOException { - // try loading the content from our factory. Resource resource=_factory.getResource(pathInContext); - HttpContent loaded = load(pathInContext,resource); + HttpContent loaded = load(pathInContext,resource,maxBufferSize); return loaded; } /* ------------------------------------------------------------ */ - private HttpContent load(String pathInContext, Resource resource) + private HttpContent load(String pathInContext, Resource resource, int maxBufferSize) throws IOException { if (resource==null || !resource.exists()) return null; if (resource.isDirectory()) - return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_maxBufferSize); + return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),maxBufferSize); // Look for a gzip resource or content String mt = _mimeTypes.getMimeByExtension(pathInContext); @@ -85,11 +77,11 @@ public class ResourceContentFactory implements Factory String pathInContextGz=pathInContext+".gz"; Resource resourceGz=_factory.getResource(pathInContextGz); if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()(); String otherGzipExtensions = getInitParameter("otherGzipFileExtensions"); if (otherGzipExtensions != null) @@ -460,7 +467,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory try { // Find the content - content=_contentFactory.getContent(pathInContext); + content=_contentFactory.getContent(pathInContext,response.getBufferSize()); if (LOG.isDebugEnabled()) LOG.info("content={}",content); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java index 0f740d9da9..13a54837e5 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -39,8 +40,10 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.eclipse.jetty.http.DateGenerator; +import org.eclipse.jetty.http.HttpContent; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.ResourceContentFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker; import org.eclipse.jetty.toolchain.test.FS; @@ -48,6 +51,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.resource.Resource; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; @@ -521,6 +525,45 @@ public class DefaultServletTest } } + @Test + public void testDirectFromResourceHttpContent() throws Exception + { + if (!OS.IS_LINUX) + return; + + testdir.ensureEmpty(); + File resBase = testdir.getPathFile("docroot").toFile(); + FS.ensureDirExists(resBase); + context.setBaseResource(Resource.newResource(resBase)); + + File index = new File(resBase, "index.html"); + createFile(index, "

Hello World

"); + + ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + defholder.setInitParameter("dirAllowed", "true"); + defholder.setInitParameter("redirectWelcome", "false"); + defholder.setInitParameter("useFileMappedBuffer", "true"); + defholder.setInitParameter("welcomeServlets", "exact"); + defholder.setInitParameter("gzip", "false"); + defholder.setInitParameter("resourceCache","resourceCache"); + + String response; + + response = connector.getResponses("GET /context/index.html HTTP/1.0\r\n\r\n"); + assertResponseContains("

Hello World

", response); + + ResourceContentFactory factory = (ResourceContentFactory)context.getServletContext().getAttribute("resourceCache"); + + HttpContent content = factory.getContent("/index.html",200); + ByteBuffer buffer = content.getDirectBuffer(); + Assert.assertTrue(buffer.isDirect()); + content = factory.getContent("/index.html",5); + buffer = content.getDirectBuffer(); + Assert.assertTrue(buffer==null); + } + + + @Test public void testRangeRequests() throws Exception { diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java index 8a13382865..aad404d5db 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java @@ -1107,10 +1107,10 @@ public class DoSFilter implements Filter { private static final long serialVersionUID = 3534663738034577872L; - protected transient final String _id; - protected transient final int _type; - protected transient final long[] _timestamps; - protected transient int _next; + protected final String _id; + protected final int _type; + protected final long[] _timestamps; + protected int _next; public RateTracker(String id, int type, int maxRequestsPerSecond) { @@ -1164,16 +1164,14 @@ public class DoSFilter implements Filter public void sessionWillPassivate(HttpSessionEvent se) { //take the tracker of the list of trackers (if its still there) - //and ensure that we take ourselves out of the session so we are not saved _rateTrackers.remove(_id); - se.getSession().removeAttribute(__TRACKER); - if (LOG.isDebugEnabled()) - LOG.debug("Value removed: {}", getId()); } public void sessionDidActivate(HttpSessionEvent se) { - LOG.warn("Unexpected session activation"); + RateTracker tracker = (RateTracker)se.getSession().getAttribute(__TRACKER); + if (tracker!=null) + _rateTrackers.put(tracker.getId(),tracker); } @Override diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java index 49377144e1..fa1ef983ab 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java @@ -19,17 +19,21 @@ package org.eclipse.jetty.util; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; +import java.lang.reflect.Field; import java.nio.Buffer; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; import java.util.Arrays; import org.eclipse.jetty.util.log.Log; @@ -903,12 +907,48 @@ public class BufferUtil public static ByteBuffer toMappedBuffer(File file) throws IOException { - try (RandomAccessFile raf = new RandomAccessFile(file, "r")) + try (FileChannel channel = FileChannel.open(file.toPath(),StandardOpenOption.READ)) { - return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()); + return channel.map(MapMode.READ_ONLY, 0, file.length()); } } + static final Field fdMappedByteBuffer; + static + { + Field fd = null; + try + { + fd=MappedByteBuffer.class.getDeclaredField("fd"); + fd.setAccessible(true); + } + catch(Exception e) + { + } + fdMappedByteBuffer=fd; + } + + public static boolean isMappedBuffer(ByteBuffer buffer) + { + if (!(buffer instanceof MappedByteBuffer)) + return false; + MappedByteBuffer mapped = (MappedByteBuffer) buffer; + + if (fdMappedByteBuffer!=null) + { + try + { + if (fdMappedByteBuffer.get(mapped) instanceof FileDescriptor) + return true; + } + catch(Exception e) + { + } + } + return false; + } + + public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException { int len=(int)resource.length(); @@ -1156,4 +1196,5 @@ public class BufferUtil + } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java index d2f8b24bec..9e44393d7c 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java @@ -21,13 +21,20 @@ package org.eclipse.jetty.util; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.charset.StandardCharsets; +import java.nio.file.OpenOption; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; @@ -335,4 +342,39 @@ public class BufferUtilTest BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out); assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true)); } + + @Test + public void testMappedFile() throws Exception + { + String data="Now is the time for all good men to come to the aid of the party"; + File file = File.createTempFile("test",".txt"); + file.deleteOnExit(); + try(FileWriter out = new FileWriter(file);) + { + out.write(data); + } + + ByteBuffer mapped = BufferUtil.toMappedBuffer(file); + assertEquals(data,BufferUtil.toString(mapped)); + assertTrue(BufferUtil.isMappedBuffer(mapped)); + + ByteBuffer direct = BufferUtil.allocateDirect(data.length()); + direct.clear(); + direct.put(data.getBytes(StandardCharsets.ISO_8859_1)); + direct.flip(); + assertEquals(data,BufferUtil.toString(direct)); + assertFalse(BufferUtil.isMappedBuffer(direct)); + + ByteBuffer slice = direct.slice(); + assertEquals(data,BufferUtil.toString(slice)); + assertFalse(BufferUtil.isMappedBuffer(slice)); + + ByteBuffer duplicate = direct.duplicate(); + assertEquals(data,BufferUtil.toString(duplicate)); + assertFalse(BufferUtil.isMappedBuffer(duplicate)); + + ByteBuffer readonly = direct.asReadOnlyBuffer(); + assertEquals(data,BufferUtil.toString(readonly)); + assertFalse(BufferUtil.isMappedBuffer(readonly)); + } } -- cgit v1.2.3