Skip to content

HTTP method support in MappedInterceptor #35273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import org.jspecify.annotations.Nullable;

import org.springframework.http.HttpMethod;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
Expand All @@ -48,6 +49,10 @@ public class InterceptorRegistration {

private @Nullable List<String> excludePatterns;

private @Nullable List<HttpMethod> includeHttpMethods;

private @Nullable List<HttpMethod> excludeHttpMethods;

private @Nullable PathMatcher pathMatcher;

private int order = 0;
Expand Down Expand Up @@ -106,6 +111,46 @@ public InterceptorRegistration excludePathPatterns(List<String> patterns) {
return this;
}

/**
* Add HTTP methods the interceptor should be included for.
* <p>Only requests with these HTTP methods will be intercepted.
* @since 7.0.x
*/
public InterceptorRegistration includeHttpMethods(HttpMethod... httpMethods) {
return includeHttpMethods(Arrays.asList(httpMethods));
}

/**
* List-based variant of {@link #includeHttpMethods(HttpMethod...)}.
* @since 7.0.x
*/
public InterceptorRegistration includeHttpMethods(List<HttpMethod> httpMethods) {
this.includeHttpMethods = (this.includeHttpMethods != null ?
this.includeHttpMethods : new ArrayList<>(httpMethods.size()));
this.includeHttpMethods.addAll(httpMethods);
return this;
}

/**
* Add HTTP methods the interceptor should be excluded from.
* <p>Requests with these HTTP methods will be ignored by the interceptor.
* @since 7.0.x
*/
public InterceptorRegistration excludeHttpMethods(HttpMethod... httpMethods){
return this.excludeHttpMethods(Arrays.asList(httpMethods));
}

/**
* List-based variant of {@link #excludeHttpMethods(HttpMethod...)}.
* @since 7.0.x
*/
public InterceptorRegistration excludeHttpMethods(List<HttpMethod> httpMethods){
this.excludeHttpMethods = (this.excludeHttpMethods != null ?
this.excludeHttpMethods : new ArrayList<>(httpMethods.size()));
this.excludeHttpMethods.addAll(httpMethods);
return this;
}

/**
* Configure the PathMatcher to use to match URL paths with against include
* and exclude patterns.
Expand Down Expand Up @@ -143,19 +188,32 @@ protected int getOrder() {
}

/**
* Build the underlying interceptor. If URL patterns are provided, the returned
* Build the underlying interceptor. If URL patterns or HTTP methods are provided, the returned
* type is {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}.
*/
@SuppressWarnings("removal")
protected Object getInterceptor() {

if (this.includePatterns == null && this.excludePatterns == null) {
if (this.includePatterns == null && this.excludePatterns == null && this.includeHttpMethods == null && this.excludeHttpMethods == null) {
return this.interceptor;
}

HttpMethod[] includeMethodsArray = (this.includeHttpMethods != null) ?
this.includeHttpMethods.toArray(new HttpMethod[0]) : null;

HttpMethod[] excludeMethodsArray = (this.excludeHttpMethods != null) ?
this.excludeHttpMethods.toArray(new HttpMethod[0]) : null;

String[] includePattersArray = StringUtils.toStringArray(this.includePatterns);

String[] excludePattersArray = StringUtils.toStringArray(this.excludePatterns);


MappedInterceptor mappedInterceptor = new MappedInterceptor(
StringUtils.toStringArray(this.includePatterns),
StringUtils.toStringArray(this.excludePatterns),
includePattersArray,
excludePattersArray,
includeMethodsArray,
excludeMethodsArray,
this.interceptor);

if (this.pathMatcher != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;

import org.springframework.http.HttpMethod;
import org.springframework.http.server.PathContainer;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
Expand Down Expand Up @@ -67,6 +68,10 @@ public final class MappedInterceptor implements HandlerInterceptor {

private final PatternAdapter @Nullable [] excludePatterns;

private final MethodAdapter @Nullable [] includeHttpMethods;

private final MethodAdapter @Nullable [] excludeHttpMethods;

private PathMatcher pathMatcher = defaultPathMatcher;

private final HandlerInterceptor interceptor;
Expand All @@ -78,58 +83,79 @@ public final class MappedInterceptor implements HandlerInterceptor {
* @param includePatterns patterns to which requests must match, or null to
* match all paths
* @param excludePatterns patterns to which requests must not match
* @param includeHttpMethods http methods to which request must match, or null to match all paths
* @param excludeHttpMethods http methods to which request must not match
* @param interceptor the target interceptor
* @param parser a parser to use to pre-parse patterns into {@link PathPattern};
* when not provided, {@link PathPatternParser#defaultInstance} is used.
* @since 5.3
*/
public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns,
public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, HttpMethod @Nullable [] includeHttpMethods, HttpMethod @Nullable [] excludeHttpMethods,
HandlerInterceptor interceptor, @Nullable PathPatternParser parser) {

this.includePatterns = PatternAdapter.initPatterns(includePatterns, parser);
this.excludePatterns = PatternAdapter.initPatterns(excludePatterns, parser);
this.includeHttpMethods = MethodAdapter.initHttpMethods(includeHttpMethods);
this.excludeHttpMethods = MethodAdapter.initHttpMethods(excludeHttpMethods);
this.interceptor = interceptor;
}


/**
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)}
* with include patterns only.
*/
public MappedInterceptor(String @Nullable [] includePatterns, HandlerInterceptor interceptor) {
this(includePatterns, null, interceptor);
this(includePatterns, null, null, null, interceptor);
}

/**
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)}
* with include methods only.
*/
public MappedInterceptor(HttpMethod @Nullable [] includeHttpMethods, HandlerInterceptor interceptor) {
this(null, null, includeHttpMethods, null, interceptor);
}

/**
* Variant of
* {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)}
* without a provided parser.
*/
public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns,
public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, HttpMethod @Nullable [] includeHttpMethods, HttpMethod @Nullable [] excludeHttpMethods,
HandlerInterceptor interceptor) {

this(includePatterns, excludePatterns, interceptor, null);
this(includePatterns, excludePatterns,includeHttpMethods,excludeHttpMethods, interceptor, null);
}

/**
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)}
* with a {@link WebRequestInterceptor} as the target.
*/
public MappedInterceptor(String @Nullable [] includePatterns, WebRequestInterceptor interceptor) {
this(includePatterns, null, interceptor);
this(includePatterns, null,null,null, interceptor);
}
/**
* Variant of
* {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)}
* with a {@link WebRequestInterceptor} as the target.
*/
public MappedInterceptor(HttpMethod @Nullable [] includeHttpMethods, WebRequestInterceptor interceptor) {
this(null, null,includeHttpMethods ,null, interceptor);
}

/**
* Variant of
* {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)}
* {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[] , HandlerInterceptor, PathPatternParser)}
* with a {@link WebRequestInterceptor} as the target.
*/
public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns,
public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, HttpMethod @Nullable [] includeHttpMethods, HttpMethod @Nullable [] excludeHttpMethods,
WebRequestInterceptor interceptor) {

this(includePatterns, excludePatterns, new WebRequestHandlerInterceptorAdapter(interceptor));
this(includePatterns, excludePatterns,includeHttpMethods,excludeHttpMethods, new WebRequestHandlerInterceptorAdapter(interceptor));
}


Expand Down Expand Up @@ -202,6 +228,7 @@ public PathMatcher getPathMatcher() {
*/
public boolean matches(HttpServletRequest request) {
Object path = ServletRequestPathUtils.getCachedPath(request);
HttpMethod method = HttpMethod.valueOf(request.getMethod());
if (this.pathMatcher != defaultPathMatcher) {
path = path.toString();
}
Expand All @@ -213,12 +240,45 @@ public boolean matches(HttpServletRequest request) {
}
}
}
if (ObjectUtils.isEmpty(this.includePatterns)) {
if (!ObjectUtils.isEmpty(this.excludeHttpMethods)) {
for (MethodAdapter adapter : this.excludeHttpMethods) {
if (adapter.match(method)){
return false;
}
}
}
if (ObjectUtils.isEmpty(this.includePatterns) && ObjectUtils.isEmpty(this.includeHttpMethods)) {
return true;
}
for (PatternAdapter adapter : this.includePatterns) {
if (adapter.match(path, isPathContainer, this.pathMatcher)) {
return true;
if (!ObjectUtils.isEmpty(this.includePatterns) && ObjectUtils.isEmpty(this.includeHttpMethods)) {
for (PatternAdapter adapter : this.includePatterns) {
if (adapter.match(path, isPathContainer, this.pathMatcher)) {
return true;
}
}
}
if (!ObjectUtils.isEmpty(this.includeHttpMethods) && ObjectUtils.isEmpty(this.includePatterns)) {
for (MethodAdapter adapter : this.includeHttpMethods) {
if (adapter.match(method)) {
return true;
}
}
}
if (!ObjectUtils.isEmpty(this.includePatterns) && !ObjectUtils.isEmpty(this.includeHttpMethods)) {
boolean match = false;
for (MethodAdapter methodAdapter : this.includeHttpMethods) {
if (methodAdapter.match(method)) {
match = true;
break;
}
}
if (!match) {
return false;
}
for (PatternAdapter pathAdapter : this.includePatterns) {
if (pathAdapter.match(path, isPathContainer, pathMatcher)) {
return true;
}
}
}
return false;
Expand Down Expand Up @@ -305,4 +365,40 @@ public boolean match(Object path, boolean isPathContainer, PathMatcher pathMatch
}
}

/**
* Adapts {@link HttpMethod} instances for internal matching purposes.
*
* <p>Encapsulates an {@link HttpMethod} and provides matching functionality.
* Also provides a utility method to initialize arrays of {@code MethodAdapter}
* instances from arrays of {@link HttpMethod}.</p>
*
* @since 7.0.x
*/
private static class MethodAdapter {

private final @Nullable HttpMethod httpMethod;

public MethodAdapter(@Nullable HttpMethod httpMethod) {
this.httpMethod = httpMethod;
}

public boolean match(HttpMethod method) {
return this.httpMethod == method;
}

public @Nullable HttpMethod getHttpMethod() {
return this.httpMethod;
}

private static MethodAdapter @Nullable [] initHttpMethods(HttpMethod @Nullable [] methods) {
if (ObjectUtils.isEmpty(methods)) {
return null;
}
return Arrays.stream(methods)
.map(MethodAdapter::new)
.toArray(MethodAdapter[]::new);
}

}

}
Loading