Implementation Principle of Spring AntPath Matcher

Keywords: JSP Apache REST Spring

Links to the original text: https://my.oschina.net/iqoFil/blog/221620

 

Recently, looking at the Spring source code, the AntPatternMatcher class has appeared many times in the source code, which is used to match URL s.

 

 

 

About this kind of introduction, some of the code is borrowed from Apache Ant.

 

 

 

PathMatcher implementation for Ant-style path patterns. Examples are provided below.

Part of this mapping code has been kindly borrowed from Apache Ant.

The mapping matches URLs using the following rules:

  • ? matches one character
  • * matches zero or more characters
  • ** matches zero or more 'directories' in a path

Some examples:

  • com/t?st.jsp - matches com/test.jsp but also com/tast.jsp or com/txst.jsp
  • com/*.jsp - matches all .jsp files in the com directory
  • com/**/test.jsp - matches all test.jsp files underneath the com path
  • org/springframework/**/*.jsp - matches all .jsp files underneath the org/springframework path
  • org/**/servlet/bla.jsp - matches org/springframework/servlet/bla.jsp but also org/springframework/testing/servlet/bla.jsp and org/servlet/bla.jsp



Look at its core approach:


/**
	 * Actually match the given <code>path</code> against the given <code>pattern</code>.
	 * @param pattern the pattern to match against
	 * @param path the path String to test
	 * @param fullMatch whether a full pattern match is required (else a pattern match
	 * as far as the given base path goes is sufficient)
	 * @return <code>true</code> if the supplied <code>path</code> matched, <code>false</code> if it didn't
	 */
	protected boolean doMatch(String pattern, String path, boolean fullMatch,
			Map<String, String> uriTemplateVariables) {

		if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
			return false;
		}

		String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
		String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);

		int pattIdxStart = 0;
		int pattIdxEnd = pattDirs.length - 1;
		int pathIdxStart = 0;
		int pathIdxEnd = pathDirs.length - 1;

		// Match all elements up to the first **
		while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxStart];
			if ("**".equals(patDir)) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
				return false;
			}
			pattIdxStart++;
			pathIdxStart++;
		}

		if (pathIdxStart > pathIdxEnd) {
			// Path is exhausted, only match if rest of pattern is * or **'s
			if (pattIdxStart > pattIdxEnd) {
/**            /a/b/c  and /a/b/c/ ==>false                            **/
				return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
						!path.endsWith(this.pathSeparator));
			}
			if (!fullMatch) {
				return true;
			}
/**  /a/b/c/*  and /a/b/c/  ==>true    /a/b/c/* and /a/b/c   ==> false **/
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
				return true;
			} /**  /a/b/c/**/**/  and /a/b/c    ==> true      /a/b/c/**/a/   and /a/b/c  ==>false         **/
			for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
				if (!pattDirs[i].equals("**")) {
					return false;
				}
			}
			return true;
		}
		else if (pattIdxStart > pattIdxEnd) {
			// String not exhausted, but pattern is. Failure.
			return false;
		}
		else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
			// Path start definitely matches due to "**" part in pattern.
			return true;
		}

		// up to last '**'
		while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxEnd];
			if (patDir.equals("**")) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
				return false;
			}
			pattIdxEnd--;
			pathIdxEnd--;
		}
		if (pathIdxStart > pathIdxEnd) {  /**     /**/a/b/c and  /a/b/c  ==> true **/
			// String is exhausted
			for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
				if (!pattDirs[i].equals("**")) {
					return false;
				}
			}
			return true;
		}         /**             /a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q  and  /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> true **/  
		while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			int patIdxTmp = -1;
			for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
				if (pattDirs[i].equals("**")) {
					patIdxTmp = i;
					break;
				}
			}
			if (patIdxTmp == pattIdxStart + 1) {
				// '**/**' situation, so skip one
				pattIdxStart++;
				continue;
			}
			// Find the pattern between padIdxStart & padIdxTmp in str between
			// strIdxStart & strIdxEnd
			int patLength = (patIdxTmp - pattIdxStart - 1);
			int strLength = (pathIdxEnd - pathIdxStart + 1);
			int foundIdx = -1;

			strLoop:
			for (int i = 0; i <= strLength - patLength; i++) {
				for (int j = 0; j < patLength; j++) {
					String subPat = pattDirs[pattIdxStart + j + 1];
					String subStr = pathDirs[pathIdxStart + i + j];
					if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
						continue strLoop;
					}
				}
				foundIdx = pathIdxStart + i;
				break;
			}

			if (foundIdx == -1) {
				return false;
			}

			pattIdxStart = patIdxTmp;
			pathIdxStart = foundIdx + patLength;
		}

		for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
			if (!pattDirs[i].equals("**")) {
				return false;
			}
		}

		return true;
	}
 

Its realization principle is as follows:

1) First translate Pattern s and matching characters into String Array, with the delimiter "/"

