codeconsole commented on code in PR #16499:
URL: https://github.com/apache/grails-core/pull/16499#discussion_r4178176905
##########
grails-forge/grails-forge-core/src/main/resources/gsp/index.gsp:
##########
@@ -577,25 +577,57 @@
<g:def type="List" var="mimeTypeProviders"
value="${applicationContext.getBeansOfType(grails.web.mime.MimeTypeProvider)
.entrySet().toList().sort {
it.key.toLowerCase() }}"/>
- <%-- The filters still on the call stack ARE this request's
pipeline, in
- execution order: walk the reversed stack, keep Filter
classes, collapse
- the extra frames a filter contributes through its
abstract bases, and
- number what remains. No registry can report this actual
order. --%>
- <g:def type="List" var="requestFilters"
-
value="${Thread.currentThread().stackTrace.toList().reverse()
- .findResults { ste ->
- def cls = null
- try { cls = Class.forName(ste.className,
false, Thread.currentThread().contextClassLoader) } catch (Throwable ignored) {
}
- (cls != null &&
jakarta.servlet.Filter.isAssignableFrom(cls)) ? cls : null
- }
- .inject([]) { acc, cls ->
- Class prev = acc ? (Class) acc[-1] : null
- if (prev == cls) { return acc }
- if (prev != null &&
prev.isAssignableFrom(cls)) { acc[-1] = cls; return acc }
- if (prev != null &&
cls.isAssignableFrom(prev)) { return acc }
- acc << cls
- }
- .unique()}"/>
+ <%-- Every filter running in the container, in chain order.
Tomcat's filter
+ maps are the exact chain: FilterRegistrationBeans, plain
Filter beans Boot
+ adapted and container-added filters like WsFilter alike.
Elsewhere, replay
+ the order Boot registers its filters in
(ServletContextInitializerBeans,
+ disabled ones skipped, matchAfter ones last), then append
what else the
+ Servlet API reports, unnumbered because no portable API
exposes its
+ position. Either way the spec chains URL-pattern matches
before
+ servlet-name matches, hence the stable sorts. --%>
+ <g:set var="tomcatContext"
+ value="${ { ->
+ try {
+
applicationContext.webServer.tomcat.host.findChildren().find { it.path ==
request.contextPath }
+ } catch (Throwable ignored) {
+ null
+ }
+ }() }"/>
+ <g:def type="List" var="servletFilters"
+ value="${tomcatContext
+ ? tomcatContext.findFilterMaps().toList()
+ .inject([:]) { Map acc, fm ->
Review Comment:
Fixed in 6eacfd59f6. The panel now lists one row per filter map instead of
grouping by filter: the URL-pattern maps first, then the servlet-name maps
under their own heading, numbered in the order Tomcat walks them. With maps `A
-> dispatcherServlet`, `B -> /*`, `A -> /*` it lists B's URL map before A's,
and A again under servlet names. A filter mapped more than once can run at
different positions for different requests, so the numbers are per mapping
rather than per filter. `WelcomePageFiltersSpec` in the new `welcome-page` test
example renders exactly that case on Tomcat.
##########
grails-profiles/web/skeleton/grails-app/views/index.gsp:
##########
@@ -577,25 +577,57 @@
<g:def type="List" var="mimeTypeProviders"
value="${applicationContext.getBeansOfType(grails.web.mime.MimeTypeProvider)
.entrySet().toList().sort {
it.key.toLowerCase() }}"/>
- <%-- The filters still on the call stack ARE this request's
pipeline, in
- execution order: walk the reversed stack, keep Filter
classes, collapse
- the extra frames a filter contributes through its
abstract bases, and
- number what remains. No registry can report this actual
order. --%>
- <g:def type="List" var="requestFilters"
-
value="${Thread.currentThread().stackTrace.toList().reverse()
- .findResults { ste ->
- def cls = null
- try { cls = Class.forName(ste.className,
false, Thread.currentThread().contextClassLoader) } catch (Throwable ignored) {
}
- (cls != null &&
jakarta.servlet.Filter.isAssignableFrom(cls)) ? cls : null
- }
- .inject([]) { acc, cls ->
- Class prev = acc ? (Class) acc[-1] : null
- if (prev == cls) { return acc }
- if (prev != null &&
prev.isAssignableFrom(cls)) { acc[-1] = cls; return acc }
- if (prev != null &&
cls.isAssignableFrom(prev)) { return acc }
- acc << cls
- }
- .unique()}"/>
+ <%-- Every filter running in the container, in chain order.
Tomcat's filter
+ maps are the exact chain: FilterRegistrationBeans, plain
Filter beans Boot
+ adapted and container-added filters like WsFilter alike.
Elsewhere, replay
+ the order Boot registers its filters in
(ServletContextInitializerBeans,
+ disabled ones skipped, matchAfter ones last), then append
what else the
+ Servlet API reports, unnumbered because no portable API
exposes its
+ position. Either way the spec chains URL-pattern matches
before
+ servlet-name matches, hence the stable sorts. --%>
+ <g:set var="tomcatContext"
+ value="${ { ->
+ try {
+
applicationContext.webServer.tomcat.host.findChildren().find { it.path ==
request.contextPath }
+ } catch (Throwable ignored) {
+ null
+ }
+ }() }"/>
+ <g:def type="List" var="servletFilters"
+ value="${tomcatContext
+ ? tomcatContext.findFilterMaps().toList()
+ .inject([:]) { Map acc, fm ->
Review Comment:
Fixed in the same commit, 6eacfd59f6. The two copies are still identical.
##########
grails-forge/grails-forge-core/src/main/resources/gsp/index.gsp:
##########
@@ -577,25 +577,57 @@
<g:def type="List" var="mimeTypeProviders"
value="${applicationContext.getBeansOfType(grails.web.mime.MimeTypeProvider)
.entrySet().toList().sort {
it.key.toLowerCase() }}"/>
- <%-- The filters still on the call stack ARE this request's
pipeline, in
- execution order: walk the reversed stack, keep Filter
classes, collapse
- the extra frames a filter contributes through its
abstract bases, and
- number what remains. No registry can report this actual
order. --%>
- <g:def type="List" var="requestFilters"
-
value="${Thread.currentThread().stackTrace.toList().reverse()
- .findResults { ste ->
- def cls = null
- try { cls = Class.forName(ste.className,
false, Thread.currentThread().contextClassLoader) } catch (Throwable ignored) {
}
- (cls != null &&
jakarta.servlet.Filter.isAssignableFrom(cls)) ? cls : null
- }
- .inject([]) { acc, cls ->
- Class prev = acc ? (Class) acc[-1] : null
- if (prev == cls) { return acc }
- if (prev != null &&
prev.isAssignableFrom(cls)) { acc[-1] = cls; return acc }
- if (prev != null &&
cls.isAssignableFrom(prev)) { return acc }
- acc << cls
- }
- .unique()}"/>
+ <%-- Every filter running in the container, in chain order.
Tomcat's filter
+ maps are the exact chain: FilterRegistrationBeans, plain
Filter beans Boot
+ adapted and container-added filters like WsFilter alike.
Elsewhere, replay
+ the order Boot registers its filters in
(ServletContextInitializerBeans,
+ disabled ones skipped, matchAfter ones last), then append
what else the
+ Servlet API reports, unnumbered because no portable API
exposes its
+ position. Either way the spec chains URL-pattern matches
before
+ servlet-name matches, hence the stable sorts. --%>
+ <g:set var="tomcatContext"
+ value="${ { ->
+ try {
+
applicationContext.webServer.tomcat.host.findChildren().find { it.path ==
request.contextPath }
+ } catch (Throwable ignored) {
+ null
+ }
+ }() }"/>
+ <g:def type="List" var="servletFilters"
+ value="${tomcatContext
+ ? tomcatContext.findFilterMaps().toList()
+ .inject([:]) { Map acc, fm ->
+ Map row =
acc.computeIfAbsent(fm.filterName) { n ->
+ [name: n, className:
tomcatContext.findFilterDef(n)?.filterClass ?: '', urlPatterns: [], mappings:
[], ordered: true]
+ }
+ row.urlPatterns.addAll(fm.URLPatterns)
+ row.mappings.addAll(fm.URLPatterns)
+ row.mappings.addAll(fm.servletNames)
+ acc
+ }
+ .values().toList()
+ .sort { it.urlPatterns ? 0 : 1 }
+ : { ->
+ List springFilters = new
org.springframework.boot.web.servlet.ServletContextInitializerBeans(
+
(org.springframework.beans.factory.ListableBeanFactory)
applicationContext).toList()
+ .findAll { it instanceof
org.springframework.boot.web.servlet.AbstractFilterRegistrationBean &&
it.enabled }
+ .collect { initializer ->
+ def rb =
(org.springframework.boot.web.servlet.AbstractFilterRegistrationBean)
initializer
+ List servletNames =
(rb.servletNames as List) + rb.servletRegistrationBeans*.servletName
+ List urlPatterns =
(rb.urlPatterns || servletNames) ? rb.urlPatterns as List : ['/*']
+ [name: rb.filterName,
className: rb.filter?.getClass()?.name ?: '', urlPatterns: urlPatterns,
+ mappings: urlPatterns +
servletNames, matchAfter: rb.matchAfter, ordered: true]
+ }
+ .sort { (it.urlPatterns ? 0 : 2) +
(it.matchAfter ? 1 : 0) }
Review Comment:
Fixed in 6eacfd59f6. Nothing is numbered off Tomcat anymore. The panel lists
Spring Boot's enabled registrations in registration order, then the container's
other filters, under a separate description (`welcome.filters.unordered`)
saying that is not necessarily the order they run.
`WelcomePageFiltersJettySpec` in the new `welcome-page-jetty` test example
checks it on Jetty.
##########
grails-profiles/web/skeleton/grails-app/views/index.gsp:
##########
@@ -577,25 +577,57 @@
<g:def type="List" var="mimeTypeProviders"
value="${applicationContext.getBeansOfType(grails.web.mime.MimeTypeProvider)
.entrySet().toList().sort {
it.key.toLowerCase() }}"/>
- <%-- The filters still on the call stack ARE this request's
pipeline, in
- execution order: walk the reversed stack, keep Filter
classes, collapse
- the extra frames a filter contributes through its
abstract bases, and
- number what remains. No registry can report this actual
order. --%>
- <g:def type="List" var="requestFilters"
-
value="${Thread.currentThread().stackTrace.toList().reverse()
- .findResults { ste ->
- def cls = null
- try { cls = Class.forName(ste.className,
false, Thread.currentThread().contextClassLoader) } catch (Throwable ignored) {
}
- (cls != null &&
jakarta.servlet.Filter.isAssignableFrom(cls)) ? cls : null
- }
- .inject([]) { acc, cls ->
- Class prev = acc ? (Class) acc[-1] : null
- if (prev == cls) { return acc }
- if (prev != null &&
prev.isAssignableFrom(cls)) { acc[-1] = cls; return acc }
- if (prev != null &&
cls.isAssignableFrom(prev)) { return acc }
- acc << cls
- }
- .unique()}"/>
+ <%-- Every filter running in the container, in chain order.
Tomcat's filter
+ maps are the exact chain: FilterRegistrationBeans, plain
Filter beans Boot
+ adapted and container-added filters like WsFilter alike.
Elsewhere, replay
+ the order Boot registers its filters in
(ServletContextInitializerBeans,
+ disabled ones skipped, matchAfter ones last), then append
what else the
+ Servlet API reports, unnumbered because no portable API
exposes its
+ position. Either way the spec chains URL-pattern matches
before
+ servlet-name matches, hence the stable sorts. --%>
+ <g:set var="tomcatContext"
+ value="${ { ->
+ try {
+
applicationContext.webServer.tomcat.host.findChildren().find { it.path ==
request.contextPath }
+ } catch (Throwable ignored) {
+ null
+ }
+ }() }"/>
+ <g:def type="List" var="servletFilters"
+ value="${tomcatContext
+ ? tomcatContext.findFilterMaps().toList()
+ .inject([:]) { Map acc, fm ->
+ Map row =
acc.computeIfAbsent(fm.filterName) { n ->
+ [name: n, className:
tomcatContext.findFilterDef(n)?.filterClass ?: '', urlPatterns: [], mappings:
[], ordered: true]
+ }
+ row.urlPatterns.addAll(fm.URLPatterns)
+ row.mappings.addAll(fm.URLPatterns)
+ row.mappings.addAll(fm.servletNames)
+ acc
+ }
+ .values().toList()
+ .sort { it.urlPatterns ? 0 : 1 }
+ : { ->
+ List springFilters = new
org.springframework.boot.web.servlet.ServletContextInitializerBeans(
+
(org.springframework.beans.factory.ListableBeanFactory)
applicationContext).toList()
+ .findAll { it instanceof
org.springframework.boot.web.servlet.AbstractFilterRegistrationBean &&
it.enabled }
+ .collect { initializer ->
+ def rb =
(org.springframework.boot.web.servlet.AbstractFilterRegistrationBean)
initializer
+ List servletNames =
(rb.servletNames as List) + rb.servletRegistrationBeans*.servletName
+ List urlPatterns =
(rb.urlPatterns || servletNames) ? rb.urlPatterns as List : ['/*']
+ [name: rb.filterName,
className: rb.filter?.getClass()?.name ?: '', urlPatterns: urlPatterns,
+ mappings: urlPatterns +
servletNames, matchAfter: rb.matchAfter, ordered: true]
+ }
+ .sort { (it.urlPatterns ? 0 : 2) +
(it.matchAfter ? 1 : 0) }
Review Comment:
Same fix, 6eacfd59f6. The copies are identical.
##########
grails-forge/grails-forge-core/src/main/resources/gsp/index.gsp:
##########
@@ -577,25 +577,57 @@
<g:def type="List" var="mimeTypeProviders"
value="${applicationContext.getBeansOfType(grails.web.mime.MimeTypeProvider)
.entrySet().toList().sort {
it.key.toLowerCase() }}"/>
- <%-- The filters still on the call stack ARE this request's
pipeline, in
- execution order: walk the reversed stack, keep Filter
classes, collapse
- the extra frames a filter contributes through its
abstract bases, and
- number what remains. No registry can report this actual
order. --%>
- <g:def type="List" var="requestFilters"
-
value="${Thread.currentThread().stackTrace.toList().reverse()
- .findResults { ste ->
- def cls = null
- try { cls = Class.forName(ste.className,
false, Thread.currentThread().contextClassLoader) } catch (Throwable ignored) {
}
- (cls != null &&
jakarta.servlet.Filter.isAssignableFrom(cls)) ? cls : null
- }
- .inject([]) { acc, cls ->
- Class prev = acc ? (Class) acc[-1] : null
- if (prev == cls) { return acc }
- if (prev != null &&
prev.isAssignableFrom(cls)) { acc[-1] = cls; return acc }
- if (prev != null &&
cls.isAssignableFrom(prev)) { return acc }
- acc << cls
- }
- .unique()}"/>
+ <%-- Every filter running in the container, in chain order.
Tomcat's filter
+ maps are the exact chain: FilterRegistrationBeans, plain
Filter beans Boot
+ adapted and container-added filters like WsFilter alike.
Elsewhere, replay
+ the order Boot registers its filters in
(ServletContextInitializerBeans,
+ disabled ones skipped, matchAfter ones last), then append
what else the
+ Servlet API reports, unnumbered because no portable API
exposes its
+ position. Either way the spec chains URL-pattern matches
before
+ servlet-name matches, hence the stable sorts. --%>
+ <g:set var="tomcatContext"
+ value="${ { ->
+ try {
+
applicationContext.webServer.tomcat.host.findChildren().find { it.path ==
request.contextPath }
+ } catch (Throwable ignored) {
+ null
+ }
+ }() }"/>
+ <g:def type="List" var="servletFilters"
+ value="${tomcatContext
+ ? tomcatContext.findFilterMaps().toList()
+ .inject([:]) { Map acc, fm ->
+ Map row =
acc.computeIfAbsent(fm.filterName) { n ->
+ [name: n, className:
tomcatContext.findFilterDef(n)?.filterClass ?: '', urlPatterns: [], mappings:
[], ordered: true]
+ }
+ row.urlPatterns.addAll(fm.URLPatterns)
+ row.mappings.addAll(fm.URLPatterns)
+ row.mappings.addAll(fm.servletNames)
Review Comment:
Fixed in 6eacfd59f6. The Tomcat rows read `matchAllUrlPatterns` and
`matchAllServletNames` and show the mapping as `*`, so a match-all URL mapping
is listed with the URL-pattern maps. The Tomcat test example registers one.
##########
grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/view/GrailsGspSpec.groovy:
##########
@@ -250,11 +250,13 @@ class GrailsGspSpec extends ApplicationContextSpec
implements CommandOutputFixtu
index.contains('mappingContext.eventListeners')
index.contains('<g:message code="welcome.datastores.listeners"/>')
- and: "the request's effective filter pipeline is derived from the
rendering call stack"
+ and: "servlet filters list every filter in the container in chain
order, with a portable fallback"
index.contains('data-switch-type="filters"')
- index.contains('Thread.currentThread().stackTrace')
- index.contains('jakarta.servlet.Filter.isAssignableFrom')
- index.contains('<g:message code="welcome.filters.request"/>')
+ index.contains('tomcatContext.findFilterMaps()')
Review Comment:
Agreed. b8f8c251f7 adds two test examples, `welcome-page` (Tomcat) and
`welcome-page-jetty`, that stage the web profile's welcome page, compile it
statically and render it in development, where the filter panels appear. Their
filters cover a servlet-name map ahead of another filter's URL map, a match-all
mapping, a disabled registration and a filter added straight to the container.
Both specs fail against the previous template, and the Jetty spec also fails
with the disabled-registration check removed. The string checks here stay only
as a quick structural check of the template.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]