Skip to content

Inconsistent behaviour of ServletContext.getResources() #17233

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
larsgrefer opened this issue Jun 17, 2019 · 12 comments
Closed

Inconsistent behaviour of ServletContext.getResources() #17233

larsgrefer opened this issue Jun 17, 2019 · 12 comments

Comments

@larsgrefer
Copy link
Contributor

larsgrefer commented Jun 17, 2019

The behavior of javax.servlet.ServletContext#getResourcePaths and (its sibling methods) is inconstent, depending on how the application is packaged and which servlet container is used.

I've created the following example application to demonstrate the problem: https://github.com/larsgrefer/servlet-context-test
All example applications in this repository return a list of all found resources under localhost:8080/resources

Jar based applications

Expected behaviour

All applications should always return the following items (in any order)

/library-classpath-resource.txt
/classpath-resource.txt

Actual behaviour:

Servlet Container gradle bootRun java -jar docker container build with jib
Tomcat Shows both items, but also a third one(*) /classpath-resource.txt is missing both items are shown
Undertow Both items are missing Both items are missing Both items are missing
Jetty Shows both items, but also a third one(*) /classpath-resource.txt is missing both items are shown

War based applications

Expected behaviour

All applications should always return the following items (in any order)

/library-classpath-resource.txt
/war-resource.txt

Actual behaviour:

Servlet Container gradle bootRun java -jar docker container build with jib (**)
Tomcat /classpath-resource.txt is shown in addition to the expected ones All items are shown as expected All items are shown as expected
Undertow Only /war-resource.txt is shown All items are missing All items are shown as expected
Jetty /classpath-resource.txt is shown in addition to the expected ones All items are shown as expected All items are shown as expected

(**) jib uses a distroless jetty for war based project, so this is a traditional deployment to an external servlet container from the spring-boot point of view

Conclusion

  • (*) src/main/webapp seems to be used as resource root, even if the project is not war-based.
  • classpath*:META-INF/resources is only used as resource root by Tomcat and Jetty, but not by undertow.
  • classpath*:META-INF/resources is not used as resource root when its the BOOT-INF/classes or WEB-INF/classes folder of a repacked jar or war.
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 17, 2019
@wilkinsona
Copy link
Member

Thanks for sharing your detailed findings. I've yet to digest everything, but the first thing I've noticed is that this is a partial duplicate of #8324. That /classpath-resource.txt is missing is things working as designed. Please see this comment for details.

@larsgrefer
Copy link
Contributor Author

Thanks for sharing the links. This makes some things clearer to me.
I now understand the rationale of not exposing WEB-INF/classes/META-INF/resources as resource root for war files, but I think BOOT-INF/classes/META-INF/resources should be supported.

For war files it might be unnecessary to expose WEB-INF/classes/META-INF/resources because I just could have put my resources in the root of the war file (src/main/webapp), but for jar packaged applications I have no other choice than putting my resources to src/main/resources/META-INF/resources).

Some additional thoughts about this:

  • The very same application should behave the same, regardless of how its run (IDE/main-method, gradle/maven bootRun, java -jar)
  • It (still) seems a bit strange to me that classpath*:META-INF/resources is handled differently, depending on where on the classpath it is.

@wilkinsona
Copy link
Member

wilkinsona commented Jun 18, 2019

but I think BOOT-INF/classes/META-INF/resources should be supported

This is what was considered in #8324 and we decided not to do it. I don't think we're going to change that decision I'm afraid.

I have no other choice than putting my resources to src/main/resources/META-INF/resources

