jetty.project icon indicating copy to clipboard operation
jetty.project copied to clipboard

Issue with Configuring Specific Static Resources in the same base directory ee10 jetty 12.0.10

Open Emilyserap opened this issue 1 year ago • 18 comments
trafficstars

Jetty version(s) 12.0.10

Jetty Environment ee10

Java version/vendor 17

Description I am encountering an issue while configuring specific static resources in the same base directory. When I try to access these resources, I receive a HTTP ERROR 404 Not Found

ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");

ResourceFactory resourceFactory = ResourceFactory.of(context);
context.setBaseResource(resourceFactory.newResource(mainResourceBase));

ServletHolder holderAlt = new ServletHolder("static", DefaultServlet.class);
holderAlt.setInitParameter("dirAllowed", "true");
holderAlt.setInitParameter("acceptRanges", "true");
context.addServlet(holderAlt, "/index.html");

ServletHolder holderDef = new ServletHolder("default", DefaultServlet.class);
holderDef.setInitParameter("dirAllowed", "true");
context.addServlet(holderDef, "/")

Emilyserap avatar Jun 06 '24 10:06 Emilyserap

Can you show examples of resource requests that fail?

janbartel avatar Jun 06 '24 10:06 janbartel

The issue only serving the /index.html

Emilyserap avatar Jun 06 '24 10:06 Emilyserap

Call server.setDumpAfterStart(true); before server.start(); and verify that there is a Base Resource.

I say this, because you are not checking that the line resourceFactory.newResource(mainResourceBase) is returning null (such as if it cannot find that resource).

What is mainResourceBase btw?

joakime avatar Jun 06 '24 11:06 joakime

resourceFactory.newResource(mainResourceBase) is not returning null. I tested the same code in Jetty EE9, and it works correctly; I can access the request /index.html. Therefore, I think the problem is in the class DefaultServlet.java EE10. image

Emilyserap avatar Jun 06 '24 11:06 Emilyserap

You are using Windows (and important thing to note when working with File System issues on Java).

Can you access some of the static resources? or none of them? If you enable DEBUG logging do you see any mentions of "alias" issues? In the path D:\eclipse-workspace\Jetty12authEE10\bin\main\static-root\ are any of those path sub-entries a link? (hard / soft / fs / etc)