For example: / a/**/b/c/d/=> ["a", **","b","c","d"]

String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
		String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);

		int pattIdxStart = 0;
		int pattIdxEnd = pattDirs.length - 1;
		int pathIdxStart = 0;
		int pathIdxEnd = pathDirs.length - 1;
 


2) Match the element before ** in Pattern s first. The element here refers to the element of two arrays.

   // Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxStart];
			if ("**".equals(patDir)) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
				return false;
			}
			pattIdxStart++;
			pathIdxStart++;
		}

 

The conditions for jumping out of this loop are:

 

PattIdxStart > pattIdxEnd (No **)

 

Explain that Patterns overflow in the loop before the ** is found, that is, Patterns are shorter than Path's length, and the result must not match and should be returned to false directly.

 

 

else if (pattIdxStart > pattIdxEnd) {
			// String not exhausted, but pattern is. Failure.
			return false;
		}
 
2. pathIdxStart > pathIdxEnd (No "**")
   
Path overflows in the loop before ** is found, that is, Path is shorter than Pattern s, or both are the same length.
If the length is the same, then compare whether the last character of the two is equal, because the last character'/'will be omitted when converted to Array, so element matching of the array does not necessarily mean that the last character will be equal, so we need to judge whether it is the same, if it is the same, return true, if it is not the same, then return. Back to false,

The second case is that Patterns are longer than Path, and there are only two successful matching scenarios. One is that Patterns end with * or **, where if
 
Patterns end with *, Patterns must be only one element larger than Path, and the last character of Path should be "/", because * only represents 0-n characters, and does not mean "Directory". Path and Patterns'Directory must have the same depth, so if Path ends with "/" and P Attrn returns true if it has only one * sign less than Path's element

If Pattern s have more than one element than Path, then the following element must be "**" or false will be returned

 
 
     if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
			if (pattIdxStart > pattIdxEnd) {
				return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
						!path.endsWith(this.pathSeparator));
			}
			if (!fullMatch) {
				return true;
			}
			if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
				return true;
			}
			for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
				if (!pattDirs[i].equals("**")) {
					return false;
				}
			}
			return true;
		}
		else if (pattIdxStart > pattIdxEnd) {
			// String not exhausted, but pattern is. Failure.
			return false;
		}
		else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
			// Path start definitely matches due to "**" part in pattern.
			return true;
		}
 
3) Then match the elements after Pattern "**" and return false if the matching is unsuccessful.
A special case is that the Path string exits early before the ** sign is found.
Special cases/**/**/**/**/a/b/c and/a/b/c==>true
/ **/a/b/**/a/b/c and/a/b/c==> false
Determine whether Pattern IdxStart to pattIdxEnd are all ** numbers, and if not, return false.
    // up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			String patDir = pattDirs[pattIdxEnd];
			if (patDir.equals("**")) {
				break;
			}
			if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
				return false;
			}
			pattIdxEnd--;
			pathIdxEnd--;
		}
		if (pathIdxStart > pathIdxEnd) {
			// String is exhausted
			for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
				if (!pattDirs[i].equals("**")) {
					return false;
				}
			}
			return true;
		}
 

In the second case, neither Pattern s nor Path s overflow out of the loop in advance, this is the case.

 /a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q and           /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> true 

 

At this time, we need to deal with multiple ** numbers, segment processing, character matching between ** numbers, and return false if the characters between ** numbers do not match.

 

Paragraph 1:

/a/b/c/d/**/**/e/f/g/h/**/

and  

a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q 

 

==>true

 

 

The second paragraph:

/**/i/j/k/**/l

 

and 

 

 

/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q

 

==>true

 

 

The third paragraph:

 

/**/l/m/n/**/

 

 

/a/b/l/m/n/a/b/o/p/q 

 

==>true

 

 

 

while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
			int patIdxTmp = -1;
			for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
				if (pattDirs[i].equals("**")) {
					patIdxTmp = i;
					break;
				}
			}
			if (patIdxTmp == pattIdxStart + 1) {
				// '**/**' situation, so skip one
				pattIdxStart++;
				continue;
			}
			// Find the pattern between padIdxStart & padIdxTmp in str between
			// strIdxStart & strIdxEnd
			int patLength = (patIdxTmp - pattIdxStart - 1);
			int strLength = (pathIdxEnd - pathIdxStart + 1);
			int foundIdx = -1;

			strLoop:
			for (int i = 0; i <= strLength - patLength; i++) {
				for (int j = 0; j < patLength; j++) {
					String subPat = pattDirs[pattIdxStart + j + 1];
					String subStr = pathDirs[pathIdxStart + i + j];
					if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
						continue strLoop;
					}
				}
				foundIdx = pathIdxStart + i;
				break;
			}

			if (foundIdx == -1) {
				return false;
			}

			pattIdxStart = patIdxTmp;
			pathIdxStart = foundIdx + patLength;
		}
 

 

 

 

 

 

 

 

Reprinted in: https://my.oschina.net/iqoFil/blog/221620

Posted by youdontmeanmuch on Wed, 18 Sep 2019 00:29:26 -0700