Skip to content
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

Issue #10661 Allow jetty api to override servlets and mappings from webdefault #10668

Merged
merged 14 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -207,14 +207,14 @@ public void testWebappContext() throws Exception
webapp.getServerClassMatcher().add("-" + pkg + ".");
webapp.getSystemClassMatcher().add(pkg + ".");

webapp.addServlet(GreetingsServlet.class, "/");
webapp.addServlet(GreetingsServlet.class, "/greet");
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));

server.start();

LocalConnector connector = server.getBean(LocalConnector.class);
String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n");
String response = connector.getResponse("GET /greet HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello GreetingsServlet filtered by Weld BeanManager "));
assertThat(response, containsString("Beans from Weld BeanManager "));
Expand Down Expand Up @@ -245,12 +245,12 @@ public void testWebappContextDiscovered() throws Exception

webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
webapp.addServlet(GreetingsServlet.class, "/");
webapp.addServlet(GreetingsServlet.class, "/greet");

server.start();

LocalConnector connector = server.getBean(LocalConnector.class);
String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n");
String response = connector.getResponse("GET /greet HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello GreetingsServlet filtered by Weld BeanManager "));
assertThat(response, containsString("Beans from Weld BeanManager "));
Expand Down
1 change: 1 addition & 0 deletions jetty-ee10/jetty-ee10-webapp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<argLine>
@{argLine} ${jetty.surefire.argLine}
--add-exports org.eclipse.jetty.ee10.webapp/org.acme.webapp=org.eclipse.jetty.ee10.servlet
--add-reads org.eclipse.jetty.ee10.webapp=org.eclipse.jetty.logging
</argLine>
<useManifestOnlyJar>false</useManifestOnlyJar>
<additionalClasspathElements>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,18 @@ public void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser
_servletHolderMap.put(name, holder);
_servletHolders.add(holder);
}
else
{
//A servlet of the same name already exists. If it came from the jetty api
//and we're parsing webdefaults, then we will stop reading the descriptor to allow
//the api to define the default
janbartel marked this conversation as resolved.
Show resolved Hide resolved
if (Source.Origin.EMBEDDED == holder.getSource().getOrigin() && descriptor instanceof DefaultsDescriptor)
return;
}

// init params
Iterator<?> iParamsIter = node.iterator("init-param");

