This is an automated email from the ASF dual-hosted git repository.
gus pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_10x by this push:
new 095ac315aee SOLR-18041 backport to 10x (#3984)
095ac315aee is described below
commit 095ac315aeefaf30367452807b19e8be019c4ed3
Author: Gus Heck <[email protected]>
AuthorDate: Thu Jan 1 21:20:39 2026 -0500
SOLR-18041 backport to 10x (#3984)
---
changelog/unreleased/SOLR-18041.yml | 8 +++
.../java/org/apache/solr/servlet/PathExcluder.java | 28 --------
.../apache/solr/servlet/PathExclusionFilter.java | 80 ++++++++++++++++++++++
.../java/org/apache/solr/servlet/ServletUtils.java | 44 ------------
.../apache/solr/servlet/SolrDispatchFilter.java | 13 +---
.../org/apache/solr/embedded/JettySolrRunner.java | 29 +++++++-
solr/webapp/web/WEB-INF/web.xml | 18 +++--
7 files changed, 129 insertions(+), 91 deletions(-)
diff --git a/changelog/unreleased/SOLR-18041.yml
b/changelog/unreleased/SOLR-18041.yml
new file mode 100644
index 00000000000..d36eaad3ee5
--- /dev/null
+++ b/changelog/unreleased/SOLR-18041.yml
@@ -0,0 +1,8 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: SOLR 18041 - Path exclusions for the admin UI are now defined in a
separate servlet filter
+type: other # added, changed, fixed, deprecated, removed, dependency_update,
security, other
+authors:
+ - name: Gus Heck
+links:
+ - name: SOLR-18041
+ url: https://issues.apache.org/jira/browse/SOLR-18041
diff --git a/solr/core/src/java/org/apache/solr/servlet/PathExcluder.java
b/solr/core/src/java/org/apache/solr/servlet/PathExcluder.java
deleted file mode 100644
index 47d5b5c9578..00000000000
--- a/solr/core/src/java/org/apache/solr/servlet/PathExcluder.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.servlet;
-
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * Denotes an object, usually a servlet that denies access to some paths based
on the supplied
- * patterns. Typically, this would be implemented via compiled regular
expressions.
- */
-public interface PathExcluder {
- void setExcludePatterns(List<Pattern> excludePatterns);
-}
diff --git
a/solr/core/src/java/org/apache/solr/servlet/PathExclusionFilter.java
b/solr/core/src/java/org/apache/solr/servlet/PathExclusionFilter.java
new file mode 100644
index 00000000000..87e1e195a21
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/servlet/PathExclusionFilter.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.servlet;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpFilter;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Filter to identify paths that should be processed by Jetty's
DefaultServlet. Typically, these
+ * paths contain static resources that need to be returned verbatim, with
appropriate content type,
+ * which Jetty will determine via
+ *
+ * <p>{@code
org.eclipse.jetty.http.MimeTypes#getMimeByExtension(java.lang.String)}
+ */
+public class PathExclusionFilter extends HttpFilter {
+
+ private List<Pattern> excludePatterns;
+
+ boolean shouldBeExcluded(HttpServletRequest request) {
+ String requestPath = ServletUtils.getPathAfterContext(request);
+ if (excludePatterns != null) {
+ return excludePatterns.stream().map(p ->
p.matcher(requestPath)).anyMatch(Matcher::lookingAt);
+ }
+ return false;
+ }
+
+ @Override
+ public void init(FilterConfig config) throws ServletException {
+ String patternConfig = config.getInitParameter("excludePatterns");
+ if (patternConfig != null) {
+ String[] excludeArray = patternConfig.split(",");
+ this.excludePatterns =
Arrays.stream(excludeArray).map(Pattern::compile).toList();
+ }
+ super.init(config);
+ }
+
+ @Override
+ protected void doFilter(HttpServletRequest req, HttpServletResponse res,
FilterChain chain)
+ throws IOException, ServletException {
+ if (shouldBeExcluded(req)) {
+ // N.B. "default" is the name for
org.eclipse.jetty.ee10.servlet.DefaultServlet
+ // configured in solr/server/etc/webdefault.xml if it doesn't exist
something is
+ // very wrong.
+ RequestDispatcher defaultServlet =
req.getServletContext().getNamedDispatcher("default");
+ if (defaultServlet == null) {
+ res.sendError(
+ 500,
+ "Server Misconfiguration: cannot find default servlet (normally
defined as org.eclipse.jetty.ee10.servlet.DefaultServlet in webdefault.xml)");
+ } else {
+ defaultServlet.forward(req, res);
+ }
+ } else {
+ chain.doFilter(req, res);
+ }
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/servlet/ServletUtils.java
b/solr/core/src/java/org/apache/solr/servlet/ServletUtils.java
index e02f6802f93..f63a16187d2 100644
--- a/solr/core/src/java/org/apache/solr/servlet/ServletUtils.java
+++ b/solr/core/src/java/org/apache/solr/servlet/ServletUtils.java
@@ -19,7 +19,6 @@ package org.apache.solr.servlet;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
-import jakarta.servlet.FilterChain;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
@@ -33,10 +32,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.Utils;
@@ -131,45 +126,6 @@ public abstract class ServletUtils {
};
}
- static boolean excludedPath(
- List<Pattern> excludePatterns,
- HttpServletRequest request,
- HttpServletResponse response,
- FilterChain chain)
- throws IOException, ServletException {
- String requestPath = getPathAfterContext(request);
- // No need to even create the HttpSolrCall object if this path is excluded.
- if (excludePatterns != null) {
- for (Pattern p : excludePatterns) {
- Matcher matcher = p.matcher(requestPath);
- if (matcher.lookingAt()) {
- if (chain != null) {
- chain.doFilter(request, response);
- }
- return true;
- }
- }
- }
- return false;
- }
-
- static boolean excludedPath(
- List<Pattern> excludePatterns, HttpServletRequest request,
HttpServletResponse response)
- throws IOException, ServletException {
- return excludedPath(excludePatterns, request, response, null);
- }
-
- static void configExcludes(PathExcluder excluder, String patternConfig) {
- if (patternConfig != null) {
- String[] excludeArray = patternConfig.split(",");
- List<Pattern> patterns = new ArrayList<>();
- excluder.setExcludePatterns(patterns);
- for (String element : excludeArray) {
- patterns.add(Pattern.compile(element));
- }
- }
- }
-
/**
* Enforces rate limiting for a request. Should be converted to a servlet
filter at some point.
* Currently, this is tightly coupled with request tracing which is not
ideal either.
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index 632c8aef504..aad2d353052 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -17,8 +17,6 @@
package org.apache.solr.servlet;
import static org.apache.solr.servlet.ServletUtils.closeShield;
-import static org.apache.solr.servlet.ServletUtils.configExcludes;
-import static org.apache.solr.servlet.ServletUtils.excludedPath;
import static org.apache.solr.util.tracing.TraceUtils.getSpan;
import static org.apache.solr.util.tracing.TraceUtils.setTracer;
@@ -67,7 +65,7 @@ import org.slf4j.LoggerFactory;
// servlets that are more focused in scope. This should become possible now
that we have a
// ServletContextListener for startup/shutdown of CoreContainer that sets up a
service from which
// things like CoreContainer can be requested. (or better yet injected)
-public class SolrDispatchFilter extends HttpFilter implements PathExcluder {
+public class SolrDispatchFilter extends HttpFilter {
private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private CoreContainerProvider containerProvider;
@@ -78,11 +76,6 @@ public class SolrDispatchFilter extends HttpFilter
implements PathExcluder {
private HttpSolrCallFactory solrCallFactory;
- @Override
- public void setExcludePatterns(List<Pattern> excludePatterns) {
- this.excludePatterns = excludePatterns;
- }
-
private List<Pattern> excludePatterns;
public final boolean isV2Enabled = V2ApiUtils.isEnabled();
@@ -133,7 +126,6 @@ public class SolrDispatchFilter extends HttpFilter
implements PathExcluder {
log.trace("SolrDispatchFilter.init(): {}",
this.getClass().getClassLoader());
}
- configExcludes(this, config.getInitParameter("excludePatterns"));
} catch (Throwable t) {
// catch this so our filter still works
log.error("Could not start Dispatch Filter.", t);
@@ -157,9 +149,6 @@ public class SolrDispatchFilter extends HttpFilter
implements PathExcluder {
"Set the thread contextClassLoader for all 3rd party dependencies
that we cannot control")
public void doFilter(HttpServletRequest request, HttpServletResponse
response, FilterChain chain)
throws IOException, ServletException {
- if (excludedPath(excludePatterns, request, response, chain)) {
- return;
- }
try (var mdcSnapshot = MDCSnapshot.create()) {
assert null != mdcSnapshot; // prevent compiler warning
diff --git
a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
index 522ec25491e..982fc98c135 100644
--- a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
+++ b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
@@ -61,6 +61,7 @@ import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.servlet.CoreContainerProvider;
+import org.apache.solr.servlet.PathExclusionFilter;
import org.apache.solr.servlet.SolrDispatchFilter;
import org.apache.solr.util.SocketProxy;
import org.apache.solr.util.TimeOut;
@@ -109,8 +110,9 @@ public class JettySolrRunner {
private Server server;
- volatile FilterHolder dispatchFilter;
volatile FilterHolder debugFilter;
+ volatile FilterHolder pathExcludeFilter;
+ volatile FilterHolder dispatchFilter;
private int jettyPort = -1;
@@ -398,14 +400,35 @@ public class JettySolrRunner {
for (Map.Entry<ServletHolder, String> entry :
config.extraServlets.entrySet()) {
root.addServlet(entry.getKey(), entry.getValue());
}
+ // TODO: This needs to be driven by a parsing of web.xml eventually
+ // though we still want to avoid classpath scanning.
+
+ // this path excludes filter isn't actually necessary for any tests, but
it's being
+ // added for parity with the live application.
+ pathExcludeFilter =
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
+ pathExcludeFilter.setHeldClass(PathExclusionFilter.class);
+ pathExcludeFilter.setInitParameter("excludePatterns", excludePatterns);
+
+ // This is our main workhorse
dispatchFilter =
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
dispatchFilter.setHeldClass(SolrDispatchFilter.class);
- dispatchFilter.setInitParameter("excludePatterns", excludePatterns);
+
// Map dispatchFilter in same path as in web.xml
+ root.addFilter(pathExcludeFilter, "/*",
EnumSet.of(DispatcherType.REQUEST));
root.addFilter(dispatchFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
// Default servlet as a fall-through
- root.addServlet(Servlet404.class, "/");
+ ServletHolder defaultHolder =
root.getServletHandler().newServletHolder(Source.EMBEDDED);
+
+ // considered adding DefaultServlet.class here but perhaps that might
grant our unit tests
+ // the power to serve static resources on the build machines? Not sure,
so I'll just give a
+ // name to our existing hack. The tests passed without this, but it will
ensure that if anyone
+ // ever hits the PathExcludeFilter in the unit test they get a 404 as
before not a 500
+ defaultHolder.setHeldClass(Servlet404.class);
+ defaultHolder.setName("default");
+ root.addServlet(defaultHolder, "/");
+
+ // TODO: end area that should be driven by web.xml and webdefault.xml
chain = root;
}
diff --git a/solr/webapp/web/WEB-INF/web.xml b/solr/webapp/web/WEB-INF/web.xml
index 37a68b4692b..aa3a192862d 100644
--- a/solr/webapp/web/WEB-INF/web.xml
+++ b/solr/webapp/web/WEB-INF/web.xml
@@ -26,11 +26,11 @@
</listener>
<!-- Any path (name) registered in solrconfig.xml will be sent to that
filter -->
<filter>
- <filter-name>SolrRequestFilter</filter-name>
- <filter-class>org.apache.solr.servlet.SolrDispatchFilter</filter-class>
+ <filter-name>PathExclusionsFilter</filter-name>
+ <filter-class>org.apache.solr.servlet.PathExclusionFilter</filter-class>
<!--
- Exclude patterns is a list of directories that would be short circuited by
the
- SolrDispatchFilter. It includes all Admin UI related static content.
+ Exclude patterns is a list of directories that would be short-circuited by
this
+ Filter. It includes all Admin UI related static content.
NOTE: It is NOT a pattern but only matches the start of the HTTP
ServletPath.
-->
<init-param>
@@ -39,6 +39,16 @@
</init-param>
</filter>
+ <filter-mapping>
+ <filter-name>PathExclusionsFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <filter>
+ <filter-name>SolrRequestFilter</filter-name>
+ <filter-class>org.apache.solr.servlet.SolrDispatchFilter</filter-class>
+ </filter>
+
<filter-mapping>
<filter-name>SolrRequestFilter</filter-name>
<url-pattern>/*</url-pattern>