Is the path D:\eclipse-workspace\Jetty12authEE10\bin\main\static-root\ using the exact case (as stored on disk) of each sub-entry in that path? (if you are being lazy and using C:\foo\bar when the actual directories are C:\Foo\Bar then this would trigger all sorts of security features and alias checking behaviors as the paths are not exactly the same. (This also goes for the requested resources within that base resource. eg: don't request /index.html if the stored on disk file is Index.Html)

joakime avatar Jun 06 '24 12:06 joakime

I can access to all the static resources except index.html. For example, I can access to static resources that end with .js. However, I can't access the static resource index.html. Here is my configuration:

Server server = new Server();
		ServerConnector connector = new ServerConnector(server);
		connector.setPort(8080);
		server.addConnector(connector);
		URI webRootUri = findDefaultBaseResource();
		System.err.println("Default Base Resource is " + webRootUri);

		ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS | 
                ServletContextHandler.SECURITY);
		context.setContextPath("/welcome");
		ResourceFactory resourceFactory = ResourceFactory.of(context);
		context.setBaseResource(resourceFactory.newResource(webRootUri));

		final var holderJs = new ServletHolder("js", DefaultServlet.class);
		holderJs.setInitParameter("dirAllowed", "true");
		holderJs.setInitParameter("cacheControl", "public, max-age=0, must-revalidate");

		context.addServlet(holderJs, "*.js"); // Map to all paths ending with .js
		ServletHolder holderAlt = new ServletHolder("static", DefaultServlet.class);
		holderAlt.setInitParameter("dirAllowed", "false");

		holderAlt.setInitParameter("cacheControl", "no-store");
		context.addServlet(holderAlt, "/index.html");
		ServletHolder holderDef = new ServletHolder("default", DefaultServlet.class);
		holderDef.setInitParameter("dirAllowed", "true");
		context.addServlet(holderDef, "/");
		server.setHandler(context);
		server.start();
		server.join();`

Emilyserap avatar Jun 06 '24 12:06 Emilyserap

Can you please copy/paste? Don't use screenshots / images, as it doesn't help others in the future. (people searching for help cannot find similar issues). Also, it doesn't help us when helping you.

joakime avatar Jun 06 '24 12:06 joakime

You only need 1 DefaultServlet and it will always be mapped to / (no other url-pattern). Get rid of the other ones.

If you want welcome file behavior, use one of the ServletContextHandler.setWelcomeFiles() methods.

joakime avatar Jun 06 '24 13:06 joakime

If I want to specify a configuration for the static resource index.html, for example, or for other resources ending with .js, because not all of them have the same InitParameter?

ServletHolder holderAlt = new ServletHolder("static", DefaultServlet.class);
holderAlt.setInitParameter("dirAllowed", "false");
holderAlt.setInitParameter("cacheControl", "no-store");
context.addServlet(holderAlt, "/index.html");
ServletHolder holderDef = new ServletHolder("default", DefaultServlet.class);
holderDef.setInitParameter("dirAllowed", "true");
context.addServlet(holderDef, "/");

For the static resource index.html, we have set .setInitParameter("dirAllowed", "false"); and for the default, we have set .setInitParameter("dirAllowed", "true");

Emilyserap avatar Jun 06 '24 13:06 Emilyserap

If I want to specify a configuration for the static resource index.html, for example, or for other resources ending with .js, because not all of them have the same InitParameter

DefaultServlet does not work with url-patterns that are absolute-path (eg: /index.html), or suffix based (eg: *.js). It only works with default (eg: /) or directory-prefix (eg: /static/*)

joakime avatar Jun 06 '24 13:06 joakime

so I cannot assign special configurations for non-default DefaultServlets? But I don't understand if the problem is related to class DefaultServlet in Jetty 12 ee10, because the same code worked in Jetty 12 ee9, allowing access to all resources, including index.html with specific configuration to the index.html

Emilyserap avatar Jun 06 '24 13:06 Emilyserap

so I cannot assign special configurations for non-default DefaultServlets?

You can, but there are restrictions.

  • Extra configurations MUST be done on new DefaultServlet instances (you cannot reuse the ServletHolder)
  • Extra DefaultServlet instances only support url-patterns defined as either ...
    1. default pattern: only /
    2. prefix directory pattern: eg: /static/* or /css/* or /scripts/*, but not /foo.* as that's not a directory.
  • Each new DefaultServlet needs it's own Base Resource

See example at https://github.com/jetty/jetty-examples/blob/12.0.x/embedded/ee10-file-server/src/main/java/examples/ServletFileServerMultipleLocations.java

joakime avatar Jun 06 '24 14:06 joakime

so I cannot assign special configurations for non-default DefaultServlets?

You can, but there are restrictions.

  • Extra configurations MUST be done on new DefaultServlet instances (you cannot reuse the ServletHolder)

  • Extra DefaultServlet instances only support url-patterns defined as either ...

    1. default pattern: only /
    2. prefix directory pattern: eg: /static/* or /css/* or /scripts/*, but not /foo.* as that's not a directory.
  • Each new DefaultServlet needs it's own Base Resource

See example at https://github.com/jetty/jetty-examples/blob/12.0.x/embedded/ee10-file-server/src/main/java/examples/ServletFileServerMultipleLocations.java

Thanks for this information. so If I need to configure the static resource index.html, I must place this file in a different base resource and then add the baseResource parameter to the ServletHolder

Emilyserap avatar Jun 07 '24 08:06 Emilyserap

The static resource /index.html is served by the servlet default url-pattern of /.

Why do you need an entirely separate DefaultServlet for a precise / absolute-path url-pattern? It makes no sense to have a DefaultServlet mapping for that.

joakime avatar Jun 07 '24 10:06 joakime

I see a serious regression with the DefaultServlet in EE10 compared to EE9. I think the problem is in the DefaultServlet class and it affects the DefaultServlet when serving a single, unique file. Please refer to this issue https://github.com/jetty/jetty.project/issues/11791

Emilyserap avatar Jun 07 '24 14:06 Emilyserap

Why do you need that? Don't get hung up on the process, don't get caught up in the details, what's important to know is the end result.

If it's just for Cache-Control, then you are doing this entirely in the wrong place, as Cache-Control is applied to all resources in the entire Base Resource, not specific entries based on the DefaultServlet. (the Cache-Control init-param in DefaultServlet just configures the low level ResourceService which handles serving content from the Base Resource)

The whole point of serving static files from DefaultServlet is to serve them from a Base Resource. If you hardcode an absolute url-pattern then that will not be served from the Base Resource in the way you expect.

Take this for example...

  • Base Resource: C:\data\webroot\
  • Context-Path: /app
  • Default Servlet URL Pattern: /
  • HTTP Request Path: /app/css/main.css

That request will result in a lookup against the Base Resource + (Path-In-Context). The Path-In-Context is the path that is left over after you remove the Context-Path, and the url-pattern. So that would mean C:\data\webroot\ + /css/main.css Meaning that C:\data\webroot\css\main.css is what is served.

If you have an absolute-path url-pattern of say /index.html, and the request is /app/index.html, the Path-In-Context is `` (blank) as the url-pattern is excluded. The lookup will attempt to load from Base Resource the file C:\data\webroot\, which wont work.

Then with all of this you are still not handling welcome file logic at all with your Cache-Control. The user could use the request path /app/ and the welcome files logic will serve /index.html which wouldn't apply your custom Cache-Control either.

If you have specific Cache-Control behavior, that's not handled via DefaultServlet configuration, but rather by paying attention to the Response Content-Type and from where that response was generated (eg: the request path, but don't forget about welcome-files logic!). This is typically done via custom Servlet filters setup against specific paths.

joakime avatar Jun 07 '24 15:06 joakime

@Emilyserap see my comment over on this other issue here: https://github.com/jetty/jetty.project/issues/11884#issuecomment-2155696862. It's not a regression, ee10 is not trying to be bug-for-bug compatible with previous versions. We are attempting to clean up the DefaultServlet. I think the piece we are missing is the ResourceServlet (see #10738) that in the meanwhile can probably be worked around via the ResourceHandler (see linked comment for a link to the doco).

janbartel avatar Jun 07 '24 23:06 janbartel

@Emilyserap see my comment over on this other issue here: #11884 (comment). It's not a regression, ee10 is not trying to be bug-for-bug compatible with previous versions. We are attempting to clean up the DefaultServlet. I think the piece we are missing is the ResourceServlet (see #10738) that in the meanwhile can probably be worked around via the ResourceHandler (see linked comment for a link to the doco).

Thanks for the explanation. Otherwise, when will the ResourceServlet be ready?

Emilyserap avatar Jun 10 '24 09:06 Emilyserap

@Emilyserap https://github.com/jetty/jetty.project/pull/11933 has been merged into jetty-12.0.x, so should be part of this month's 12.0.12 release.

janbartel avatar Jul 10 '24 06:07 janbartel