while (iParamsIter.hasNext())
{
XmlParser.Node paramNode = (XmlParser.Node)iParamsIter.next();
Expand Down Expand Up @@ -1054,34 +1063,41 @@ public ServletMapping addServletMapping(String servletName, XmlParser.Node node,
{
//The same path has been mapped multiple times, either to a different servlet or the same servlet.
//If its a different servlet, this is only valid to do if the old mapping was from a default descriptor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is valid to change a mapping from a default descriptor... and we allow embedded code to be used instead of a default descriptor, then why isn't it valid to do if the old mapping was from embedded?
I.e. If we define a servlet in embedded it is seen as a default, but a mapping appears to be handled differently?

Not sure exactly what should be done here... but either way probably need to comment to exact reasoning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we allow descriptors to override embedded, then a user can never change a mapping that came from the default descriptor. The principle of least surprise says to me that if I define a servlet and mapping in embedded, I would expect those to be honoured, and not obscured by something from the defaults descriptor, which is mostly hidden from users buried inside jetty-webapp jar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@agree I think we agree on the action, but disagree on the description. If either a servlet or a mapping is defined by embedded code, then it should not be overridden by the default descriptor, but could be overridden by a normal descriptor.

Said another way, the default for either Servlet or Mapping comes from either embedded or if not set there, then the webdefaults xml. Either default can be overridden by a normal descriptor.
So what I'd like to see is the same logic here as is done for a servlet. If the mapping is already defined and we are the default descriptor and the mapping came from embedded, then skip the current mapping.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the latest commit make this any clearer?

if (p.equals(ps) && (sm.isFromDefaultDescriptor() || servletName.equals(sm.getServletName())))
if (p.equals(ps))
{
if (sm.isFromDefaultDescriptor())
{
if (LOG.isDebugEnabled())
LOG.debug("{} in mapping {} from defaults descriptor is overridden by {}", ps, sm, servletName);
}
else
LOG.warn("Duplicate mapping from {} to {}", p, servletName);

//remove ps from the path specs on the existing mapping
//if the mapping now has no pathspecs, remove it
String[] updatedPaths = ArrayUtil.removeFromArray(sm.getPathSpecs(), ps);

if (updatedPaths == null || updatedPaths.length == 0)
if (!servletName.equals(sm.getServletName()))
{
if (LOG.isDebugEnabled())
LOG.debug("Removed empty mapping {}", sm);
listItor.remove();
if (!sm.isFromDefaultDescriptor())
throw new IllegalStateException("Duplicate mapping of " + p + ": " + servletName + "," + sm.getServletName());
else
{
if (LOG.isDebugEnabled())
LOG.debug("{} in mapping {} from defaults descriptor is overridden by {}", ps, sm, servletName);

//remove ps from the path specs on the existing mapping
//if the mapping now has no pathspecs, remove it
String[] updatedPaths = ArrayUtil.removeFromArray(sm.getPathSpecs(), ps);

if (updatedPaths == null || updatedPaths.length == 0)
{
if (LOG.isDebugEnabled())
LOG.debug("Removed empty mapping {}", sm);
listItor.remove();
}
else
{
sm.setPathSpecs(updatedPaths);
if (LOG.isDebugEnabled())
LOG.debug("Removed path {} from mapping {}", p, sm);
}
found = true;
break;
}
}
else
{
sm.setPathSpecs(updatedPaths);
if (LOG.isDebugEnabled())
LOG.debug("Removed path {} from mapping {}", p, sm);
LOG.warn("Duplicate mapping from {} to {}", p, servletName);
}
found = true;
break;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
package org.eclipse.jetty.ee10.webapp;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
Expand All @@ -33,6 +37,8 @@
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

@ExtendWith(WorkDirExtension.class)
public class StandardDescriptorProcessorTest
Expand All @@ -53,6 +59,104 @@ public void afterEach() throws Exception
_server.stop();
}

@Test
public void testJettyApiDefaults(WorkDir workDir) throws Exception
{
//Test that the DefaultServlet named "default" defined by jetty api is not redefined by webdefault-ee10.xml
Path docroot = workDir.getEmptyPathDir();
WebAppContext wac = new WebAppContext();
wac.setServer(_server);
wac.setBaseResourceAsPath(docroot);
ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class);
defaultServlet.setName("default");
defaultServlet.setInitParameter("acceptRanges", "false");
defaultServlet.setInitParameter("dirAllowed", "false");
defaultServlet.setInitParameter("welcomeServlets", "true");
defaultServlet.setInitParameter("redirectWelcome", "true");
defaultServlet.setInitParameter("maxCacheSize", "10");
defaultServlet.setInitParameter("maxCachedFileSize", "1");
defaultServlet.setInitParameter("maxCacheFiles", "10");
defaultServlet.setInitParameter("etags", "true");
defaultServlet.setInitParameter("useFileMappedBuffer", "false");
defaultServlet.setInitOrder(2);
defaultServlet.setRunAsRole("foo");
wac.getServletHandler().addServletWithMapping(defaultServlet, "/");
wac.start();

ServletHolder[] holders = wac.getServletHandler().getServlets();
ServletHolder holder = null;
for (ServletHolder h:holders)
{
if ("default".equals(h.getName()))
{
assertEquals(null, holder);
holder = h;
}
}
assertNotNull(holder);
assertEquals("false", holder.getInitParameter("acceptRanges"));
assertEquals("false", holder.getInitParameter("dirAllowed"));
assertEquals("true", holder.getInitParameter("welcomeServlets"));
assertEquals("true", holder.getInitParameter("redirectWelcome"));
assertEquals("10", holder.getInitParameter("maxCacheSize"));
assertEquals("1", holder.getInitParameter("maxCachedFileSize"));
assertEquals("10", holder.getInitParameter("maxCacheFiles"));
assertEquals("true", holder.getInitParameter("etags"));
assertEquals("false", holder.getInitParameter("useFileMappedBuffer"));
assertEquals(2, holder.getInitOrder());
assertEquals("foo", holder.getRunAsRole());
}

@Test
public void testDuplicateServletMappingsFromJettyApi(WorkDir workDir) throws Exception
{
Path docroot = workDir.getEmptyPathDir();
WebAppContext wac = new WebAppContext();
wac.setServer(_server);
wac.setBaseResourceAsPath(docroot);
wac.setThrowUnavailableOnStartupException(true);
//add a mapping that will conflict with the webdefault-ee10.xml mapping
//for the default servlet
ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class);
defaultServlet.setName("noname");
wac.getServletHandler().addServletWithMapping(defaultServlet, "/");
try (StacklessLogging ignored = new StacklessLogging(WebAppContext.class))
{
assertThrows(InvocationTargetException.class, () -> wac.start());
}
}

@Test
public void testDuplicateServletMappingsFromDescriptors(WorkDir workDir) throws Exception
{
//Test that the DefaultServlet mapping from webdefault-ee10.xml can be overridden in web.xml
Path docroot = workDir.getEmptyPathDir();
File webXml = MavenTestingUtils.getTestResourceFile("web-redefine-mapping.xml");
WebAppContext wac = new WebAppContext();
wac.setServer(_server);
wac.setBaseResourceAsPath(docroot);
wac.setDescriptor(webXml.toURI().toURL().toString());
wac.start();
assertEquals("other", wac.getServletHandler().getServletMapping("/").getServletName());
janbartel marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
public void testBadDuplicateServletMappingsFromDescriptors(WorkDir workDir) throws Exception
{
//Test that the same mapping cannot be redefined to a different servlet in the same (non-default) descriptor
Path docroot = workDir.getEmptyPathDir();
File webXml = MavenTestingUtils.getTestResourceFile("web-redefine-mapping-fail.xml");
WebAppContext wac = new WebAppContext();
wac.setServer(_server);
wac.setBaseResourceAsPath(docroot);
wac.setDescriptor(webXml.toURI().toURL().toString());
wac.setThrowUnavailableOnStartupException(true);
try (StacklessLogging ignored = new StacklessLogging(WebAppContext.class))
{
assertThrows(InvocationTargetException.class, () -> wac.start());
}
}

@Test
public void testVisitSessionConfig(WorkDir workDir) throws Exception
{
Expand Down Expand Up @@ -118,7 +222,6 @@ public void testVisitSessionConfig(WorkDir workDir) throws Exception

//test the attributes on SessionHandler do NOT contain the name
Map<String, String> sessionAttributes = wac.getSessionHandler().getSessionCookieAttributes();
sessionAttributes.keySet().forEach(System.err::println);
assertThat(sessionAttributes.keySet(),
containsInAnyOrder(Arrays.asList(
equalToIgnoringCase("comment"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">

<display-name>Bad Redefine Default Servlet Mapping WebApp</display-name>
janbartel marked this conversation as resolved.
Show resolved Hide resolved
<servlet>
<servlet-name>first</servlet-name>
<servlet-class>org.acme.webapp.GetResourceServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>first</servlet-name>
<url-pattern>/x</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>second</servlet-name>
<servlet-class>org.acme.webapp.GetResourceServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>second</servlet-name>
<url-pattern>/x</url-pattern>
</servlet-mapping>
</web-app>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">

<display-name>Redefine Default Servlet Mapping WebApp</display-name>
janbartel marked this conversation as resolved.
Show resolved Hide resolved
<servlet>
<servlet-name>other</servlet-name>
<servlet-class>org.acme.webapp.GetResourceServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>other</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>
1 change: 1 addition & 0 deletions jetty-ee8/jetty-ee8-webapp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<configuration>
<argLine>
@{argLine} ${jetty.surefire.argLine}
--add-reads org.eclipse.jetty.ee8.webapp=org.eclipse.jetty.logging
</argLine>
<useManifestOnlyJar>false</useManifestOnlyJar>
<additionalClasspathElements>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,14 +205,14 @@ public void testWebappContext() throws Exception
webapp.getServerClassMatcher().add("-" + pkg + ".");
webapp.getSystemClassMatcher().add(pkg + ".");

webapp.addServlet(GreetingsServlet.class, "/");
webapp.addServlet(GreetingsServlet.class, "/greet");
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));

server.start();

LocalConnector connector = server.getBean(LocalConnector.class);
String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n");
String response = connector.getResponse("GET /greet HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello GreetingsServlet filtered by Weld BeanManager "));
assertThat(response, containsString("Beans from Weld BeanManager "));
Expand Down Expand Up @@ -243,12 +243,12 @@ public void testWebappContextDiscovered() throws Exception

webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
webapp.addServlet(GreetingsServlet.class, "/");
webapp.addServlet(GreetingsServlet.class, "/greet");

server.start();

LocalConnector connector = server.getBean(LocalConnector.class);
String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n");
String response = connector.getResponse("GET /greet HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello GreetingsServlet filtered by Weld BeanManager "));
assertThat(response, containsString("Beans from Weld BeanManager "));
Expand Down
1 change: 1 addition & 0 deletions jetty-ee9/jetty-ee9-webapp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
@{argLine} ${jetty.surefire.argLine}
--add-exports org.eclipse.jetty.ee9.webapp/org.acme.webapp=org.eclipse.jetty.ee9.servlet
--add-exports org.eclipse.jetty.ee9.webapp/org.acme.webapp=org.eclipse.jetty.ee9.nested
--add-reads org.eclipse.jetty.ee9.webapp=org.eclipse.jetty.logging
</argLine>
<useManifestOnlyJar>false</useManifestOnlyJar>
<additionalClasspathElements>
Expand Down
Loading