In keeping with the servlet spec loading resources from WEB-INF/lib/*.jar/META-INF/resources, we support the same with jar packaging where resources can be loaded from BOOT-INF/lib/*.jar/META-INF/resources. In other words, if you want to use jar packaging, you should package your static resources in a separate module and then depend upon it.

The very same application should behave the same, regardless of how its run (IDE/main-method, gradle/maven bootRun, java -jar)

Agreed. We thought that things were consistent and we have tests the are intended to cover this so it would appear that we're missing something. We can use this issue to investigate and straighten things out.

@wilkinsona
Copy link
Member

The expectations for a war file are incorrect. The servlet contains requires static resources to be served from the root of the war file or from META-INF/resources in files in WEB-INF/lib. They should not be loaded from WEB-INF/classes/META-INF/resources. This means that classpath-resource.txt should not be loaded and that Tomcat and Jetty are behaving correctly when running a packaged war with java -jar.

@larsgrefer
Copy link
Contributor Author

I agree that the servlet specification doesn't require /classpath-resource.txt to be shown for war files. I've updated the table above to reflect that.

@wilkinsona
Copy link
Member

There's a bug in how we set up Undertow's ResourceManager which breaks getResourcePaths(String) (our integration tests only cover getResource(String) at the moment). I've opened #17243 to fix it.

@larsgrefer
Copy link
Contributor Author

After this is resolved, a few sentences should be added to the documentation about which locations are used as servlet context resources (and which locations are not used) and how this can be changed if necessary.

Some other thoughts I had in the meantime:

  • The servlet spec (section 4.6) does not specify whether the locations mentioned there (war root and /WEB-INF/lib/*.jar!/META-INF/resources) are the only locations allowed to be used as resource roots or if additional locations are permitted, too.
  • Im not sure if section 4.6 of the servlet spec can or should be applied to jar-based spring boot applications, because its not clear what "root of the context" should be. Also the directory structure in section 10.5 can't be applied to spring boot applications that aren't packaged as war file.

if you want to use jar packaging, you should package your static resources in a separate module and then depend upon it.

I'd rather modify the underlying servlet container to include a custom location as resource root than creating a separate module.

@larsgrefer
Copy link
Contributor Author

I've updated the tables above in order to include docker containers built with jib as third packaging option.

@larsgrefer
Copy link
Contributor Author

There seems to be an additional inconsistency for repackaged wars running using java -jar:

When using an embedded Tomcat, ServletContext.getResourcePaths() can traverse through /org/springframework/boot/loader/, while an embedded Jetty (successfully) hides /org/springframework/boot/loader/

@wilkinsona
Copy link
Member

The servlet spec (section 4.6) does not specify whether the locations mentioned there are the only locations allowed to be used

To follow the principle of least surprise, I think it's important that we align with the Servlet spec. While it doesn't prohibit additional locations, I think would be surprising for additional locations to be used by default.

I'm not sure if section 4.6 of the servlet spec can or should be applied to jar-based spring boot applications

We have decided that it can and should be applied, with some translations to Spring Boot's layout. Where the spec talks about WEB-INF/lib we apply the same rules to BOOT-INF/lib.

because its not clear what "root of the context" should be

The is no root of the context in a jar-based application. If we followed the war model, we'd have the same problem with accidentally serving content from the root of the jar.

Also the directory structure in section 10.5 can't be applied to spring boot applications that aren't packaged as war file.

It's almost entirely applicable, with WEB-INF translated to BOOT-INF. Only the document root is not applicable.

When using an embedded Tomcat, ServletContext.getResourcePaths() can traverse through /org/springframework/boot/loader/, while an embedded Jetty (successfully) hides /org/springframework/boot/loader/

Thanks. Our main concern with hiding the loader is preventing it from being served over HTTP which we currently do. That said, we'll see if it's possible to hide it from getResourcePaths() with Tomcat in the interests of consistency if nothing else. I've opened #17262.

@wilkinsona wilkinsona added the for: team-attention An issue we'd like other members of the team to review label Jun 19, 2019
@larsgrefer
Copy link
Contributor Author

We have decided that it can and should be applied, with some translations to Spring Boot's layout.

Does that mean my initial expectation to see /classpath-resource.txt for non-war applications is wrong?

It's almost entirely applicable, with WEB-INF translated to BOOT-INF. Only the document root is not applicable.

I see, that the servlet spec, in fact, could be applied to spring-boot-repackaged jar files.
But a non-war spring boot application can be run in a variety of ways:

  • repackaged jar
  • IDE's
  • gradle bootRun, mvn boot:run
  • jib-based docker images
  • gradle application distribution
  • just calling the main method

I think for most of these ways of running the application it would be difficult, error-prone or even improssible to distinguish between /classpath-resource.txt and /library-classpath-resource.txt. (Which would be neccessary to exclude the first but include the latter).

@wilkinsona wilkinsona removed the for: team-attention An issue we'd like other members of the team to review label Jun 21, 2019
@wilkinsona wilkinsona self-assigned this Jun 21, 2019
@wilkinsona wilkinsona removed their assignment Dec 20, 2019
@bclozel bclozel added the for: team-attention An issue we'd like other members of the team to review label Feb 20, 2020
@wilkinsona
Copy link
Member

I think we've done all that we can here for the time being at least. The Undertow issue I opened remains open. If it's fixed we can revisit that part of things in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants