Drop Undertow support

Spring Framework 7.0 requires a Servlet 6.1 baseline for Servlet containers.

Partial Servlet 6.1 support is available on the `main` Undertow branch,
but there isn't any milestone version available for the undertow 2.4
generation. At this stage of our 4.0 schedule, we think it's safer to drop
Undertow support now.

Closes: gh-46917
This commit is contained in:
Brian Clozel 2025-08-20 09:25:05 +02:00
parent f79c7d90b2
commit 8c0051a02e
134 changed files with 43 additions and 8278 deletions

View File

@ -116,7 +116,7 @@ The main library providing features that support the other parts of Spring Boot.
* The `SpringApplication` class, providing static convenience methods that can be used to write a stand-alone Spring Application. * The `SpringApplication` class, providing static convenience methods that can be used to write a stand-alone Spring Application.
Its sole job is to create and refresh an appropriate Spring `ApplicationContext`. Its sole job is to create and refresh an appropriate Spring `ApplicationContext`.
* Embedded web applications with a choice of container (Tomcat, Jetty, or Undertow). * Embedded web applications with a choice of container (Tomcat, Jetty).
* First-class externalized configuration support. * First-class externalized configuration support.
* Convenience `ApplicationContext` initializers, including support for sensible logging defaults. * Convenience `ApplicationContext` initializers, including support for sensible logging defaults.

View File

@ -128,7 +128,6 @@ def dependenciesOf(String version) {
"spring-boot-tomcat", "spring-boot-tomcat",
"spring-boot-tracing", "spring-boot-tracing",
"spring-boot-tx", "spring-boot-tx",
"spring-boot-undertow",
"spring-boot-validation", "spring-boot-validation",
"spring-boot-web-server", "spring-boot-web-server",
"spring-boot-webclient", "spring-boot-webclient",

View File

@ -111,7 +111,6 @@ dependencies {
implementation(project(path: ":module:spring-boot-security")) implementation(project(path: ":module:spring-boot-security"))
implementation(project(path: ":module:spring-boot-test-autoconfigure")) implementation(project(path: ":module:spring-boot-test-autoconfigure"))
implementation(project(path: ":module:spring-boot-tomcat")) implementation(project(path: ":module:spring-boot-tomcat"))
implementation(project(path: ":module:spring-boot-undertow"))
implementation(project(path: ":module:spring-boot-web-server-test")) implementation(project(path: ":module:spring-boot-web-server-test"))
implementation(project(path: ":module:spring-boot-webclient")) implementation(project(path: ":module:spring-boot-webclient"))
implementation(project(path: ":module:spring-boot-webflux")) implementation(project(path: ":module:spring-boot-webflux"))
@ -126,7 +125,6 @@ dependencies {
implementation("io.micrometer:micrometer-registry-jmx") implementation("io.micrometer:micrometer-registry-jmx")
implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0") implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0")
implementation("io.projectreactor.netty:reactor-netty-http") implementation("io.projectreactor.netty:reactor-netty-http")
implementation("io.undertow:undertow-core")
implementation("jakarta.annotation:jakarta.annotation-api") implementation("jakarta.annotation:jakarta.annotation-api")
implementation("jakarta.jms:jakarta.jms-api") implementation("jakarta.jms:jakarta.jms-api")
implementation("jakarta.persistence:jakarta.persistence-api") implementation("jakarta.persistence:jakarta.persistence-api")

View File

@ -684,8 +684,6 @@
* xref:how-to:webserver.adoc#howto.webserver.configure-http2.netty[#howto.webserver.configure-http2.netty] * xref:how-to:webserver.adoc#howto.webserver.configure-http2.netty[#howto.webserver.configure-http2.netty]
* xref:how-to:webserver.adoc#howto.webserver.configure-http2.tomcat[#howto-configure-http2-tomcat] * xref:how-to:webserver.adoc#howto.webserver.configure-http2.tomcat[#howto-configure-http2-tomcat]
* xref:how-to:webserver.adoc#howto.webserver.configure-http2.tomcat[#howto.webserver.configure-http2.tomcat] * xref:how-to:webserver.adoc#howto.webserver.configure-http2.tomcat[#howto.webserver.configure-http2.tomcat]
* xref:how-to:webserver.adoc#howto.webserver.configure-http2.undertow[#howto-configure-http2-undertow]
* xref:how-to:webserver.adoc#howto.webserver.configure-http2.undertow[#howto.webserver.configure-http2.undertow]
* xref:how-to:webserver.adoc#howto.webserver.configure-http2[#howto-configure-http2] * xref:how-to:webserver.adoc#howto.webserver.configure-http2[#howto-configure-http2]
* xref:how-to:webserver.adoc#howto.webserver.configure-http2[#howto.webserver.configure-http2] * xref:how-to:webserver.adoc#howto.webserver.configure-http2[#howto.webserver.configure-http2]
* xref:how-to:webserver.adoc#howto.webserver.configure-ssl.pem-files[#howto.webserver.configure-ssl.pem-files] * xref:how-to:webserver.adoc#howto.webserver.configure-ssl.pem-files[#howto.webserver.configure-ssl.pem-files]
@ -701,8 +699,6 @@
* xref:how-to:webserver.adoc#howto.webserver.discover-port[#howto.webserver.discover-port] * xref:how-to:webserver.adoc#howto.webserver.discover-port[#howto.webserver.discover-port]
* xref:how-to:webserver.adoc#howto.webserver.enable-multiple-connectors-in-tomcat[#howto-enable-multiple-connectors-in-tomcat] * xref:how-to:webserver.adoc#howto.webserver.enable-multiple-connectors-in-tomcat[#howto-enable-multiple-connectors-in-tomcat]
* xref:how-to:webserver.adoc#howto.webserver.enable-multiple-connectors-in-tomcat[#howto.webserver.enable-multiple-connectors-in-tomcat] * xref:how-to:webserver.adoc#howto.webserver.enable-multiple-connectors-in-tomcat[#howto.webserver.enable-multiple-connectors-in-tomcat]
* xref:how-to:webserver.adoc#howto.webserver.enable-multiple-listeners-in-undertow[#howto-enable-multiple-listeners-in-undertow]
* xref:how-to:webserver.adoc#howto.webserver.enable-multiple-listeners-in-undertow[#howto.webserver.enable-multiple-listeners-in-undertow]
* xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[#how-to-enable-http-response-compression] * xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[#how-to-enable-http-response-compression]
* xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[#howto.webserver.enable-response-compression] * xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[#howto.webserver.enable-response-compression]
* xref:how-to:webserver.adoc#howto.webserver.enable-tomcat-mbean-registry[#howto-enable-tomcat-mbean-registry] * xref:how-to:webserver.adoc#howto.webserver.enable-tomcat-mbean-registry[#howto-enable-tomcat-mbean-registry]

View File

@ -32,8 +32,6 @@ Spring Boot supports the following embedded servlet containers:
| Jetty 12.0 | Jetty 12.0
| 6.0 | 6.0
| Undertow 2.3
| 6.0
|=== |===
You can also deploy Spring Boot applications to any servlet 5.0+ compatible container. You can also deploy Spring Boot applications to any servlet 5.0+ compatible container.

View File

@ -179,7 +179,7 @@ web: java -Dserver.port=$PORT -jar target/demo-0.0.1-SNAPSHOT.jar
---- ----
Spring Boot makes `-D` arguments available as properties accessible from a Spring javadoc:org.springframework.core.env.Environment[] instance. Spring Boot makes `-D` arguments available as properties accessible from a Spring javadoc:org.springframework.core.env.Environment[] instance.
The `server.port` configuration property is fed to the embedded Tomcat, Jetty, or Undertow instance, which then uses the port when it starts up. The `server.port` configuration property is fed to the embedded Tomcat or Jetty instance, which then uses the port when it starts up.
The `$PORT` environment variable is assigned to us by the Heroku PaaS. The `$PORT` environment variable is assigned to us by the Heroku PaaS.
This should be everything you need. This should be everything you need.

View File

@ -12,8 +12,8 @@ This section answers those questions.
Many Spring Boot starters include default embedded containers. Many Spring Boot starters include default embedded containers.
* For servlet stack applications, the `spring-boot-starter-web` includes Tomcat by including `spring-boot-starter-tomcat`, but you can use `spring-boot-starter-jetty` or `spring-boot-starter-undertow` instead. * For servlet stack applications, the `spring-boot-starter-web` includes Tomcat by including `spring-boot-starter-tomcat`, but you can use `spring-boot-starter-jetty` instead.
* For reactive stack applications, the `spring-boot-starter-webflux` includes Reactor Netty by including `spring-boot-starter-reactor-netty`, but you can use `spring-boot-starter-tomcat`, `spring-boot-starter-jetty`, or `spring-boot-starter-undertow` instead. * For reactive stack applications, the `spring-boot-starter-webflux` includes Reactor Netty by including `spring-boot-starter-reactor-netty`, but you can use `spring-boot-starter-tomcat` or `spring-boot-starter-jetty` instead.
When switching to a different HTTP server, you need to swap the default dependencies for those that you need instead. When switching to a different HTTP server, you need to swap the default dependencies for those that you need instead.
To help with this process, Spring Boot provides a separate starter for each of the supported HTTP servers. To help with this process, Spring Boot provides a separate starter for each of the supported HTTP servers.
@ -40,16 +40,16 @@ The following Maven example shows how to exclude Tomcat and include Jetty for Sp
</dependency> </dependency>
---- ----
The following Gradle example configures the necessary dependencies and a {url-gradle-docs}/resolution_rules.html#sec:module_replacement[module replacement] to use Undertow in place of Reactor Netty for Spring WebFlux: The following Gradle example configures the necessary dependencies and a {url-gradle-docs}/resolution_rules.html#sec:module_replacement[module replacement] to use Tomcat in place of Reactor Netty for Spring WebFlux:
[source,gradle] [source,gradle]
---- ----
dependencies { dependencies {
implementation "org.springframework.boot:spring-boot-starter-undertow" implementation "org.springframework.boot:spring-boot-starter-tomcat"
implementation "org.springframework.boot:spring-boot-starter-webflux" implementation "org.springframework.boot:spring-boot-starter-webflux"
modules { modules {
module("org.springframework.boot:spring-boot-starter-reactor-netty") { module("org.springframework.boot:spring-boot-starter-reactor-netty") {
replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Reactor Netty") replacedBy("org.springframework.boot:spring-boot-starter-tomcat", "Use Tomcat instead of Reactor Netty")
} }
} }
} }
@ -116,7 +116,7 @@ Contrary to a test, application code callbacks are processed early (before the v
[[howto.webserver.enable-response-compression]] [[howto.webserver.enable-response-compression]]
== Enable HTTP Response Compression == Enable HTTP Response Compression
HTTP response compression is supported by Jetty, Tomcat, Reactor Netty, and Undertow. HTTP response compression is supported by Jetty, Tomcat and Reactor Netty.
It can be enabled in `application.properties`, as follows: It can be enabled in `application.properties`, as follows:
[configprops,yaml] [configprops,yaml]
@ -221,7 +221,7 @@ These properties should instead be defined using `spring.ssl.bundle.<type>.<name
[[howto.webserver.configure-ssl.sni]] [[howto.webserver.configure-ssl.sni]]
=== Configure Server Name Indication === Configure Server Name Indication
Tomcat, Netty, and Undertow can be configured to use unique SSL trust material for individual host names to support Server Name Indication (SNI). Tomcat, and Netty can be configured to use unique SSL trust material for individual host names to support Server Name Indication (SNI).
SNI configuration is not supported with Jetty, but Jetty can https://eclipse.dev/jetty/documentation/jetty-12/operations-guide/index.html#og-protocols-ssl-sni[automatically set up SNI] if multiple certificates are provided to it. SNI configuration is not supported with Jetty, but Jetty can https://eclipse.dev/jetty/documentation/jetty-12/operations-guide/index.html#og-protocols-ssl-sni[automatically set up SNI] if multiple certificates are provided to it.
Assuming xref:reference:features/ssl.adoc[SSL bundles] named `web`, `web-alt1`, and `web-alt2` have been configured, the following configuration can be used to assign each bundle to a host name served by the embedded web server: Assuming xref:reference:features/ssl.adoc[SSL bundles] named `web`, `web-alt1`, and `web-alt2` have been configured, the following configuration can be used to assign each bundle to a host name served by the embedded web server:
@ -292,13 +292,6 @@ Developers can choose to import only the required dependencies using a classifie
[[howto.webserver.configure-http2.undertow]]
=== HTTP/2 With Undertow
Undertow supports `h2c` and `h2` out of the box.
[[howto.webserver.configure]] [[howto.webserver.configure]]
== Configure the Web Server == Configure the Web Server
@ -309,7 +302,7 @@ See the list of xref:appendix:application-properties/index.adoc[].
The previous sections covered already many common use cases, such as compression, SSL or HTTP/2. The previous sections covered already many common use cases, such as compression, SSL or HTTP/2.
However, if a configuration key does not exist for your use case, you should then look at javadoc:org.springframework.boot.web.server.WebServerFactoryCustomizer[]. However, if a configuration key does not exist for your use case, you should then look at javadoc:org.springframework.boot.web.server.WebServerFactoryCustomizer[].
You can declare such a component and get access to the server factory relevant to your choice: you should select the variant for the chosen Server (Tomcat, Jetty, Reactor Netty, Undertow) and the chosen web stack (servlet or reactive). You can declare such a component and get access to the server factory relevant to your choice: you should select the variant for the chosen Server (Tomcat, Jetty, Reactor Netty) and the chosen web stack (servlet or reactive).
The example below is for Tomcat with the `spring-boot-starter-web` (servlet stack): The example below is for Tomcat with the `spring-boot-starter-web` (servlet stack):
@ -335,10 +328,6 @@ In addition Spring Boot provides:
| javadoc:org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory[] | javadoc:org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory[]
| javadoc:org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory[] | javadoc:org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory[]
| Undertow
| javadoc:org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory[]
| javadoc:org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory[]
| Reactor | Reactor
| N/A | N/A
| javadoc:org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory[] | javadoc:org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory[]
@ -402,7 +391,7 @@ By default, javadoc:org.springframework.boot.web.servlet.ServletComponentScan[fo
[[howto.webserver.configure-access-logs]] [[howto.webserver.configure-access-logs]]
== Configure Access Logging == Configure Access Logging
Access logs can be configured for Tomcat, Undertow, and Jetty through their respective namespaces. Access logs can be configured for Tomcat and Jetty through their respective namespaces.
For instance, the following settings log access on Tomcat with a {url-tomcat-docs}/config/valve.html#Access_Logging[custom pattern]. For instance, the following settings log access on Tomcat with a {url-tomcat-docs}/config/valve.html#Access_Logging[custom pattern].
@ -420,24 +409,6 @@ NOTE: The default location for logs is a `logs` directory relative to the Tomcat
By default, the `logs` directory is a temporary directory, so you may want to fix Tomcat's base directory or use an absolute path for the logs. By default, the `logs` directory is a temporary directory, so you may want to fix Tomcat's base directory or use an absolute path for the logs.
In the preceding example, the logs are available in `my-tomcat/logs` relative to the working directory of the application. In the preceding example, the logs are available in `my-tomcat/logs` relative to the working directory of the application.
Access logging for Undertow can be configured in a similar fashion, as shown in the following example:
[configprops,yaml]
----
server:
undertow:
accesslog:
enabled: true
pattern: "%t %a %r %s (%D milliseconds)"
options:
server:
record-request-start-time: true
----
Note that, in addition to enabling access logging and configuring its pattern, recording request start times has also been enabled.
This is required when including the response time (`%D`) in the access log pattern.
Logs are stored in a `logs` directory relative to the working directory of the application.
You can customize this location by setting the configprop:server.undertow.accesslog.dir[] property.
Finally, access logging for Jetty can also be configured as follows: Finally, access logging for Jetty can also be configured as follows:
@ -537,14 +508,6 @@ server:
[[howto.webserver.enable-multiple-listeners-in-undertow]]
== Enable Multiple Listeners with Undertow
Add an javadoc:org.springframework.boot.web.embedded.undertow.UndertowBuilderCustomizer[] to the javadoc:org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory[] and add a listener to the `io.undertow.Undertow.Builder`, as shown in the following example:
include-code::MyUndertowConfiguration[]
[[howto.webserver.create-websocket-endpoints-using-serverendpoint]] [[howto.webserver.create-websocket-endpoints-using-serverendpoint]]
== Create WebSocket Endpoints Using @ServerEndpoint == Create WebSocket Endpoints Using @ServerEndpoint

View File

@ -1,7 +1,7 @@
[[messaging.websockets]] [[messaging.websockets]]
= WebSockets = WebSockets
Spring Boot provides WebSockets auto-configuration for embedded Tomcat, Jetty, and Undertow. Spring Boot provides WebSockets auto-configuration for embedded Tomcat and Jetty.
If you deploy a war file to a standalone container, Spring Boot assumes that the container is responsible for the configuration of its WebSocket support. If you deploy a war file to a standalone container, Spring Boot assumes that the container is responsible for the configuration of its WebSocket support.
Spring Framework provides {url-spring-framework-docs}/web/websocket.html[rich WebSocket support] for MVC web applications that can be easily accessed through the `spring-boot-starter-websocket` module. Spring Framework provides {url-spring-framework-docs}/web/websocket.html[rich WebSocket support] for MVC web applications that can be easily accessed through the `spring-boot-starter-websocket` module.

View File

@ -1,7 +1,7 @@
[[web.graceful-shutdown]] [[web.graceful-shutdown]]
= Graceful Shutdown = Graceful Shutdown
Graceful shutdown is enabled by default with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and servlet-based web applications. Graceful shutdown is enabled by default with all four embedded web servers (Jetty, Reactor Netty, and Tomcat) and with both reactive and servlet-based web applications.
It occurs as part of closing the application context and is performed in the earliest phase of stopping javadoc:org.springframework.context.SmartLifecycle[] beans. It occurs as part of closing the application context and is performed in the earliest phase of stopping javadoc:org.springframework.context.SmartLifecycle[] beans.
This stop processing uses a timeout which provides a grace period during which existing requests will be allowed to complete but no new requests will be permitted. This stop processing uses a timeout which provides a grace period during which existing requests will be allowed to complete but no new requests will be permitted.
@ -26,10 +26,9 @@ The exact way in which new requests are not permitted varies depending on the we
Implementations may stop accepting requests at the network layer, or they may return a response with a specific HTTP status code or HTTP header. Implementations may stop accepting requests at the network layer, or they may return a response with a specific HTTP status code or HTTP header.
The use of persistent connections can also change the way that requests stop being accepted. The use of persistent connections can also change the way that requests stop being accepted.
TIP: To learn more about the specific method used with your web server, see the `shutDownGracefully` API documentation for javadoc:org.springframework.boot.web.embedded.tomcat.TomcatWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[], javadoc:org.springframework.boot.web.embedded.netty.NettyWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[], javadoc:org.springframework.boot.web.embedded.jetty.JettyWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[] or javadoc:org.springframework.boot.web.embedded.undertow.UndertowWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[]. TIP: To learn more about the specific method used with your web server, see the `shutDownGracefully` API documentation for javadoc:org.springframework.boot.web.embedded.tomcat.TomcatWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[], javadoc:org.springframework.boot.web.embedded.netty.NettyWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[], or javadoc:org.springframework.boot.web.embedded.jetty.JettyWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[].
Jetty, Reactor Netty, and Tomcat will stop accepting new requests at the network layer. Jetty, Reactor Netty, and Tomcat will stop accepting new requests at the network layer.
Undertow will accept new connections but respond immediately with a service unavailable (503) response.

View File

@ -2,7 +2,7 @@
= Web = Web
Spring Boot is well suited for web application development. Spring Boot is well suited for web application development.
You can create a self-contained HTTP server by using embedded Tomcat, Jetty, Undertow, or Netty. You can create a self-contained HTTP server by using embedded Tomcat, Jetty, or Netty.
Most web applications use the `spring-boot-starter-web` module to get up and running quickly. Most web applications use the `spring-boot-starter-web` module to get up and running quickly.
You can also choose to build reactive web applications by using the `spring-boot-starter-webflux` module. You can also choose to build reactive web applications by using the `spring-boot-starter-webflux` module.

View File

@ -310,7 +310,7 @@ See xref:io/rest-client.adoc#io.rest-client.apiversioning[] for details.
[[web.reactive.reactive-server]] [[web.reactive.reactive-server]]
== Embedded Reactive Server Support == Embedded Reactive Server Support
Spring Boot includes support for the following embedded reactive web servers: Reactor Netty, Tomcat, Jetty, and Undertow. Spring Boot includes support for the following embedded reactive web servers: Reactor Netty, Tomcat, and Jetty.
Most developers use the appropriate starter to obtain a fully configured instance. Most developers use the appropriate starter to obtain a fully configured instance.
By default, the embedded server listens for HTTP requests on port 8080. By default, the embedded server listens for HTTP requests on port 8080.
@ -345,7 +345,7 @@ The following example shows programmatically setting the port:
include-code::MyWebServerFactoryCustomizer[] include-code::MyWebServerFactoryCustomizer[]
javadoc:org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory[], javadoc:org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory[], javadoc:org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory[], and javadoc:org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory[] are dedicated variants of javadoc:org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory[] that have additional customization setter methods for Jetty, Reactor Netty, Tomcat, and Undertow respectively. javadoc:org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory[], javadoc:org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory[], and javadoc:org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory[] are dedicated variants of javadoc:org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory[] that have additional customization setter methods for Jetty, Reactor Netty, and Tomcat respectively.
The following example shows how to customize javadoc:org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory[] that provides access to Reactor Netty-specific configuration options: The following example shows how to customize javadoc:org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory[] that provides access to Reactor Netty-specific configuration options:
include-code::MyNettyWebServerFactoryCustomizer[] include-code::MyNettyWebServerFactoryCustomizer[]

View File

@ -537,7 +537,7 @@ Both the servlet and the filter registrations can be given init parameters by us
[[web.servlet.embedded-container]] [[web.servlet.embedded-container]]
== Embedded Servlet Container Support == Embedded Servlet Container Support
For servlet application, Spring Boot includes support for embedded https://tomcat.apache.org/[Tomcat], https://www.eclipse.org/jetty/[Jetty], and https://github.com/undertow-io/undertow[Undertow] servers. For servlet application, Spring Boot includes support for embedded https://tomcat.apache.org/[Tomcat] and https://www.eclipse.org/jetty/[Jetty] servers.
Most developers use the appropriate starter to obtain a fully configured instance. Most developers use the appropriate starter to obtain a fully configured instance.
By default, the embedded server listens for HTTP requests on port `8080`. By default, the embedded server listens for HTTP requests on port `8080`.
@ -605,7 +605,7 @@ TIP: javadoc:org.springframework.boot.web.servlet.ServletComponentScan[format=an
Under the hood, Spring Boot uses a different type of javadoc:org.springframework.context.ApplicationContext[] for embedded servlet container support. Under the hood, Spring Boot uses a different type of javadoc:org.springframework.context.ApplicationContext[] for embedded servlet container support.
The javadoc:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext[] is a special type of javadoc:org.springframework.web.context.WebApplicationContext[] that bootstraps itself by searching for a single javadoc:org.springframework.boot.web.servlet.server.ServletWebServerFactory[] bean. The javadoc:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext[] is a special type of javadoc:org.springframework.web.context.WebApplicationContext[] that bootstraps itself by searching for a single javadoc:org.springframework.boot.web.servlet.server.ServletWebServerFactory[] bean.
Usually a javadoc:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory[], javadoc:org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory[], or javadoc:org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory[] has been auto-configured. Usually a javadoc:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory[], or javadoc:org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory[] has been auto-configured.
NOTE: You usually do not need to be aware of these implementation classes. NOTE: You usually do not need to be aware of these implementation classes.
Most applications are auto-configured, and the appropriate javadoc:org.springframework.context.ApplicationContext[] and javadoc:org.springframework.boot.web.servlet.server.ServletWebServerFactory[] are created on your behalf. Most applications are auto-configured, and the appropriate javadoc:org.springframework.context.ApplicationContext[] and javadoc:org.springframework.boot.web.servlet.server.ServletWebServerFactory[] are created on your behalf.
@ -635,7 +635,7 @@ Common server settings include:
* xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[HTTP compression] * xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[HTTP compression]
Spring Boot tries as much as possible to expose common settings, but this is not always possible. Spring Boot tries as much as possible to expose common settings, but this is not always possible.
For those cases, dedicated namespaces offer server-specific customizations (see `server.tomcat` and `server.undertow`). For those cases, dedicated namespaces offer server-specific customizations (see `server.tomcat`).
For instance, xref:how-to:webserver.adoc#howto.webserver.configure-access-logs[access logs] can be configured with specific features of the embedded servlet container. For instance, xref:how-to:webserver.adoc#howto.webserver.configure-access-logs[access logs] can be configured with specific features of the embedded servlet container.
TIP: See the javadoc:org.springframework.boot.autoconfigure.web.ServerProperties[] class for a complete list. TIP: See the javadoc:org.springframework.boot.autoconfigure.web.ServerProperties[] class for a complete list.
@ -649,7 +649,7 @@ The `SameSite` cookie attribute can be used by web browsers to control if and ho
The attribute is particularly relevant for modern web browsers which have started to change the default value that is used when the attribute is missing. The attribute is particularly relevant for modern web browsers which have started to change the default value that is used when the attribute is missing.
If you want to change the `SameSite` attribute of your session cookie, you can use the configprop:server.servlet.session.cookie.same-site[] property. If you want to change the `SameSite` attribute of your session cookie, you can use the configprop:server.servlet.session.cookie.same-site[] property.
This property is supported by auto-configured Tomcat, Jetty and Undertow servers. This property is supported by auto-configured Tomcat and Jetty servers.
It is also used to configure Spring Session servlet based javadoc:org.springframework.session.SessionRepository[] beans. It is also used to configure Spring Session servlet based javadoc:org.springframework.session.SessionRepository[] beans.
For example, if you want your session cookie to have a `SameSite` attribute of `None`, you can add the following to your `application.properties` or `application.yaml` file: For example, if you want your session cookie to have a `SameSite` attribute of `None`, you can add the following to your `application.properties` or `application.yaml` file:
@ -705,7 +705,7 @@ The following example shows programmatically setting the port:
include-code::MyWebServerFactoryCustomizer[] include-code::MyWebServerFactoryCustomizer[]
javadoc:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory[], javadoc:org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory[] and javadoc:org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory[] are dedicated variants of javadoc:org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory[] that have additional customization setter methods for Tomcat, Jetty and Undertow respectively. javadoc:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory[], and javadoc:org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory[] are dedicated variants of javadoc:org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory[] that have additional customization setter methods for Tomcat, and Jetty respectively.
The following example shows how to customize javadoc:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory[] that provides access to Tomcat-specific configuration options: The following example shows how to customize javadoc:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory[] that provides access to Tomcat-specific configuration options:
include-code::MyTomcatWebServerFactoryCustomizer[] include-code::MyTomcatWebServerFactoryCustomizer[]
@ -734,7 +734,5 @@ When running a Spring Boot application that uses an embedded servlet container (
An executable war will work when launched with `java -jar`, and will also be deployable to any standard container. An executable war will work when launched with `java -jar`, and will also be deployable to any standard container.
JSPs are not supported when using an executable jar. JSPs are not supported when using an executable jar.
* Undertow does not support JSPs.
* Creating a custom `error.jsp` page does not override the default view for xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling[error handling]. * Creating a custom `error.jsp` page does not override the default view for xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling[error handling].
xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages[Custom error pages] should be used instead. xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages[Custom error pages] should be used instead.

View File

@ -1,38 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.docs.howto.webserver.enablemultiplelistenersinundertow;
import io.undertow.Undertow.Builder;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyUndertowConfiguration {
@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowListenerCustomizer() {
return (factory) -> factory.addBuilderCustomizers(this::addHttpListener);
}
private Builder addHttpListener(Builder builder) {
return builder.addHttpListener(8080, "0.0.0.0");
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.docs.howto.webserver.enablemultiplelistenersinundertow
import io.undertow.Undertow
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.undertow.UndertowBuilderCustomizer
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyUndertowConfiguration {
@Bean
fun undertowListenerCustomizer(): WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
return WebServerFactoryCustomizer { factory: UndertowServletWebServerFactory ->
factory.addBuilderCustomizers(
UndertowBuilderCustomizer { builder: Undertow.Builder -> addHttpListener(builder) })
}
}
private fun addHttpListener(builder: Undertow.Builder): Undertow.Builder {
return builder.addHttpListener(8080, "0.0.0.0")
}
}

View File

@ -26,7 +26,6 @@ dependencies {
testImplementation(project(":module:spring-boot-jetty")) testImplementation(project(":module:spring-boot-jetty"))
testImplementation(project(":module:spring-boot-tomcat")) testImplementation(project(":module:spring-boot-tomcat"))
testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation(project(":module:spring-boot-undertow"))
testImplementation("io.projectreactor.netty:reactor-netty-http") testImplementation("io.projectreactor.netty:reactor-netty-http")
testImplementation("org.apache.httpcomponents.client5:httpclient5") testImplementation("org.apache.httpcomponents.client5:httpclient5")
testImplementation("org.apache.httpcomponents.core5:httpcore5-reactive") testImplementation("org.apache.httpcomponents.core5:httpcore5-reactive")
@ -36,7 +35,6 @@ dependencies {
testImplementation("org.springframework:spring-webmvc") testImplementation("org.springframework:spring-webmvc")
testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("io.undertow:undertow-servlet")
testRuntimeOnly("org.eclipse.jetty.ee11:jetty-ee11-servlets") testRuntimeOnly("org.eclipse.jetty.ee11:jetty-ee11-servlets")
testRuntimeOnly("org.eclipse.jetty.ee11:jetty-ee11-webapp") testRuntimeOnly("org.eclipse.jetty.ee11:jetty-ee11-webapp")
} }

View File

@ -38,7 +38,6 @@ dependencies {
testRepository(project(path: ":starter:spring-boot-starter-jackson", configuration: "mavenRepository")) testRepository(project(path: ":starter:spring-boot-starter-jackson", configuration: "mavenRepository"))
testRepository(project(path: ":starter:spring-boot-starter-parent", configuration: "mavenRepository")) testRepository(project(path: ":starter:spring-boot-starter-parent", configuration: "mavenRepository"))
testRepository(project(path: ":starter:spring-boot-starter-tomcat", configuration: "mavenRepository")) testRepository(project(path: ":starter:spring-boot-starter-tomcat", configuration: "mavenRepository"))
testRepository(project(path: ":starter:spring-boot-starter-undertow", configuration: "mavenRepository"))
testRuntimeOnly(project(":starter:spring-boot-starter-logging")) testRuntimeOnly(project(":starter:spring-boot-starter-logging"))
} }
@ -63,9 +62,7 @@ tasks.register("buildApps", GradleBuild) {
"jettyBootJar", "jettyBootJar",
"jettyBootWar", "jettyBootWar",
"tomcatBootJar", "tomcatBootJar",
"tomcatBootWar", "tomcatBootWar"
"undertowBootJar",
"undertowBootWar"
] ]
} }
@ -75,9 +72,7 @@ intTest {
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-jetty.war"), layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-jetty.war"),
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-resources.jar"), layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-resources.jar"),
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat.jar"), layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat.jar"),
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat.war"), layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat.war")
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-undertow.jar"),
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-undertow.war")
) )
.withPropertyName("applicationArchives") .withPropertyName("applicationArchives")
.withPathSensitivity(PathSensitivity.RELATIVE) .withPathSensitivity(PathSensitivity.RELATIVE)

View File

@ -44,9 +44,6 @@ configurations {
tomcat { tomcat {
extendsFrom(app) extendsFrom(app)
} }
undertow {
extendsFrom(app)
}
} }
tasks.register("resourcesJar", Jar) { jar -> tasks.register("resourcesJar", Jar) { jar ->
@ -77,14 +74,13 @@ dependencies {
app("org.springframework:spring-web") app("org.springframework:spring-web")
jetty("org.springframework.boot:spring-boot-starter-jetty") jetty("org.springframework.boot:spring-boot-starter-jetty")
tomcat("org.springframework.boot:spring-boot-starter-tomcat") tomcat("org.springframework.boot:spring-boot-starter-tomcat")
undertow("org.springframework.boot:spring-boot-starter-undertow")
} }
static boolean isWindows() { static boolean isWindows() {
return File.separatorChar == '\\' return File.separatorChar == '\\'
} }
["jetty", "tomcat", "undertow"].each { webServer -> ["jetty", "tomcat"].each { webServer ->
def configurer = { task -> def configurer = { task ->
task.dependsOn resourcesJar task.dependsOn resourcesJar
task.mainClass = "com.example.ResourceHandlingApplication" task.mainClass = "com.example.ResourceHandlingApplication"

View File

@ -62,7 +62,7 @@ import org.springframework.web.util.UriTemplateHandler;
class EmbeddedServerContainerInvocationContextProvider class EmbeddedServerContainerInvocationContextProvider
implements TestTemplateInvocationContextProvider, AfterAllCallback { implements TestTemplateInvocationContextProvider, AfterAllCallback {
private static final Set<String> CONTAINERS = new HashSet<>(Arrays.asList("jetty", "tomcat", "undertow")); private static final Set<String> CONTAINERS = new HashSet<>(Arrays.asList("jetty", "tomcat"));
private static final BuildOutput buildOutput = new BuildOutput( private static final BuildOutput buildOutput = new BuildOutput(
EmbeddedServerContainerInvocationContextProvider.class); EmbeddedServerContainerInvocationContextProvider.class);

View File

@ -31,7 +31,6 @@ dependencies {
app project(path: ":starter:spring-boot-starter", configuration: "mavenRepository") app project(path: ":starter:spring-boot-starter", configuration: "mavenRepository")
app project(path: ":starter:spring-boot-starter-actuator", configuration: "mavenRepository") app project(path: ":starter:spring-boot-starter-actuator", configuration: "mavenRepository")
app project(path: ":starter:spring-boot-starter-tomcat", configuration: "mavenRepository") app project(path: ":starter:spring-boot-starter-tomcat", configuration: "mavenRepository")
app project(path: ":starter:spring-boot-starter-undertow", configuration: "mavenRepository")
app project(path: ":starter:spring-boot-starter-web", configuration: "mavenRepository") app project(path: ":starter:spring-boot-starter-web", configuration: "mavenRepository")
app project(path: ":starter:spring-boot-starter-webflux", configuration: "mavenRepository") app project(path: ":starter:spring-boot-starter-webflux", configuration: "mavenRepository")
app project(path: ":build-plugin:spring-boot-gradle-plugin", configuration: "mavenRepository") app project(path: ":build-plugin:spring-boot-gradle-plugin", configuration: "mavenRepository")
@ -61,8 +60,7 @@ tasks.register("buildReactiveServerApps", GradleBuild) {
startParameter.buildCacheEnabled = false startParameter.buildCacheEnabled = false
tasks = [ tasks = [
"nettyServerApp", "nettyServerApp",
"tomcatServerApp", "tomcatServerApp"
"undertowServerApp"
] ]
} }
@ -76,8 +74,7 @@ tasks.register("buildServletServerApps", GradleBuild) {
dir = layout.buildDirectory.dir("spring-boot-sni-servlet-app") dir = layout.buildDirectory.dir("spring-boot-sni-servlet-app")
startParameter.buildCacheEnabled = false startParameter.buildCacheEnabled = false
tasks = [ tasks = [
"tomcatServerApp", "tomcatServerApp"
"undertowServerApp"
] ]
} }
@ -97,9 +94,7 @@ intTest {
inputs.files( inputs.files(
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-netty-reactive.jar"), layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-netty-reactive.jar"),
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat-reactive.jar"), layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat-reactive.jar"),
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat-servlet.jar"), layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-tomcat-servlet.jar")
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-undertow-reactive.jar"),
layout.buildDirectory.file("spring-boot-server-tests-app/build/libs/spring-boot-server-tests-app-undertow-servlet.jar")
) )
.withPropertyName("applicationArchives") .withPropertyName("applicationArchives")
.withPathSensitivity(PathSensitivity.RELATIVE) .withPathSensitivity(PathSensitivity.RELATIVE)

View File

@ -42,9 +42,6 @@ configurations {
tomcat { tomcat {
extendsFrom(app) extendsFrom(app)
} }
undertow {
extendsFrom(app)
}
} }
dependencies { dependencies {
@ -58,10 +55,9 @@ dependencies {
app("org.springframework.boot:spring-boot-webflux") app("org.springframework.boot:spring-boot-webflux")
netty("org.springframework.boot:spring-boot-starter-webflux") netty("org.springframework.boot:spring-boot-starter-webflux")
tomcat("org.springframework.boot:spring-boot-starter-tomcat") tomcat("org.springframework.boot:spring-boot-starter-tomcat")
undertow("org.springframework.boot:spring-boot-starter-undertow")
} }
["netty", "tomcat", "undertow"].each { webServer -> ["netty", "tomcat"].each { webServer ->
def configurer = { task -> def configurer = { task ->
task.mainClass = "org.springframework.boot.sni.server.SniServerApplication" task.mainClass = "org.springframework.boot.sni.server.SniServerApplication"
task.classpath = configurations.getByName(webServer) task.classpath = configurations.getByName(webServer)

View File

@ -39,9 +39,6 @@ configurations {
tomcat { tomcat {
extendsFrom(app) extendsFrom(app)
} }
undertow {
extendsFrom(app)
}
} }
dependencies { dependencies {
@ -58,10 +55,9 @@ dependencies {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
} }
tomcat("org.springframework.boot:spring-boot-starter-tomcat") tomcat("org.springframework.boot:spring-boot-starter-tomcat")
undertow("org.springframework.boot:spring-boot-starter-undertow")
} }
["tomcat", "undertow"].each { webServer -> ["tomcat"].each { webServer ->
def configurer = { task -> def configurer = { task ->
task.mainClass = "org.springframework.boot.sni.server.SniServerApplication" task.mainClass = "org.springframework.boot.sni.server.SniServerApplication"
task.classpath = configurations.getByName(webServer) task.classpath = configurations.getByName(webServer)

View File

@ -42,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class SniIntegrationTests { class SniIntegrationTests {
private static final Map<String, String> SERVER_START_MESSAGES = Map.ofEntries(Map.entry("netty", "Netty started"), private static final Map<String, String> SERVER_START_MESSAGES = Map.ofEntries(Map.entry("netty", "Netty started"),
Map.entry("tomcat", "Tomcat initialized"), Map.entry("undertow", "starting server: Undertow")); Map.entry("tomcat", "Tomcat initialized"));
public static final String PRIMARY_SERVER_NAME = "hello.example.com"; public static final String PRIMARY_SERVER_NAME = "hello.example.com";
@ -53,7 +53,7 @@ class SniIntegrationTests {
private static final Network SHARED_NETWORK = Network.newNetwork(); private static final Network SHARED_NETWORK = Network.newNetwork();
@ParameterizedTest @ParameterizedTest
@CsvSource({ "reactive,netty", "reactive,tomcat", "servlet,tomcat", "reactive,undertow", "servlet,undertow" }) @CsvSource({ "reactive,netty", "reactive,tomcat", "servlet,tomcat" })
void home(String webStack, String server) { void home(String webStack, String server) {
try (ApplicationContainer serverContainer = new ServerApplicationContainer(webStack, server)) { try (ApplicationContainer serverContainer = new ServerApplicationContainer(webStack, server)) {
serverContainer.start(); serverContainer.start();

View File

@ -259,9 +259,6 @@ dependencies {
api(project(":module:spring-boot-tx")) { api(project(":module:spring-boot-tx")) {
transitive = false transitive = false
} }
api(project(":module:spring-boot-undertow")) {
transitive = false
}
api(project(":module:spring-boot-validation")) { api(project(":module:spring-boot-validation")) {
transitive = false transitive = false
} }

View File

@ -24,7 +24,7 @@ import org.eclipse.jetty.http.HttpMethod;
* HttpMethods} rather than just {@code GET}, {@code POST} and {@code HEAD}. By default * HttpMethods} rather than just {@code GET}, {@code POST} and {@code HEAD}. By default
* Jetty <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=446039">intentionally only * Jetty <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=446039">intentionally only
* supports a limited set of HTTP methods</a> for error pages, however, Spring Boot * supports a limited set of HTTP methods</a> for error pages, however, Spring Boot
* prefers Tomcat, Jetty and Undertow to all behave in the same way. * prefers Tomcat and Jetty to all behave in the same way.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Christoph Dreis * @author Christoph Dreis

View File

@ -38,7 +38,6 @@ dependencies {
testImplementation(project(":module:spring-boot-jetty")) testImplementation(project(":module:spring-boot-jetty"))
testImplementation(project(":module:spring-boot-tomcat")) testImplementation(project(":module:spring-boot-tomcat"))
testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation(project(":module:spring-boot-undertow"))
testImplementation("org.springframework:spring-webmvc") testImplementation("org.springframework:spring-webmvc")
testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("ch.qos.logback:logback-classic")

View File

@ -33,7 +33,6 @@ import org.springframework.boot.testsupport.classpath.ForkedClassPath;
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration; import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.autoconfigure.ServerProperties; import org.springframework.boot.web.server.autoconfigure.ServerProperties;
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -104,8 +103,7 @@ class MultipartAutoConfigurationTests {
static Stream<Arguments> webServerWithNoMultipartConfigurationArguments() { static Stream<Arguments> webServerWithNoMultipartConfigurationArguments() {
return Stream.of(Arguments.of("Jetty", WebServerWithNoMultipartJetty.class), return Stream.of(Arguments.of("Jetty", WebServerWithNoMultipartJetty.class),
Arguments.of("Tomcat", WebServerWithNoMultipartTomcat.class), Arguments.of("Tomcat", WebServerWithNoMultipartTomcat.class));
Arguments.of("Undertow", WebServerWithNoMultipartUndertow.class));
} }
@ParameterizedTest(name = "{0}") @ParameterizedTest(name = "{0}")
@ -121,18 +119,7 @@ class MultipartAutoConfigurationTests {
static Stream<Arguments> webServerWithAutomatedMultipartConfigurationArguments() { static Stream<Arguments> webServerWithAutomatedMultipartConfigurationArguments() {
return Stream.of(Arguments.of("Jetty", WebServerWithEverythingJetty.class), return Stream.of(Arguments.of("Jetty", WebServerWithEverythingJetty.class),
Arguments.of("Tomcat", WebServerWithEverythingTomcat.class), Arguments.of("Tomcat", WebServerWithEverythingTomcat.class));
Arguments.of("Undertow", WebServerWithEverythingUndertow.class));
}
@Test
void webServerWithNonAbsoluteMultipartLocationUndertowConfiguration() {
this.context = new AnnotationConfigServletWebServerApplicationContext(
WebServerWithNonAbsolutePathUndertow.class, BaseConfiguration.class);
this.context.getBean(MultipartConfigElement.class);
verifyServletWorks();
assertThat(this.context.getBean(StandardServletMultipartResolver.class))
.isSameAs(this.context.getBean(DispatcherServlet.class).getMultipartResolver());
} }
@Test @Test
@ -248,21 +235,6 @@ class MultipartAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class WebServerWithNoMultipartUndertow {
@Bean
UndertowServletWebServerFactory webServerFactory() {
return new UndertowServletWebServerFactory();
}
@Bean
WebController controller() {
return new WebController();
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Import({ TomcatServletWebServerAutoConfiguration.class, MultipartAutoConfiguration.class }) @Import({ TomcatServletWebServerAutoConfiguration.class, MultipartAutoConfiguration.class })
@EnableConfigurationProperties(MultipartProperties.class) @EnableConfigurationProperties(MultipartProperties.class)
@ -338,48 +310,6 @@ class MultipartAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
static class WebServerWithEverythingUndertow {
@Bean
MultipartConfigElement multipartConfigElement() {
return new MultipartConfigElement("");
}
@Bean
UndertowServletWebServerFactory webServerFactory() {
return new UndertowServletWebServerFactory();
}
@Bean
WebController webController() {
return new WebController();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
static class WebServerWithNonAbsolutePathUndertow {
@Bean
MultipartConfigElement multipartConfigElement() {
return new MultipartConfigElement("test/not-absolute");
}
@Bean
UndertowServletWebServerFactory webServerFactory() {
return new UndertowServletWebServerFactory();
}
@Bean
WebController webController() {
return new WebController();
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class WebServerWithCustomMultipartResolver { static class WebServerWithCustomMultipartResolver {

View File

@ -1,54 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Undertow"
dependencies {
api(project(":module:spring-boot-web-server"))
api("io.undertow:undertow-servlet")
optional(project(":core:spring-boot-autoconfigure"))
optional(project(":module:spring-boot-actuator-autoconfigure"))
optional("io.undertow:undertow-websockets-jsr")
optional("org.springframework:spring-webflux")
testImplementation(project(":core:spring-boot-test"))
testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation(testFixtures(project(":core:spring-boot-autoconfigure")))
testImplementation(testFixtures(project(":module:spring-boot-web-server")))
testImplementation("org.apache.httpcomponents.client5:httpclient5")
testImplementation("org.apache.tomcat.embed:tomcat-embed-jasper")
testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("io.projectreactor:reactor-test")
testRuntimeOnly("io.projectreactor.netty:reactor-netty-http")
testRuntimeOnly("org.eclipse.jetty:jetty-client")
testRuntimeOnly("org.eclipse.jetty.http2:jetty-http2-client")
testRuntimeOnly("org.eclipse.jetty.http2:jetty-http2-client-transport")
testRuntimeOnly("org.springframework:spring-webmvc")
}
test {
jvmArgs += "--add-opens=java.base/java.net=ALL-UNNAMED"
}

View File

@ -1,120 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.accesslog.AccessLogHandler;
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
import org.jspecify.annotations.Nullable;
import org.xnio.OptionMap;
import org.xnio.Options;
import org.xnio.Xnio;
import org.xnio.XnioWorker;
/**
* An {@link HttpHandlerFactory} for an {@link AccessLogHandler}.
*
* @author Andy Wilkinson
*/
class AccessLogHttpHandlerFactory implements HttpHandlerFactory {
private final File directory;
private final @Nullable String pattern;
private final @Nullable String prefix;
private final @Nullable String suffix;
private final boolean rotate;
AccessLogHttpHandlerFactory(File directory, @Nullable String pattern, @Nullable String prefix,
@Nullable String suffix, boolean rotate) {
this.directory = directory;
this.pattern = pattern;
this.prefix = prefix;
this.suffix = suffix;
this.rotate = rotate;
}
@Override
public HttpHandler getHandler(@Nullable HttpHandler next) {
try {
createAccessLogDirectoryIfNecessary();
XnioWorker worker = createWorker();
String baseName = (this.prefix != null) ? this.prefix : "access_log.";
String formatString = (this.pattern != null) ? this.pattern : "common";
return new ClosableAccessLogHandler(next, worker,
new DefaultAccessLogReceiver(worker, this.directory, baseName, this.suffix, this.rotate),
formatString);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to create AccessLogHandler", ex);
}
}
private void createAccessLogDirectoryIfNecessary() {
if (!this.directory.isDirectory() && !this.directory.mkdirs()) {
throw new IllegalStateException("Failed to create access log directory '" + this.directory + "'");
}
}
private XnioWorker createWorker() throws IOException {
Xnio xnio = Xnio.getInstance(Undertow.class.getClassLoader());
return xnio.createWorker(OptionMap.builder().set(Options.THREAD_DAEMON, true).getMap());
}
/**
* {@link Closeable} variant of {@link AccessLogHandler}.
*/
private static class ClosableAccessLogHandler extends AccessLogHandler implements Closeable {
private final DefaultAccessLogReceiver accessLogReceiver;
private final XnioWorker worker;
ClosableAccessLogHandler(@Nullable HttpHandler next, XnioWorker worker,
DefaultAccessLogReceiver accessLogReceiver, String formatString) {
super(next, accessLogReceiver, formatString, Undertow.class.getClassLoader());
this.worker = worker;
this.accessLogReceiver = accessLogReceiver;
}
@Override
public void close() throws IOException {
try {
this.accessLogReceiver.close();
this.worker.shutdown();
this.worker.awaitTermination(30, TimeUnit.SECONDS);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}

View File

@ -1,135 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.util.ArrayList;
import java.util.List;
import io.undertow.attribute.RequestHeaderAttribute;
import io.undertow.predicate.Predicate;
import io.undertow.predicate.Predicates;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.encoding.ContentEncodingRepository;
import io.undertow.server.handlers.encoding.EncodingHandler;
import io.undertow.server.handlers.encoding.GzipEncodingProvider;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.web.server.Compression;
import org.springframework.http.HttpHeaders;
import org.springframework.util.InvalidMimeTypeException;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* {@link HttpHandlerFactory} that adds a compression handler.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
class CompressionHttpHandlerFactory implements HttpHandlerFactory {
private final Compression compression;
CompressionHttpHandlerFactory(Compression compression) {
this.compression = compression;
}
@Override
public @Nullable HttpHandler getHandler(@Nullable HttpHandler next) {
if (!this.compression.getEnabled()) {
return next;
}
ContentEncodingRepository repository = new ContentEncodingRepository();
repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
Predicates.and(getCompressionPredicates(this.compression)));
return new EncodingHandler(repository).setNext(next);
}
private static Predicate[] getCompressionPredicates(Compression compression) {
List<Predicate> predicates = new ArrayList<>();
predicates.add(new MaxSizePredicate((int) compression.getMinResponseSize().toBytes()));
predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes()));
if (compression.getExcludedUserAgents() != null) {
for (String agent : compression.getExcludedUserAgents()) {
RequestHeaderAttribute agentHeader = new RequestHeaderAttribute(new HttpString(HttpHeaders.USER_AGENT));
predicates.add(Predicates.not(Predicates.regex(agentHeader, agent)));
}
}
return predicates.toArray(new Predicate[0]);
}
/**
* Predicate used to match specific mime types.
*/
private static class CompressibleMimeTypePredicate implements Predicate {
private final List<MimeType> mimeTypes;
CompressibleMimeTypePredicate(String[] mimeTypes) {
this.mimeTypes = new ArrayList<>(mimeTypes.length);
for (String mimeTypeString : mimeTypes) {
this.mimeTypes.add(MimeTypeUtils.parseMimeType(mimeTypeString));
}
}
@Override
public boolean resolve(HttpServerExchange value) {
String contentType = value.getResponseHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (contentType != null) {
try {
MimeType parsed = MimeTypeUtils.parseMimeType(contentType);
for (MimeType mimeType : this.mimeTypes) {
if (mimeType.isCompatibleWith(parsed)) {
return true;
}
}
}
catch (InvalidMimeTypeException ex) {
return false;
}
}
return false;
}
}
/**
* Predicate that returns true if the Content-Size of a request is above a given value
* or is missing.
*/
private static class MaxSizePredicate implements Predicate {
private final Predicate maxContentSize;
MaxSizePredicate(int size) {
this.maxContentSize = Predicates.requestLargerThan(size);
}
@Override
public boolean resolve(HttpServerExchange value) {
if (value.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) {
return this.maxContentSize.resolve(value);
}
return true;
}
}
}

View File

@ -1,116 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.io.File;
import java.util.Collection;
import io.undertow.Undertow.Builder;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
/**
* {@link ConfigurableWebServerFactory} for Undertow-specific features.
*
* @author Brian Clozel
* @since 4.0.0
*/
public interface ConfigurableUndertowWebServerFactory extends ConfigurableWebServerFactory {
/**
* Set {@link UndertowBuilderCustomizer}s that should be applied to the Undertow
* {@link Builder}. Calling this method will replace any existing customizers.
* @param customizers the customizers to set
* @since 4.0.0
*/
void setBuilderCustomizers(Collection<? extends UndertowBuilderCustomizer> customizers);
/**
* Add {@link UndertowBuilderCustomizer}s that should be used to customize the
* Undertow {@link Builder}.
* @param customizers the customizers to add
*/
void addBuilderCustomizers(UndertowBuilderCustomizer... customizers);
/**
* Set the buffer size.
* @param bufferSize buffer size
*/
void setBufferSize(@Nullable Integer bufferSize);
/**
* Set the number of IO Threads.
* @param ioThreads number of IO Threads
*/
void setIoThreads(@Nullable Integer ioThreads);
/**
* Set the number of Worker Threads.
* @param workerThreads number of Worker Threads
*/
void setWorkerThreads(@Nullable Integer workerThreads);
/**
* Set whether direct buffers should be used.
* @param useDirectBuffers whether direct buffers should be used
*/
void setUseDirectBuffers(@Nullable Boolean useDirectBuffers);
/**
* Set the access log directory.
* @param accessLogDirectory access log directory
*/
void setAccessLogDirectory(@Nullable File accessLogDirectory);
/**
* Set the access log pattern.
* @param accessLogPattern access log pattern
*/
void setAccessLogPattern(@Nullable String accessLogPattern);
/**
* Set the access log prefix.
* @param accessLogPrefix log prefix
*/
void setAccessLogPrefix(@Nullable String accessLogPrefix);
/**
* Set the access log suffix.
* @param accessLogSuffix access log suffix
*/
void setAccessLogSuffix(@Nullable String accessLogSuffix);
/**
* Set whether access logs are enabled.
* @param accessLogEnabled whether access logs are enabled
*/
void setAccessLogEnabled(boolean accessLogEnabled);
/**
* Set whether access logs rotation is enabled.
* @param accessLogRotate whether access logs rotation is enabled
*/
void setAccessLogRotate(boolean accessLogRotate);
/**
* Set if x-forward-* headers should be processed.
* @param useForwardHeaders if x-forward headers should be used
*/
void setUseForwardHeaders(boolean useForwardHeaders);
}

View File

@ -1,49 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.io.Closeable;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.GracefulShutdownHandler;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.undertow.servlet.UndertowServletWebServer;
/**
* Factory used by {@link UndertowServletWebServer} to add {@link HttpHandler
* HttpHandlers}. Instances returned from this factory may optionally implement the
* following interfaces:
* <ul>
* <li>{@link Closeable} - if they wish to be closed just before server stops.</li>
* <li>{@link GracefulShutdownHandler} - if they wish to manage graceful shutdown.</li>
* </ul>
*
* @author Phillip Webb
* @since 4.0.0
*/
@FunctionalInterface
public interface HttpHandlerFactory {
/**
* Create the {@link HttpHandler} instance that should be added.
* @param next the next handler in the chain
* @return the new HTTP handler instance
*/
@Nullable HttpHandler getHandler(@Nullable HttpHandler next);
}

View File

@ -1,94 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.net.InetAddress;
import java.util.Map;
import javax.net.ssl.SSLContext;
import io.undertow.Undertow;
import io.undertow.protocols.ssl.SNIContextMatcher;
import io.undertow.protocols.ssl.SNISSLContext;
import org.jspecify.annotations.Nullable;
import org.xnio.Options;
import org.xnio.Sequence;
import org.xnio.SslClientAuthMode;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.boot.web.server.Ssl.ClientAuth;
/**
* {@link UndertowBuilderCustomizer} that configures SSL on the given builder instance.
*
* @author Brian Clozel
* @author Raheela Aslam
* @author Cyril Dangerville
* @author Scott Frederick
*/
class SslBuilderCustomizer implements UndertowBuilderCustomizer {
private final int port;
private final @Nullable InetAddress address;
private final @Nullable ClientAuth clientAuth;
private final SslBundle sslBundle;
private final Map<String, SslBundle> serverNameSslBundles;
SslBuilderCustomizer(int port, @Nullable InetAddress address, @Nullable ClientAuth clientAuth, SslBundle sslBundle,
Map<String, SslBundle> serverNameSslBundles) {
this.port = port;
this.address = address;
this.clientAuth = clientAuth;
this.sslBundle = sslBundle;
this.serverNameSslBundles = serverNameSslBundles;
}
@Override
public void customize(Undertow.Builder builder) {
SslOptions options = this.sslBundle.getOptions();
builder.addHttpsListener(this.port, getListenAddress(), createSslContext());
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, ClientAuth.map(this.clientAuth,
SslClientAuthMode.NOT_REQUESTED, SslClientAuthMode.REQUESTED, SslClientAuthMode.REQUIRED));
if (options.getEnabledProtocols() != null) {
builder.setSocketOption(Options.SSL_ENABLED_PROTOCOLS, Sequence.of(options.getEnabledProtocols()));
}
if (options.getCiphers() != null) {
builder.setSocketOption(Options.SSL_ENABLED_CIPHER_SUITES, Sequence.of(options.getCiphers()));
}
}
private SSLContext createSslContext() {
SNIContextMatcher.Builder builder = new SNIContextMatcher.Builder();
builder.setDefaultContext(this.sslBundle.createSslContext());
this.serverNameSslBundles
.forEach((serverName, sslBundle) -> builder.addMatch(serverName, sslBundle.createSslContext()));
return new SNISSLContext(builder.build());
}
private String getListenAddress() {
if (this.address == null) {
return "0.0.0.0";
}
return this.address.getHostAddress();
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import io.undertow.Undertow.Builder;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
/**
* Callback interface that can be used to customize an Undertow {@link Builder}.
*
* @author Andy Wilkinson
* @since 4.0.0
* @see UndertowServletWebServerFactory
*/
@FunctionalInterface
public interface UndertowBuilderCustomizer {
/**
* Customize the builder.
* @param builder the {@code Builder} to customize
*/
void customize(Builder builder);
}

View File

@ -1,484 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.GracefulShutdownHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.xnio.channels.BoundChannel;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.web.server.GracefulShutdownCallback;
import org.springframework.boot.web.server.GracefulShutdownResult;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link WebServer} that can be used to control an Undertow web server. Usually this
* class should be created using a {@link ConfigurableUndertowWebServerFactory} and not
* directly.
*
* @author Ivan Sopov
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Christoph Dreis
* @author Brian Clozel
* @since 4.0.0
*/
public class UndertowWebServer implements WebServer {
private static final Log logger = LogFactory.getLog(UndertowWebServer.class);
private final AtomicReference<@Nullable GracefulShutdownCallback> gracefulShutdownCallback = new AtomicReference<>();
private final Object monitor = new Object();
private final Undertow.Builder builder;
private final Iterable<HttpHandlerFactory> httpHandlerFactories;
private final boolean autoStart;
private @Nullable Undertow undertow;
private volatile boolean started = false;
private volatile @Nullable GracefulShutdownHandler gracefulShutdown;
private volatile @Nullable List<Closeable> closeables;
/**
* Create a new {@link UndertowWebServer} instance.
* @param builder the builder
* @param autoStart if the server should be started
*/
public UndertowWebServer(Undertow.Builder builder, boolean autoStart) {
this(builder, Collections.singleton(new CloseableHttpHandlerFactory(null)), autoStart);
}
/**
* Create a new {@link UndertowWebServer} instance.
* @param builder the builder
* @param httpHandlerFactories the handler factories
* @param autoStart if the server should be started
* @since 4.0.0
*/
public UndertowWebServer(Undertow.Builder builder, Iterable<HttpHandlerFactory> httpHandlerFactories,
boolean autoStart) {
this.builder = builder;
this.httpHandlerFactories = httpHandlerFactories;
this.autoStart = autoStart;
}
@Override
public void start() throws WebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
if (!this.autoStart) {
return;
}
if (this.undertow == null) {
this.undertow = createUndertowServer();
}
this.undertow.start();
this.started = true;
String message = getStartedLogMessage();
logger.info(message);
}
catch (Exception ex) {
try {
PortInUseException.ifPortBindingException(ex, (bindException) -> {
List<Port> failedPorts = getConfiguredPorts();
failedPorts.removeAll(getActualPorts());
if (failedPorts.size() == 1) {
throw new PortInUseException(failedPorts.get(0).getNumber());
}
});
throw new WebServerException("Unable to start embedded Undertow", ex);
}
finally {
destroySilently();
}
}
}
}
private void destroySilently() {
try {
if (this.undertow != null) {
this.undertow.stop();
Assert.state(this.closeables != null, "'closeables' must not be null");
this.closeables.forEach(this::closeSilently);
}
}
catch (Exception ex) {
// Ignore
}
}
private void closeSilently(Closeable closeable) {
try {
closeable.close();
}
catch (Exception ex) {
// Ignore
}
}
private Undertow createUndertowServer() {
this.closeables = new ArrayList<>();
this.gracefulShutdown = null;
HttpHandler handler = createHttpHandler();
this.builder.setHandler(handler);
return this.builder.build();
}
protected @Nullable HttpHandler createHttpHandler() {
HttpHandler handler = null;
for (HttpHandlerFactory factory : this.httpHandlerFactories) {
handler = factory.getHandler(handler);
if (handler instanceof Closeable closeable) {
Assert.state(this.closeables != null, "'closeables' must not be null");
this.closeables.add(closeable);
}
if (handler instanceof GracefulShutdownHandler shutdownHandler) {
Assert.state(this.gracefulShutdown == null, "Only a single GracefulShutdownHandler can be defined");
this.gracefulShutdown = shutdownHandler;
}
}
return handler;
}
private String getPortsDescription() {
StringBuilder description = new StringBuilder();
List<UndertowWebServer.Port> ports = getActualPorts();
description.append("port");
if (ports.size() != 1) {
description.append("s");
}
description.append(" ");
if (!ports.isEmpty()) {
description.append(StringUtils.collectionToDelimitedString(ports, ", "));
}
else {
description.append("unknown");
}
return description.toString();
}
private List<Port> getActualPorts() {
List<Port> ports = new ArrayList<>();
try {
if (!this.autoStart) {
ports.add(new Port(-1, "unknown"));
}
else {
for (BoundChannel channel : extractChannels()) {
ports.add(getPortFromChannel(channel));
}
}
}
catch (Exception ex) {
// Continue
}
return ports;
}
@SuppressWarnings("unchecked")
private List<BoundChannel> extractChannels() {
Field channelsField = ReflectionUtils.findField(Undertow.class, "channels");
Assert.state(channelsField != null, "'channelsField' must not be null");
ReflectionUtils.makeAccessible(channelsField);
List<BoundChannel> channels = (List<BoundChannel>) ReflectionUtils.getField(channelsField, this.undertow);
Assert.state(channels != null, "'channels' must not be null");
return channels;
}
private UndertowWebServer.@Nullable Port getPortFromChannel(BoundChannel channel) {
SocketAddress socketAddress = channel.getLocalAddress();
if (socketAddress instanceof InetSocketAddress inetSocketAddress) {
Field sslField = ReflectionUtils.findField(channel.getClass(), "ssl");
String protocol = (sslField != null) ? "https" : "http";
return new UndertowWebServer.Port(inetSocketAddress.getPort(), protocol);
}
return null;
}
private List<UndertowWebServer.Port> getConfiguredPorts() {
List<Port> ports = new ArrayList<>();
for (Object listener : extractListeners()) {
try {
Port port = getPortFromListener(listener);
if (port.getNumber() != 0) {
ports.add(port);
}
}
catch (Exception ex) {
// Continue
}
}
return ports;
}
@SuppressWarnings("unchecked")
private List<Object> extractListeners() {
Field listenersField = ReflectionUtils.findField(Undertow.class, "listeners");
Assert.state(listenersField != null, "'listenersField' must not be null");
ReflectionUtils.makeAccessible(listenersField);
List<Object> listeners = (List<Object>) ReflectionUtils.getField(listenersField, this.undertow);
Assert.state(listeners != null, "'listeners' must not be null");
return listeners;
}
private UndertowWebServer.Port getPortFromListener(Object listener) {
Field typeField = ReflectionUtils.findField(listener.getClass(), "type");
Assert.state(typeField != null, "'typeField' must not be null");
String protocol = getProtocol(listener, typeField);
Field portField = ReflectionUtils.findField(listener.getClass(), "port");
Assert.state(portField != null, "'portField' must not be null");
int port = getPort(listener, portField);
return new UndertowWebServer.Port(port, protocol);
}
private static Integer getPort(Object listener, Field portField) {
ReflectionUtils.makeAccessible(portField);
Integer value = (Integer) ReflectionUtils.getField(portField, listener);
Assert.state(value != null, "'value' must not be null");
return value;
}
private String getProtocol(Object listener, Field typeField) {
ReflectionUtils.makeAccessible(typeField);
Object value = ReflectionUtils.getField(typeField, listener);
Assert.state(value != null, "'value' must not be null");
return value.toString();
}
@Override
public void stop() throws WebServerException {
synchronized (this.monitor) {
if (!this.started) {
return;
}
this.started = false;
if (this.gracefulShutdown != null) {
notifyGracefulCallback(false);
}
try {
if (this.undertow != null) {
this.undertow.stop();
Assert.state(this.closeables != null, "'closeables' must not be null");
for (Closeable closeable : this.closeables) {
closeable.close();
}
}
}
catch (Exception ex) {
throw new WebServerException("Unable to stop Undertow", ex);
}
}
}
@Override
public int getPort() {
List<Port> ports = getActualPorts();
if (ports.isEmpty()) {
return -1;
}
return ports.get(0).getNumber();
}
/**
* Returns the {@link Undertow Undertow server}. Returns {@code null} until the server
* has been started.
* @return the Undertow server or {@code null} if the server hasn't been started yet
* @since 4.0.0
*/
public @Nullable Undertow getUndertow() {
return this.undertow;
}
/**
* Initiates a graceful shutdown of the Undertow web server. Handling of new requests
* is prevented and the given {@code callback} is invoked at the end of the attempt.
* The attempt can be explicitly ended by invoking {@link #stop}.
* <p>
* Once shutdown has been initiated Undertow will return an {@code HTTP 503} response
* for any new or existing connections.
*/
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
this.gracefulShutdownCallback.set(callback);
this.gracefulShutdown.shutdown();
this.gracefulShutdown.addShutdownListener(this::notifyGracefulCallback);
}
private void notifyGracefulCallback(boolean success) {
GracefulShutdownCallback callback = this.gracefulShutdownCallback.getAndSet(null);
if (callback != null) {
if (success) {
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
else {
logger.info("Graceful shutdown aborted with one or more requests still active");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
}
}
}
protected String getStartedLogMessage() {
return "Undertow started on " + getPortsDescription();
}
/**
* An active Undertow port.
*/
private static final class Port {
private final int number;
private final String protocol;
private Port(int number, String protocol) {
this.number = number;
this.protocol = protocol;
}
int getNumber() {
return this.number;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
UndertowWebServer.Port other = (UndertowWebServer.Port) obj;
return this.number == other.number;
}
@Override
public int hashCode() {
return this.number;
}
@Override
public String toString() {
return this.number + " (" + this.protocol + ")";
}
}
/**
* {@link HttpHandlerFactory} to wrap a closable.
*/
private static final class CloseableHttpHandlerFactory implements HttpHandlerFactory {
private final @Nullable Closeable closeable;
private CloseableHttpHandlerFactory(@Nullable Closeable closeable) {
this.closeable = closeable;
}
@Override
public @Nullable HttpHandler getHandler(@Nullable HttpHandler next) {
Closeable closeable = this.closeable;
if (closeable == null) {
return next;
}
return new CloseableHttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if (next != null) {
next.handleRequest(exchange);
}
}
@Override
public void close() throws IOException {
closeable.close();
}
};
}
}
/**
* {@link Closeable} {@link HttpHandler}.
*/
private interface CloseableHttpHandler extends HttpHandler, Closeable {
}
/**
* {@link RuntimeHintsRegistrar} that allows Undertow's configured and actual ports to
* be retrieved at runtime in a native image.
*/
static class UndertowWebServerRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection()
.registerTypeIfPresent(classLoader, "io.undertow.Undertow",
(hint) -> hint.withField("listeners").withField("channels"));
hints.reflection()
.registerTypeIfPresent(classLoader, "io.undertow.Undertow$ListenerConfig",
(hint) -> hint.withField("type").withField("port"));
hints.reflection()
.registerTypeIfPresent(classLoader, "io.undertow.protocols.ssl.UndertowAcceptingSslChannel",
(hint) -> hint.withField("ssl"));
}
}
}

View File

@ -1,237 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.io.File;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.UndertowOptions;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.Ssl;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class for factories that produce an {@link UndertowWebServer}.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
public abstract class UndertowWebServerFactory extends AbstractConfigurableWebServerFactory
implements ConfigurableUndertowWebServerFactory {
private Set<UndertowBuilderCustomizer> builderCustomizers = new LinkedHashSet<>();
private @Nullable Integer bufferSize;
private @Nullable Integer ioThreads;
private @Nullable Integer workerThreads;
private @Nullable Boolean directBuffers;
private @Nullable File accessLogDirectory;
private @Nullable String accessLogPattern;
private @Nullable String accessLogPrefix;
private @Nullable String accessLogSuffix;
private boolean accessLogEnabled;
private boolean accessLogRotate = true;
private boolean useForwardHeaders;
protected UndertowWebServerFactory() {
}
protected UndertowWebServerFactory(int port) {
super(port);
}
public Collection<UndertowBuilderCustomizer> getBuilderCustomizers() {
return this.builderCustomizers;
}
@Override
public void setBuilderCustomizers(Collection<? extends UndertowBuilderCustomizer> customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
this.builderCustomizers = new LinkedHashSet<>(customizers);
}
@Override
public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
this.builderCustomizers.addAll(Arrays.asList(customizers));
}
@Override
public void setBufferSize(@Nullable Integer bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public void setIoThreads(@Nullable Integer ioThreads) {
this.ioThreads = ioThreads;
}
@Override
public void setWorkerThreads(@Nullable Integer workerThreads) {
this.workerThreads = workerThreads;
}
@Override
public void setUseDirectBuffers(@Nullable Boolean directBuffers) {
this.directBuffers = directBuffers;
}
@Override
public void setAccessLogDirectory(@Nullable File accessLogDirectory) {
this.accessLogDirectory = accessLogDirectory;
}
@Override
public void setAccessLogPattern(@Nullable String accessLogPattern) {
this.accessLogPattern = accessLogPattern;
}
@Override
public void setAccessLogPrefix(@Nullable String accessLogPrefix) {
this.accessLogPrefix = accessLogPrefix;
}
public @Nullable String getAccessLogPrefix() {
return this.accessLogPrefix;
}
@Override
public void setAccessLogSuffix(@Nullable String accessLogSuffix) {
this.accessLogSuffix = accessLogSuffix;
}
@Override
public void setAccessLogEnabled(boolean accessLogEnabled) {
this.accessLogEnabled = accessLogEnabled;
}
public boolean isAccessLogEnabled() {
return this.accessLogEnabled;
}
@Override
public void setAccessLogRotate(boolean accessLogRotate) {
this.accessLogRotate = accessLogRotate;
}
@Override
public void setUseForwardHeaders(boolean useForwardHeaders) {
this.useForwardHeaders = useForwardHeaders;
}
public boolean isUseForwardHeaders() {
return this.useForwardHeaders;
}
public Builder createBuilder(AbstractConfigurableWebServerFactory factory, Supplier<SslBundle> sslBundleSupplier,
Supplier<Map<String, SslBundle>> serverNameSslBundlesSupplier) {
InetAddress address = factory.getAddress();
int port = factory.getPort();
Builder builder = Undertow.builder();
if (this.bufferSize != null) {
builder.setBufferSize(this.bufferSize);
}
if (this.ioThreads != null) {
builder.setIoThreads(this.ioThreads);
}
if (this.workerThreads != null) {
builder.setWorkerThreads(this.workerThreads);
}
if (this.directBuffers != null) {
builder.setDirectBuffers(this.directBuffers);
}
Http2 http2 = factory.getHttp2();
if (http2 != null) {
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2.isEnabled());
}
Ssl ssl = factory.getSsl();
if (Ssl.isEnabled(ssl)) {
new SslBuilderCustomizer(factory.getPort(), address, ssl.getClientAuth(), sslBundleSupplier.get(),
serverNameSslBundlesSupplier.get())
.customize(builder);
}
else {
builder.addHttpListener(port, (address != null) ? address.getHostAddress() : "0.0.0.0");
}
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
return builder;
}
public List<HttpHandlerFactory> createHttpHandlerFactories(AbstractConfigurableWebServerFactory webServerFactory,
HttpHandlerFactory... initialHttpHandlerFactories) {
List<HttpHandlerFactory> factories = createHttpHandlerFactories(webServerFactory.getCompression(),
this.useForwardHeaders, webServerFactory.getServerHeader(), webServerFactory.getShutdown(),
initialHttpHandlerFactories);
if (isAccessLogEnabled()) {
Assert.state(this.accessLogDirectory != null, "Access log directory is not set");
factories.add(new AccessLogHttpHandlerFactory(this.accessLogDirectory, this.accessLogPattern,
this.accessLogPrefix, this.accessLogSuffix, this.accessLogRotate));
}
return factories;
}
static List<HttpHandlerFactory> createHttpHandlerFactories(@Nullable Compression compression,
boolean useForwardHeaders, @Nullable String serverHeader, Shutdown shutdown,
HttpHandlerFactory... initialHttpHandlerFactories) {
List<HttpHandlerFactory> factories = new ArrayList<>(Arrays.asList(initialHttpHandlerFactories));
if (compression != null && compression.getEnabled()) {
factories.add(new CompressionHttpHandlerFactory(compression));
}
if (useForwardHeaders) {
factories.add(Handlers::proxyPeerAddress);
}
if (StringUtils.hasText(serverHeader)) {
factories.add((next) -> Handlers.header(next, "Server", serverHeader));
}
if (shutdown == Shutdown.GRACEFUL) {
factories.add(Handlers::gracefulShutdown);
}
return factories;
}
}

View File

@ -1,406 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import io.undertow.UndertowOptions;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
/**
* Undertow server properties.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Ivan Sopov
* @author Marcos Barbero
* @author Eddú Meléndez
* @author Quinten De Swaef
* @author Venil Noronha
* @author Aurélien Leboulanger
* @author Brian Clozel
* @author Olivier Lamy
* @author Chentao Qu
* @author Artsiom Yudovin
* @author Andrew McGhie
* @author Rafiullah Hamedy
* @author Dirk Deyne
* @author HaiTao Zhang
* @author Victor Mandujano
* @author Chris Bono
* @author Parviz Rozikov
* @author Florian Storz
* @author Michael Weidmann
* @author Lasse Wulff
* @since 4.0.0
*/
@ConfigurationProperties("server.undertow")
public class UndertowServerProperties {
/**
* Maximum size of the HTTP post content. When the value is -1, the default, the size
* is unlimited.
*/
private DataSize maxHttpPostSize = DataSize.ofBytes(-1);
/**
* Size of each buffer. The default is derived from the maximum amount of memory that
* is available to the JVM.
*/
private @Nullable DataSize bufferSize;
/**
* Whether to allocate buffers outside the Java heap. The default is derived from the
* maximum amount of memory that is available to the JVM.
*/
private @Nullable Boolean directBuffers;
/**
* Whether servlet filters should be initialized on startup.
*/
private boolean eagerFilterInit = true;
/**
* Maximum number of query or path parameters that are allowed. This limit exists to
* prevent hash collision based DOS attacks.
*/
private int maxParameters = UndertowOptions.DEFAULT_MAX_PARAMETERS;
/**
* Maximum number of headers that are allowed. This limit exists to prevent hash
* collision based DOS attacks.
*/
private int maxHeaders = UndertowOptions.DEFAULT_MAX_HEADERS;
/**
* Maximum number of cookies that are allowed. This limit exists to prevent hash
* collision based DOS attacks.
*/
private int maxCookies = 200;
/**
* Whether encoded slash characters (%2F) should be decoded. Decoding can cause
* security problems if a front-end proxy does not perform the same decoding. Only
* enable this if you have a legacy application that requires it. When set,
* server.undertow.allow-encoded-slash has no effect.
*/
private @Nullable Boolean decodeSlash;
/**
* Whether the URL should be decoded. When disabled, percent-encoded characters in the
* URL will be left as-is.
*/
private boolean decodeUrl = true;
/**
* Charset used to decode URLs.
*/
private Charset urlCharset = StandardCharsets.UTF_8;
/**
* Whether the 'Connection: keep-alive' header should be added to all responses, even
* if not required by the HTTP specification.
*/
private boolean alwaysSetKeepAlive = true;
/**
* Amount of time a connection can sit idle without processing a request, before it is
* closed by the server.
*/
private @Nullable Duration noRequestTimeout;
/**
* Whether to preserve the path of a request when it is forwarded.
*/
private boolean preservePathOnForward = false;
private final Accesslog accesslog = new Accesslog();
/**
* Thread related configuration.
*/
private final Threads threads = new Threads();
private final Options options = new Options();
public DataSize getMaxHttpPostSize() {
return this.maxHttpPostSize;
}
public void setMaxHttpPostSize(DataSize maxHttpPostSize) {
this.maxHttpPostSize = maxHttpPostSize;
}
public @Nullable DataSize getBufferSize() {
return this.bufferSize;
}
public void setBufferSize(@Nullable DataSize bufferSize) {
this.bufferSize = bufferSize;
}
public @Nullable Boolean getDirectBuffers() {
return this.directBuffers;
}
public void setDirectBuffers(@Nullable Boolean directBuffers) {
this.directBuffers = directBuffers;
}
public boolean isEagerFilterInit() {
return this.eagerFilterInit;
}
public void setEagerFilterInit(boolean eagerFilterInit) {
this.eagerFilterInit = eagerFilterInit;
}
public int getMaxParameters() {
return this.maxParameters;
}
public void setMaxParameters(Integer maxParameters) {
this.maxParameters = maxParameters;
}
public int getMaxHeaders() {
return this.maxHeaders;
}
public void setMaxHeaders(int maxHeaders) {
this.maxHeaders = maxHeaders;
}
public Integer getMaxCookies() {
return this.maxCookies;
}
public void setMaxCookies(Integer maxCookies) {
this.maxCookies = maxCookies;
}
public @Nullable Boolean getDecodeSlash() {
return this.decodeSlash;
}
public void setDecodeSlash(@Nullable Boolean decodeSlash) {
this.decodeSlash = decodeSlash;
}
public boolean isDecodeUrl() {
return this.decodeUrl;
}
public void setDecodeUrl(Boolean decodeUrl) {
this.decodeUrl = decodeUrl;
}
public Charset getUrlCharset() {
return this.urlCharset;
}
public void setUrlCharset(Charset urlCharset) {
this.urlCharset = urlCharset;
}
public boolean isAlwaysSetKeepAlive() {
return this.alwaysSetKeepAlive;
}
public void setAlwaysSetKeepAlive(boolean alwaysSetKeepAlive) {
this.alwaysSetKeepAlive = alwaysSetKeepAlive;
}
public @Nullable Duration getNoRequestTimeout() {
return this.noRequestTimeout;
}
public void setNoRequestTimeout(@Nullable Duration noRequestTimeout) {
this.noRequestTimeout = noRequestTimeout;
}
public boolean isPreservePathOnForward() {
return this.preservePathOnForward;
}
public void setPreservePathOnForward(boolean preservePathOnForward) {
this.preservePathOnForward = preservePathOnForward;
}
public UndertowServerProperties.Accesslog getAccesslog() {
return this.accesslog;
}
public UndertowServerProperties.Threads getThreads() {
return this.threads;
}
public UndertowServerProperties.Options getOptions() {
return this.options;
}
/**
* Undertow access log properties.
*/
public static class Accesslog {
/**
* Whether to enable the access log.
*/
private boolean enabled = false;
/**
* Format pattern for access logs.
*/
private String pattern = "common";
/**
* Log file name prefix.
*/
protected String prefix = "access_log.";
/**
* Log file name suffix.
*/
private String suffix = "log";
/**
* Undertow access log directory.
*/
private File dir = new File("logs");
/**
* Whether to enable access log rotation.
*/
private boolean rotate = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getPattern() {
return this.pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return this.suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public File getDir() {
return this.dir;
}
public void setDir(File dir) {
this.dir = dir;
}
public boolean isRotate() {
return this.rotate;
}
public void setRotate(boolean rotate) {
this.rotate = rotate;
}
}
/**
* Undertow thread properties.
*/
public static class Threads {
/**
* Number of I/O threads to create for the worker. The default is derived from the
* number of available processors.
*/
private @Nullable Integer io;
/**
* Number of worker threads. The default is 8 times the number of I/O threads.
*/
private @Nullable Integer worker;
public @Nullable Integer getIo() {
return this.io;
}
public void setIo(@Nullable Integer io) {
this.io = io;
}
public @Nullable Integer getWorker() {
return this.worker;
}
public void setWorker(@Nullable Integer worker) {
this.worker = worker;
}
}
public static class Options {
/**
* Socket options as defined in org.xnio.Options.
*/
private final Map<String, String> socket = new LinkedHashMap<>();
/**
* Server options as defined in io.undertow.UndertowOptions.
*/
private final Map<String, String> server = new LinkedHashMap<>();
public Map<String, String> getServer() {
return this.server;
}
public Map<String, String> getSocket() {
return this.socket;
}
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment;
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* {@link Configuration Configuration} for an Undertow-based reactive or servlet web
* server.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@ConditionalOnNotWarDeployment
@Configuration(proxyBeanMethods = false)
public class UndertowWebServerConfiguration {
@Bean
UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties, UndertowServerProperties undertowProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties, undertowProperties);
}
}

View File

@ -1,239 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import io.undertow.UndertowOptions;
import org.xnio.Option;
import org.xnio.Options;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.undertow.ConfigurableUndertowWebServerFactory;
import org.springframework.boot.undertow.autoconfigure.UndertowServerProperties.Accesslog;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.unit.DataSize;
/**
* Customization for Undertow-specific features common for both Servlet and Reactive
* servers.
*
* @author Brian Clozel
* @author Yulin Qin
* @author Stephane Nicoll
* @author Phillip Webb
* @author Arstiom Yudovin
* @author Rafiullah Hamedy
* @author HaiTao Zhang
* @since 4.0.0
*/
public class UndertowWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<ConfigurableUndertowWebServerFactory>, Ordered {
private final Environment environment;
private final ServerProperties serverProperties;
private final UndertowServerProperties undertowProperties;
public UndertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties,
UndertowServerProperties undertowProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
this.undertowProperties = undertowProperties;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void customize(ConfigurableUndertowWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
ServerOptions options = new ServerOptions(factory);
map.from(this.serverProperties::getMaxHttpRequestHeaderSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to(options.option(UndertowOptions.MAX_HEADER_SIZE));
mapUndertowProperties(factory, options);
mapAccessLogProperties(factory);
map.from(this::getOrDeduceUseForwardHeaders).to(factory::setUseForwardHeaders);
}
private void mapUndertowProperties(ConfigurableUndertowWebServerFactory factory, ServerOptions serverOptions) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.undertowProperties::getBufferSize)
.whenNonNull()
.asInt(DataSize::toBytes)
.to(factory::setBufferSize);
UndertowServerProperties.Threads threadProperties = this.undertowProperties.getThreads();
map.from(threadProperties::getIo).to(factory::setIoThreads);
map.from(threadProperties::getWorker).to(factory::setWorkerThreads);
map.from(this.undertowProperties::getDirectBuffers).to(factory::setUseDirectBuffers);
map.from(this.undertowProperties::getMaxHttpPostSize)
.as(DataSize::toBytes)
.when(this::isPositive)
.to(serverOptions.option(UndertowOptions.MAX_ENTITY_SIZE));
map.from(this.undertowProperties::getMaxParameters).to(serverOptions.option(UndertowOptions.MAX_PARAMETERS));
map.from(this.undertowProperties::getMaxHeaders).to(serverOptions.option(UndertowOptions.MAX_HEADERS));
map.from(this.undertowProperties::getMaxCookies).to(serverOptions.option(UndertowOptions.MAX_COOKIES));
mapSlashProperty(this.undertowProperties, serverOptions);
map.from(this.undertowProperties::isDecodeUrl).to(serverOptions.option(UndertowOptions.DECODE_URL));
map.from(this.undertowProperties::getUrlCharset)
.as(Charset::name)
.to(serverOptions.option(UndertowOptions.URL_CHARSET));
map.from(this.undertowProperties::isAlwaysSetKeepAlive)
.to(serverOptions.option(UndertowOptions.ALWAYS_SET_KEEP_ALIVE));
map.from(this.undertowProperties::getNoRequestTimeout)
.asInt(Duration::toMillis)
.to(serverOptions.option(UndertowOptions.NO_REQUEST_TIMEOUT));
map.from(this.undertowProperties.getOptions()::getServer).to(serverOptions.forEach(serverOptions::option));
SocketOptions socketOptions = new SocketOptions(factory);
map.from(this.undertowProperties.getOptions()::getSocket).to(socketOptions.forEach(socketOptions::option));
}
private void mapSlashProperty(UndertowServerProperties properties, ServerOptions serverOptions) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::getDecodeSlash).to(serverOptions.option(UndertowOptions.DECODE_SLASH));
}
private boolean isPositive(Number value) {
return value.longValue() > 0;
}
private void mapAccessLogProperties(ConfigurableUndertowWebServerFactory factory) {
Accesslog properties = this.undertowProperties.getAccesslog();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::isEnabled).to(factory::setAccessLogEnabled);
map.from(properties::getDir).to(factory::setAccessLogDirectory);
map.from(properties::getPattern).to(factory::setAccessLogPattern);
map.from(properties::getPrefix).to(factory::setAccessLogPrefix);
map.from(properties::getSuffix).to(factory::setAccessLogSuffix);
map.from(properties::isRotate).to(factory::setAccessLogRotate);
}
private boolean getOrDeduceUseForwardHeaders() {
if (this.serverProperties.getForwardHeadersStrategy() == null) {
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
}
return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
}
private abstract static class AbstractOptions {
private final Class<?> source;
private final Map<String, Option<?>> nameLookup;
private final ConfigurableUndertowWebServerFactory factory;
AbstractOptions(Class<?> source, ConfigurableUndertowWebServerFactory factory) {
Map<String, Option<?>> lookup = new HashMap<>();
ReflectionUtils.doWithLocalFields(source, (field) -> {
int modifiers = field.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)
&& Option.class.isAssignableFrom(field.getType())) {
try {
Option<?> option = (Option<?>) field.get(null);
lookup.put(getCanonicalName(field.getName()), option);
}
catch (IllegalAccessException ex) {
// Ignore
}
}
});
this.source = source;
this.nameLookup = Collections.unmodifiableMap(lookup);
this.factory = factory;
}
protected ConfigurableUndertowWebServerFactory getFactory() {
return this.factory;
}
@SuppressWarnings("unchecked")
<T> Consumer<Map<String, String>> forEach(Function<Option<T>, Consumer<T>> function) {
return (map) -> map.forEach((key, value) -> {
Option<T> option = (Option<T>) this.nameLookup.get(getCanonicalName(key));
Assert.state(option != null,
() -> "Unable to find '" + key + "' in " + ClassUtils.getShortName(this.source));
T parsed = option.parseValue(value, getClass().getClassLoader());
function.apply(option).accept(parsed);
});
}
private static String getCanonicalName(String name) {
StringBuilder canonicalName = new StringBuilder(name.length());
name.chars()
.filter(Character::isLetterOrDigit)
.map(Character::toLowerCase)
.forEach((c) -> canonicalName.append((char) c));
return canonicalName.toString();
}
}
/**
* {@link ConfigurableUndertowWebServerFactory} wrapper that makes it easier to apply
* {@link UndertowOptions server options}.
*/
private static class ServerOptions extends AbstractOptions {
ServerOptions(ConfigurableUndertowWebServerFactory factory) {
super(UndertowOptions.class, factory);
}
<T> Consumer<T> option(Option<T> option) {
return (value) -> getFactory().addBuilderCustomizers((builder) -> builder.setServerOption(option, value));
}
}
/**
* {@link ConfigurableUndertowWebServerFactory} wrapper that makes it easier to apply
* {@link Options socket options}.
*/
private static class SocketOptions extends AbstractOptions {
SocketOptions(ConfigurableUndertowWebServerFactory factory) {
super(Options.class, factory);
}
<T> Consumer<T> option(Option<T> option) {
return (value) -> getFactory().addBuilderCustomizers((builder) -> builder.setSocketOption(option, value));
}
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.actuate.web.server;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.autoconfigure.web.server.AccessLogCustomizer;
import org.springframework.boot.undertow.ConfigurableUndertowWebServerFactory;
/**
* {@link AccessLogCustomizer} for Undertow.
*
* @param <T> the type of factory that can be customized
* @author Andy Wilkinson
*/
class UndertowAccessLogCustomizer<T extends ConfigurableUndertowWebServerFactory> extends AccessLogCustomizer<T> {
private final Function<T, @Nullable String> accessLogPrefixExtractor;
UndertowAccessLogCustomizer(UndertowManagementServerProperties properties,
Function<T, @Nullable String> accessLogPrefixExtractor) {
super(properties.getAccesslog().getPrefix());
this.accessLogPrefixExtractor = accessLogPrefixExtractor;
}
@Override
public void customize(T factory) {
factory.setAccessLogPrefix(customizePrefix(this.accessLogPrefixExtractor.apply(factory)));
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.actuate.web.server;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties for an Undertow-based management server.
*
* @author Moritz Halbritter
* @since 4.0.0
*/
@ConfigurationProperties("management.server.undertow")
public class UndertowManagementServerProperties {
private final Accesslog accesslog = new Accesslog();
public Accesslog getAccesslog() {
return this.accesslog;
}
public static class Accesslog {
/**
* Management log file name prefix.
*/
private String prefix = "management_";
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.actuate.web.server;
import io.undertow.Undertow;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.undertow.autoconfigure.reactive.UndertowReactiveWebServerAutoConfiguration;
import org.springframework.boot.undertow.reactive.UndertowReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
/**
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for
* Undertow-based reactive web endpoint infrastructure when a separate management context
* running on a different port is required.
*
* @author Andy Wilkinson
*/
@ConditionalOnClass(Undertow.class)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@EnableConfigurationProperties(UndertowManagementServerProperties.class)
@ManagementContextConfiguration(value = ManagementContextType.CHILD, proxyBeanMethods = false)
class UndertowReactiveManagementChildContextConfiguration {
@Bean
static ManagementContextFactory reactiveWebChildContextFactory() {
return new ManagementContextFactory(WebApplicationType.REACTIVE, ReactiveWebServerFactory.class,
UndertowReactiveWebServerAutoConfiguration.class);
}
@Bean
UndertowAccessLogCustomizer<UndertowReactiveWebServerFactory> undertowManagementAccessLogCustomizer(
UndertowManagementServerProperties properties) {
return new UndertowAccessLogCustomizer<>(properties, UndertowReactiveWebServerFactory::getAccessLogPrefix);
}
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.actuate.web.server;
import io.undertow.Undertow;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextFactory;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.undertow.autoconfigure.reactive.UndertowReactiveWebServerAutoConfiguration;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
/**
* Auto-configuration for an Undertow-based reactive management context.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ Undertow.class, ManagementContextFactory.class })
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
public final class UndertowReactiveManagementContextAutoConfiguration {
@Bean
static ManagementContextFactory reactiveWebChildContextFactory() {
return new ManagementContextFactory(WebApplicationType.REACTIVE, ReactiveWebServerFactory.class,
UndertowReactiveWebServerAutoConfiguration.class);
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.actuate.web.server;
import io.undertow.Undertow;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.context.annotation.Bean;
/**
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for
* Undertow-based servlet web endpoint infrastructure when a separate management context
* running on a different port is required.
*
* @author Andy Wilkinson
*/
@ConditionalOnClass(Undertow.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(UndertowManagementServerProperties.class)
@ManagementContextConfiguration(value = ManagementContextType.CHILD, proxyBeanMethods = false)
class UndertowServletManagementChildContextConfiguration {
@Bean
UndertowAccessLogCustomizer<UndertowServletWebServerFactory> undertowManagementAccessLogCustomizer(
UndertowManagementServerProperties properties) {
return new UndertowAccessLogCustomizer<>(properties, UndertowServletWebServerFactory::getAccessLogPrefix);
}
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.actuate.web.server;
import io.undertow.Undertow;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextFactory;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.undertow.autoconfigure.servlet.UndertowServletWebServerAutoConfiguration;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
/**
* Auto-configuration for an Undertow-based servlet management context.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ Undertow.class, ManagementContextFactory.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
public final class UndertowServletManagementContextAutoConfiguration {
@Bean
static ManagementContextFactory servletWebChildContextFactory() {
return new ManagementContextFactory(WebApplicationType.SERVLET, ServletWebServerFactory.class,
UndertowServletWebServerAutoConfiguration.class);
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
/**
* Actuator Undertow actuator web concerns.
*/
@NullMarked
package org.springframework.boot.undertow.autoconfigure.actuate.web.server;
import org.jspecify.annotations.NullMarked;

View File

@ -1,24 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
/**
* Classes related to the auto-configuration of a servlet or reactive web server using
* Undertow.
*/
@NullMarked
package org.springframework.boot.undertow.autoconfigure;
import org.jspecify.annotations.NullMarked;

View File

@ -1,62 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.reactive;
import io.undertow.Undertow;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.undertow.autoconfigure.UndertowServerProperties;
import org.springframework.boot.undertow.autoconfigure.UndertowWebServerConfiguration;
import org.springframework.boot.undertow.reactive.UndertowReactiveWebServerFactory;
import org.springframework.boot.web.server.autoconfigure.reactive.ReactiveWebServerConfiguration;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.ReactiveHttpInputMessage;
/**
* {@link EnableAutoConfiguration Auto-configuration} for an Undertow-based reactive web
* server.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ ReactiveHttpInputMessage.class, Undertow.class })
@ConditionalOnWebApplication(type = Type.REACTIVE)
@EnableConfigurationProperties(UndertowServerProperties.class)
@Import({ UndertowWebServerConfiguration.class, ReactiveWebServerConfiguration.class })
public final class UndertowReactiveWebServerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ReactiveWebServerFactory.class)
UndertowReactiveWebServerFactory undertowReactiveWebServerFactory(
ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
UndertowReactiveWebServerFactory factory = new UndertowReactiveWebServerFactory();
factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().toList());
return factory;
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
/**
* Classes related to the auto-configuration of a reactive web server using Undertow.
*/
@NullMarked
package org.springframework.boot.undertow.autoconfigure.reactive;
import org.jspecify.annotations.NullMarked;

View File

@ -1,97 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.servlet;
import io.undertow.Undertow;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.websockets.jsr.Bootstrap;
import jakarta.servlet.ServletRequest;
import org.xnio.SslClientAuthMode;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.thread.Threading;
import org.springframework.boot.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.undertow.autoconfigure.UndertowServerProperties;
import org.springframework.boot.undertow.autoconfigure.UndertowWebServerConfiguration;
import org.springframework.boot.undertow.servlet.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.task.VirtualThreadTaskExecutor;
/**
* {@link EnableAutoConfiguration Auto-configuration} for an Undertow-based servlet web
* server.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ ServletRequest.class, Undertow.class, SslClientAuthMode.class, DeploymentInfo.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(UndertowServerProperties.class)
@Import({ UndertowWebServerConfiguration.class, ServletWebServerConfiguration.class })
public final class UndertowServletWebServerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
UndertowServletWebServerFactory undertowServletWebServerFactory(
ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.getDeploymentInfoCustomizers().addAll(deploymentInfoCustomizers.orderedStream().toList());
factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().toList());
return factory;
}
@Bean
UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(
UndertowServerProperties undertowProperties) {
return new UndertowServletWebServerFactoryCustomizer(undertowProperties);
}
@Bean
@ConditionalOnThreading(Threading.VIRTUAL)
UndertowDeploymentInfoCustomizer virtualThreadsUndertowDeploymentInfoCustomizer() {
return (deploymentInfo) -> deploymentInfo.setExecutor(new VirtualThreadTaskExecutor("undertow-"));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Bootstrap.class)
static class UndertowWebSocketConfiguration {
@Bean
@ConditionalOnMissingBean(name = "websocketServletWebServerCustomizer")
WebSocketUndertowServletWebServerFactoryCustomizer websocketServletWebServerCustomizer() {
return new WebSocketUndertowServletWebServerFactoryCustomizer();
}
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.servlet;
import org.springframework.boot.undertow.autoconfigure.UndertowServerProperties;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
/**
* {@link WebServerFactoryCustomizer} to apply {@link ServerProperties} to Undertow
* Servlet web servers.
*
* @author Andy Wilkinson
*/
class UndertowServletWebServerFactoryCustomizer implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
private final UndertowServerProperties undertowProperties;
UndertowServletWebServerFactoryCustomizer(UndertowServerProperties undertowProperties) {
this.undertowProperties = undertowProperties;
}
@Override
public void customize(UndertowServletWebServerFactory factory) {
factory.setEagerFilterInit(this.undertowProperties.isEagerFilterInit());
factory.setPreservePathOnForward(this.undertowProperties.isPreservePathOnForward());
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.servlet;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.springframework.boot.undertow.servlet.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
/**
* WebSocket customizer for {@link UndertowServletWebServerFactory}.
*
* @author Phillip Webb
* @since 4.0.0
*/
public class WebSocketUndertowServletWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<UndertowServletWebServerFactory>, Ordered {
@Override
public void customize(UndertowServletWebServerFactory factory) {
WebsocketDeploymentInfoCustomizer customizer = new WebsocketDeploymentInfoCustomizer();
factory.addDeploymentInfoCustomizers(customizer);
}
@Override
public int getOrder() {
return 0;
}
private static final class WebsocketDeploymentInfoCustomizer implements UndertowDeploymentInfoCustomizer {
@Override
public void customize(DeploymentInfo deploymentInfo) {
WebSocketDeploymentInfo info = new WebSocketDeploymentInfo();
deploymentInfo.addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, info);
}
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
/**
* Classes related to the auto-configuration of a servlet web server using Undertow.
*/
@NullMarked
package org.springframework.boot.undertow.autoconfigure.servlet;
import org.jspecify.annotations.NullMarked;

View File

@ -1,26 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
/**
* Reactive and servlet web server implementations backed by Undertow.
*
* @see org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory
* @see org.springframework.boot.undertow.reactive.UndertowReactiveWebServerFactory
*/
@NullMarked
package org.springframework.boot.undertow;
import org.jspecify.annotations.NullMarked;

View File

@ -1,65 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.reactive;
import java.util.List;
import io.undertow.Undertow;
import org.springframework.boot.undertow.ConfigurableUndertowWebServerFactory;
import org.springframework.boot.undertow.HttpHandlerFactory;
import org.springframework.boot.undertow.UndertowWebServer;
import org.springframework.boot.undertow.UndertowWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.reactive.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
/**
* {@link ReactiveWebServerFactory} that can be used to create {@link UndertowWebServer}s.
*
* @author Brian Clozel
* @author Scott Frederick
* @since 4.0.0
*/
public class UndertowReactiveWebServerFactory extends UndertowWebServerFactory
implements ConfigurableUndertowWebServerFactory, ConfigurableReactiveWebServerFactory {
/**
* Create a new {@link UndertowReactiveWebServerFactory} instance.
*/
public UndertowReactiveWebServerFactory() {
}
/**
* Create a new {@link UndertowReactiveWebServerFactory} that listens for requests
* using the specified port.
* @param port the port to listen on
*/
public UndertowReactiveWebServerFactory(int port) {
super(port);
}
@Override
public WebServer getWebServer(org.springframework.http.server.reactive.HttpHandler httpHandler) {
Undertow.Builder builder = createBuilder(this, this::getSslBundle, this::getServerNameSslBundles);
List<HttpHandlerFactory> httpHandlerFactories = createHttpHandlerFactories(this,
(next) -> new UndertowHttpHandlerAdapter(httpHandler));
return new UndertowWebServer(builder, httpHandlerFactories, getPort() >= 0);
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
/**
* Reactive web server implementation backed by Undertow.
*/
@NullMarked
package org.springframework.boot.undertow.reactive;
import org.jspecify.annotations.NullMarked;

View File

@ -1,75 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import io.undertow.UndertowMessages;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceChangeListener;
import io.undertow.server.handlers.resource.ResourceManager;
import org.jspecify.annotations.Nullable;
/**
* A {@link ResourceManager} that delegates to multiple {@code ResourceManager} instances.
*
* @author Andy Wilkinson
*/
class CompositeResourceManager implements ResourceManager {
private final List<ResourceManager> resourceManagers;
CompositeResourceManager(ResourceManager... resourceManagers) {
this.resourceManagers = Arrays.asList(resourceManagers);
}
@Override
public void close() throws IOException {
for (ResourceManager resourceManager : this.resourceManagers) {
resourceManager.close();
}
}
@Override
public @Nullable Resource getResource(String path) throws IOException {
for (ResourceManager resourceManager : this.resourceManagers) {
Resource resource = resourceManager.getResource(path);
if (resource != null) {
return resource;
}
}
return null;
}
@Override
public boolean isResourceChangeListenerSupported() {
return false;
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
}
}

View File

@ -1,96 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.Closeable;
import java.io.IOException;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.api.DeploymentManager;
import jakarta.servlet.ServletException;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.undertow.HttpHandlerFactory;
import org.springframework.util.Assert;
/**
* {@link HttpHandlerFactory} that for a {@link DeploymentManager}.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
class DeploymentManagerHttpHandlerFactory implements HttpHandlerFactory {
private final DeploymentManager deploymentManager;
DeploymentManagerHttpHandlerFactory(DeploymentManager deploymentManager) {
this.deploymentManager = deploymentManager;
}
@Override
public @Nullable HttpHandler getHandler(@Nullable HttpHandler next) {
Assert.state(next == null, "DeploymentManagerHttpHandlerFactory must be first");
return new DeploymentManagerHandler(this.deploymentManager);
}
DeploymentManager getDeploymentManager() {
return this.deploymentManager;
}
/**
* {@link HttpHandler} that delegates to a {@link DeploymentManager}.
*/
static class DeploymentManagerHandler implements HttpHandler, Closeable {
private final DeploymentManager deploymentManager;
private final HttpHandler handler;
DeploymentManagerHandler(DeploymentManager deploymentManager) {
this.deploymentManager = deploymentManager;
try {
this.handler = deploymentManager.start();
}
catch (ServletException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
this.handler.handleRequest(exchange);
}
@Override
public void close() throws IOException {
try {
this.deploymentManager.stop();
this.deploymentManager.undeploy();
}
catch (ServletException ex) {
throw new RuntimeException(ex);
}
}
DeploymentManager getDeploymentManager() {
return this.deploymentManager;
}
}
}

View File

@ -1,148 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import io.undertow.servlet.UndertowServletLogger;
import io.undertow.servlet.api.SessionPersistenceManager;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ConfigurableObjectInputStream;
/**
* {@link SessionPersistenceManager} that stores session information in a file.
*
* @author Phillip Webb
* @author Peter Leibiger
* @author Raja Kolli
*/
class FileSessionPersistence implements SessionPersistenceManager {
private final File dir;
FileSessionPersistence(File dir) {
this.dir = dir;
}
@Override
public void persistSessions(String deploymentName, Map<String, PersistentSession> sessionData) {
try {
save(sessionData, getSessionFile(deploymentName));
}
catch (Exception ex) {
UndertowServletLogger.ROOT_LOGGER.failedToPersistSessions(ex);
}
}
private void save(Map<String, PersistentSession> sessionData, File file) throws IOException {
try (ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(file))) {
save(sessionData, stream);
}
}
private void save(Map<String, PersistentSession> sessionData, ObjectOutputStream stream) throws IOException {
Map<String, Serializable> session = new LinkedHashMap<>();
sessionData.forEach((key, value) -> session.put(key, new SerializablePersistentSession(value)));
stream.writeObject(session);
}
@Override
public @Nullable Map<String, PersistentSession> loadSessionAttributes(String deploymentName,
final ClassLoader classLoader) {
try {
File file = getSessionFile(deploymentName);
if (file.exists()) {
return load(file, classLoader);
}
}
catch (Exception ex) {
UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(ex);
}
return null;
}
private Map<String, PersistentSession> load(File file, ClassLoader classLoader)
throws IOException, ClassNotFoundException {
try (ObjectInputStream stream = new ConfigurableObjectInputStream(new FileInputStream(file), classLoader)) {
return load(stream);
}
}
private Map<String, PersistentSession> load(ObjectInputStream stream) throws ClassNotFoundException, IOException {
Map<String, SerializablePersistentSession> session = readSession(stream);
long time = System.currentTimeMillis();
Map<String, PersistentSession> result = new LinkedHashMap<>();
session.forEach((key, value) -> {
PersistentSession entrySession = value.getPersistentSession();
if (entrySession.getExpiration().getTime() > time) {
result.put(key, entrySession);
}
});
return result;
}
@SuppressWarnings("unchecked")
private Map<String, SerializablePersistentSession> readSession(ObjectInputStream stream)
throws ClassNotFoundException, IOException {
return ((Map<String, SerializablePersistentSession>) stream.readObject());
}
private File getSessionFile(String deploymentName) {
if (!this.dir.exists()) {
this.dir.mkdirs();
}
return new File(this.dir, deploymentName + ".session");
}
@Override
public void clear(String deploymentName) {
getSessionFile(deploymentName).delete();
}
/**
* Session data in a serializable form.
*/
static class SerializablePersistentSession implements Serializable {
private static final long serialVersionUID = 0L;
private final Date expiration;
private final Map<String, Object> sessionData;
SerializablePersistentSession(PersistentSession session) {
this.expiration = session.getExpiration();
this.sessionData = new LinkedHashMap<>(session.getSessionData());
}
PersistentSession getPersistentSession() {
return new PersistentSession(this.expiration, this.sessionData);
}
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import io.undertow.UndertowMessages;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceChangeListener;
import io.undertow.server.handlers.resource.ResourceManager;
import io.undertow.server.handlers.resource.URLResource;
import org.jspecify.annotations.Nullable;
import org.springframework.util.StringUtils;
/**
* {@link ResourceManager} for JAR resources.
*
* @author Ivan Sopov
* @author Andy Wilkinson
*/
class JarResourceManager implements ResourceManager {
private final String jarPath;
JarResourceManager(File jarFile) {
try {
this.jarPath = jarFile.getAbsoluteFile().toURI().toURL().toString();
}
catch (MalformedURLException ex) {
throw new IllegalArgumentException(ex);
}
}
@Override
public @Nullable Resource getResource(String path) throws IOException {
URL url = new URL("jar:" + this.jarPath + "!" + (path.startsWith("/") ? path : "/" + path));
URLResource resource = new URLResource(url, path);
if (StringUtils.hasText(path) && !"/".equals(path) && resource.getContentLength() < 0) {
return null;
}
return resource;
}
@Override
public boolean isResourceChangeListenerSupported() {
return false;
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
}
@Override
public void close() throws IOException {
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import io.undertow.servlet.api.DeploymentInfo;
/**
* Callback interface that can be used to customize an Undertow {@link DeploymentInfo}.
*
* @author Phillip Webb
* @since 4.0.0
* @see UndertowServletWebServerFactory
*/
@FunctionalInterface
public interface UndertowDeploymentInfoCustomizer {
/**
* Customize the deployment info.
* @param deploymentInfo the {@code DeploymentInfo} to customize
*/
void customize(DeploymentInfo deploymentInfo);
}

View File

@ -1,96 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import io.undertow.Handlers;
import io.undertow.Undertow.Builder;
import io.undertow.server.HttpHandler;
import io.undertow.servlet.api.DeploymentManager;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.undertow.HttpHandlerFactory;
import org.springframework.boot.undertow.UndertowWebServer;
import org.springframework.boot.web.server.WebServer;
import org.springframework.util.StringUtils;
/**
* {@link WebServer} that can be used to control an embedded Undertow server. Typically
* this class should be created using {@link UndertowServletWebServerFactory} and not
* directly.
*
* @author Ivan Sopov
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Christoph Dreis
* @author Kristine Jetzke
* @since 4.0.0
* @see UndertowServletWebServerFactory
*/
public class UndertowServletWebServer extends UndertowWebServer {
private final String contextPath;
private final @Nullable DeploymentManager manager;
/**
* Create a new {@link UndertowServletWebServer} instance.
* @param builder the builder
* @param httpHandlerFactories the handler factories
* @param contextPath the root context path
* @param autoStart if the server should be started
* @since 4.0.0
*/
public UndertowServletWebServer(Builder builder, Iterable<HttpHandlerFactory> httpHandlerFactories,
String contextPath, boolean autoStart) {
super(builder, httpHandlerFactories, autoStart);
this.contextPath = contextPath;
this.manager = findManager(httpHandlerFactories);
}
private @Nullable DeploymentManager findManager(Iterable<HttpHandlerFactory> httpHandlerFactories) {
for (HttpHandlerFactory httpHandlerFactory : httpHandlerFactories) {
if (httpHandlerFactory instanceof DeploymentManagerHttpHandlerFactory deploymentManagerFactory) {
return deploymentManagerFactory.getDeploymentManager();
}
}
return null;
}
@Override
protected @Nullable HttpHandler createHttpHandler() {
HttpHandler handler = super.createHttpHandler();
if (StringUtils.hasLength(this.contextPath)) {
handler = Handlers.path().addPrefixPath(this.contextPath, handler);
}
return handler;
}
@Override
protected String getStartedLogMessage() {
String contextPath = StringUtils.hasText(this.contextPath) ? this.contextPath : "/";
StringBuilder message = new StringBuilder(super.getStartedLogMessage());
message.append(" with context path '");
message.append(contextPath);
message.append("'");
return message.toString();
}
public @Nullable DeploymentManager getDeploymentManager() {
return this.manager;
}
}

View File

@ -1,608 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import io.undertow.Undertow.Builder;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.Cookie;
import io.undertow.server.handlers.resource.FileResourceManager;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceChangeListener;
import io.undertow.server.handlers.resource.ResourceManager;
import io.undertow.server.handlers.resource.URLResource;
import io.undertow.server.session.SessionManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.Deployment;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.ListenerInfo;
import io.undertow.servlet.api.MimeMapping;
import io.undertow.servlet.api.ServletContainerInitializerInfo;
import io.undertow.servlet.api.ServletStackTraces;
import io.undertow.servlet.core.DeploymentImpl;
import io.undertow.servlet.handlers.DefaultServlet;
import io.undertow.servlet.util.ImmediateInstanceFactory;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.undertow.HttpHandlerFactory;
import org.springframework.boot.undertow.UndertowWebServerFactory;
import org.springframework.boot.web.error.ErrorPage;
import org.springframework.boot.web.server.Cookie.SameSite;
import org.springframework.boot.web.server.MimeMappings.Mapping;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.server.servlet.ContextPath;
import org.springframework.boot.web.server.servlet.CookieSameSiteSupplier;
import org.springframework.boot.web.server.servlet.DocumentRoot;
import org.springframework.boot.web.server.servlet.ServletContextInitializers;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.boot.web.server.servlet.ServletWebServerSettings;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* {@link ServletWebServerFactory} that can be used to create
* {@link UndertowServletWebServer}s.
* <p>
* Unless explicitly configured otherwise, the factory will create servers that listen for
* HTTP requests on port 8080.
*
* @author Ivan Sopov
* @author Andy Wilkinson
* @author Marcos Barbero
* @author Eddú Meléndez
* @author Scott Frederick
* @since 4.0.0
* @see UndertowServletWebServer
*/
public class UndertowServletWebServerFactory extends UndertowWebServerFactory
implements ConfigurableServletWebServerFactory, ResourceLoaderAware {
private static final Log logger = LogFactory.getLog(UndertowServletWebServerFactory.class);
private static final Pattern ENCODED_SLASH = Pattern.compile("%2F", Pattern.LITERAL);
private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
private final ServletWebServerSettings settings = new ServletWebServerSettings();
private Set<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers = new LinkedHashSet<>();
@SuppressWarnings("NullAway.Init")
private ResourceLoader resourceLoader;
private boolean eagerFilterInit = true;
private boolean preservePathOnForward;
/**
* Create a new {@link UndertowServletWebServerFactory} instance.
*/
public UndertowServletWebServerFactory() {
getSettings().getJsp().setRegistered(false);
}
/**
* Create a new {@link UndertowServletWebServerFactory} that listens for requests
* using the specified port.
* @param port the port to listen on
*/
public UndertowServletWebServerFactory(int port) {
super(port);
getSettings().getJsp().setRegistered(false);
}
/**
* Create a new {@link UndertowServletWebServerFactory} with the specified context
* path and port.
* @param contextPath the root context path
* @param port the port to listen on
*/
public UndertowServletWebServerFactory(String contextPath, int port) {
super(port);
getSettings().setContextPath(ContextPath.of(contextPath));
getSettings().getJsp().setRegistered(false);
}
/**
* Returns a mutable collection of the {@link UndertowDeploymentInfoCustomizer}s that
* will be applied to the Undertow {@link DeploymentInfo}.
* @return the customizers that will be applied
*/
public Collection<UndertowDeploymentInfoCustomizer> getDeploymentInfoCustomizers() {
return this.deploymentInfoCustomizers;
}
/**
* Set {@link UndertowDeploymentInfoCustomizer}s that should be applied to the
* Undertow {@link DeploymentInfo}. Calling this method will replace any existing
* customizers.
* @param customizers the customizers to set
*/
public void setDeploymentInfoCustomizers(Collection<? extends UndertowDeploymentInfoCustomizer> customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
this.deploymentInfoCustomizers = new LinkedHashSet<>(customizers);
}
/**
* Add {@link UndertowDeploymentInfoCustomizer}s that should be used to customize the
* Undertow {@link DeploymentInfo}.
* @param customizers the customizers to add
*/
public void addDeploymentInfoCustomizers(UndertowDeploymentInfoCustomizer... customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
this.deploymentInfoCustomizers.addAll(Arrays.asList(customizers));
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* Return if filters should be eagerly initialized.
* @return {@code true} if filters are eagerly initialized, otherwise {@code false}.
*/
public boolean isEagerFilterInit() {
return this.eagerFilterInit;
}
/**
* Set whether filters should be eagerly initialized.
* @param eagerFilterInit {@code true} if filters are eagerly initialized, otherwise
* {@code false}.
*/
public void setEagerFilterInit(boolean eagerFilterInit) {
this.eagerFilterInit = eagerFilterInit;
}
/**
* Return whether the request path should be preserved on forward.
* @return {@code true} if the path should be preserved when a request is forwarded,
* otherwise {@code false}.
*/
public boolean isPreservePathOnForward() {
return this.preservePathOnForward;
}
/**
* Set whether the request path should be preserved on forward.
* @param preservePathOnForward {@code true} if the path should be preserved when a
* request is forwarded, otherwise {@code false}.
*/
public void setPreservePathOnForward(boolean preservePathOnForward) {
this.preservePathOnForward = preservePathOnForward;
}
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Builder builder = createBuilder(this, this::getSslBundle, this::getServerNameSslBundles);
DeploymentManager manager = createManager(initializers);
return getUndertowWebServer(builder, manager, getPort());
}
private DeploymentManager createManager(ServletContextInitializer... initializers) {
DeploymentInfo deployment = Servlets.deployment();
registerServletContainerInitializerToDriveServletContextInitializers(deployment, initializers);
deployment.setClassLoader(getServletClassLoader());
deployment.setContextPath(getSettings().getContextPath().toString());
deployment.setDisplayName(getSettings().getDisplayName());
deployment.setDeploymentName("spring-boot");
if (getSettings().isRegisterDefaultServlet()) {
deployment.addServlet(Servlets.servlet("default", DefaultServlet.class));
}
configureErrorPages(deployment);
deployment.setServletStackTraces(ServletStackTraces.NONE);
deployment.setResourceManager(getDocumentRootResourceManager());
deployment.setTempDir(createTempDir("undertow"));
deployment.setEagerFilterInit(this.eagerFilterInit);
deployment.setPreservePathOnForward(this.preservePathOnForward);
configureMimeMappings(deployment);
configureWebListeners(deployment);
for (UndertowDeploymentInfoCustomizer customizer : this.deploymentInfoCustomizers) {
customizer.customize(deployment);
}
if (getSettings().getSession().isPersistent()) {
File dir = getSettings().getSession().getSessionStoreDirectory().getValidDirectory(true);
deployment.setSessionPersistenceManager(new FileSessionPersistence(dir));
}
addLocaleMappings(deployment);
DeploymentManager manager = Servlets.newContainer().addDeployment(deployment);
manager.deploy();
if (manager.getDeployment() instanceof DeploymentImpl managerDeployment) {
removeSuperfluousMimeMappings(managerDeployment, deployment);
}
SessionManager sessionManager = manager.getDeployment().getSessionManager();
Duration timeoutDuration = getSettings().getSession().getTimeout();
int sessionTimeout = (isZeroOrLess(timeoutDuration) ? -1 : (int) timeoutDuration.getSeconds());
sessionManager.setDefaultSessionTimeout(sessionTimeout);
return manager;
}
private void configureWebListeners(DeploymentInfo deployment) {
for (String className : getSettings().getWebListenerClassNames()) {
try {
deployment.addListener(new ListenerInfo(loadWebListenerClass(className)));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to load web listener class '" + className + "'", ex);
}
}
}
@SuppressWarnings("unchecked")
private Class<? extends EventListener> loadWebListenerClass(String className) throws ClassNotFoundException {
ClassLoader classLoader = getServletClassLoader();
Assert.state(classLoader != null, "'classLoader' must not be null");
return (Class<? extends EventListener>) classLoader.loadClass(className);
}
private boolean isZeroOrLess(@Nullable Duration timeoutDuration) {
return timeoutDuration == null || timeoutDuration.isZero() || timeoutDuration.isNegative();
}
private void addLocaleMappings(DeploymentInfo deployment) {
getSettings().getLocaleCharsetMappings()
.forEach((locale, charset) -> deployment.addLocaleCharsetMapping(locale.toString(), charset.toString()));
}
private void registerServletContainerInitializerToDriveServletContextInitializers(DeploymentInfo deployment,
ServletContextInitializer... initializers) {
ServletContextInitializers mergedInitializers = ServletContextInitializers.from(this.settings, initializers);
Initializer initializer = new Initializer(mergedInitializers);
deployment.addServletContainerInitializer(new ServletContainerInitializerInfo(Initializer.class,
new ImmediateInstanceFactory<>(initializer), NO_CLASSES));
}
private @Nullable ClassLoader getServletClassLoader() {
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return getClass().getClassLoader();
}
private ResourceManager getDocumentRootResourceManager() {
DocumentRoot documentRoot = new DocumentRoot(logger);
documentRoot.setDirectory(this.settings.getDocumentRoot());
File root = documentRoot.getValidDirectory();
File docBase = getCanonicalDocumentRoot(root);
List<URL> metaInfResourceUrls = getSettings().getStaticResourceUrls();
List<URL> resourceJarUrls = new ArrayList<>();
List<ResourceManager> managers = new ArrayList<>();
ResourceManager rootManager = (docBase.isDirectory() ? new FileResourceManager(docBase, 0)
: new JarResourceManager(docBase));
if (root != null) {
rootManager = new LoaderHidingResourceManager(rootManager);
}
managers.add(rootManager);
for (URL url : metaInfResourceUrls) {
if ("file".equals(url.getProtocol())) {
try {
File file = new File(url.toURI());
if (file.isFile()) {
resourceJarUrls.add(new URL("jar:" + url + "!/"));
}
else {
managers.add(new FileResourceManager(new File(file, "META-INF/resources"), 0));
}
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
else {
resourceJarUrls.add(url);
}
}
managers.add(new MetaInfResourcesResourceManager(resourceJarUrls));
return new CompositeResourceManager(managers.toArray(new ResourceManager[0]));
}
private File getCanonicalDocumentRoot(@Nullable File docBase) {
try {
File root = (docBase != null) ? docBase : createTempDir("undertow-docbase");
return root.getCanonicalFile();
}
catch (IOException ex) {
throw new IllegalStateException("Cannot get canonical document root", ex);
}
}
private void configureErrorPages(DeploymentInfo deployment) {
for (ErrorPage errorPage : getErrorPages()) {
deployment.addErrorPage(getUndertowErrorPage(errorPage));
}
}
private io.undertow.servlet.api.ErrorPage getUndertowErrorPage(ErrorPage errorPage) {
if (errorPage.getStatus() != null) {
return new io.undertow.servlet.api.ErrorPage(errorPage.getPath(), errorPage.getStatusCode());
}
if (errorPage.getException() != null) {
return new io.undertow.servlet.api.ErrorPage(errorPage.getPath(), errorPage.getException());
}
return new io.undertow.servlet.api.ErrorPage(errorPage.getPath());
}
private void configureMimeMappings(DeploymentInfo deployment) {
for (Mapping mimeMapping : getSettings().getMimeMappings()) {
deployment.addMimeMapping(new MimeMapping(mimeMapping.getExtension(), mimeMapping.getMimeType()));
}
}
private void removeSuperfluousMimeMappings(DeploymentImpl deployment, DeploymentInfo deploymentInfo) {
// DeploymentManagerImpl will always add MimeMappings.DEFAULT_MIME_MAPPINGS
// but we only want ours
Map<String, String> mappings = new HashMap<>();
for (MimeMapping mapping : deploymentInfo.getMimeMappings()) {
mappings.put(mapping.getExtension().toLowerCase(Locale.ENGLISH), mapping.getMimeType());
}
deployment.setMimeExtensionMappings(mappings);
}
/**
* Factory method called to create the {@link UndertowServletWebServer}. Subclasses
* can override this method to return a different {@link UndertowServletWebServer} or
* apply additional processing to the {@link Builder} and {@link DeploymentManager}
* used to bootstrap Undertow
* @param builder the builder
* @param manager the deployment manager
* @param port the port that Undertow should listen on
* @return a new {@link UndertowServletWebServer} instance
*/
protected UndertowServletWebServer getUndertowWebServer(Builder builder, DeploymentManager manager, int port) {
List<HttpHandlerFactory> initialHandlerFactories = new ArrayList<>();
initialHandlerFactories.add(new DeploymentManagerHttpHandlerFactory(manager));
HttpHandlerFactory cooHandlerFactory = getCookieHandlerFactory(manager.getDeployment());
if (cooHandlerFactory != null) {
initialHandlerFactories.add(cooHandlerFactory);
}
List<HttpHandlerFactory> httpHandlerFactories = createHttpHandlerFactories(this,
initialHandlerFactories.toArray(new HttpHandlerFactory[0]));
return new UndertowServletWebServer(builder, httpHandlerFactories, getSettings().getContextPath().toString(),
port >= 0);
}
private @Nullable HttpHandlerFactory getCookieHandlerFactory(Deployment deployment) {
SameSite sessionSameSite = getSettings().getSession().getCookie().getSameSite();
List<CookieSameSiteSupplier> suppliers = new ArrayList<>();
if (sessionSameSite != null) {
String sessionCookieName = deployment.getServletContext().getSessionCookieConfig().getName();
suppliers.add(CookieSameSiteSupplier.of(sessionSameSite).whenHasName(sessionCookieName));
}
if (!CollectionUtils.isEmpty(getSettings().getCookieSameSiteSuppliers())) {
suppliers.addAll(getSettings().getCookieSameSiteSuppliers());
}
return (!suppliers.isEmpty()) ? (next) -> new SuppliedSameSiteCookieHandler(next, suppliers) : null;
}
@Override
public ServletWebServerSettings getSettings() {
return this.settings;
}
/**
* {@link ServletContainerInitializer} to initialize {@link ServletContextInitializer
* ServletContextInitializers}.
*/
private static class Initializer implements ServletContainerInitializer {
private final ServletContextInitializers initializers;
Initializer(ServletContextInitializers initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
}
/**
* {@link ResourceManager} that exposes resource in {@code META-INF/resources}
* directory of nested (in {@code BOOT-INF/lib} or {@code WEB-INF/lib}) jars.
*/
private static final class MetaInfResourcesResourceManager implements ResourceManager {
private final List<URL> metaInfResourceJarUrls;
private MetaInfResourcesResourceManager(List<URL> metaInfResourceJarUrls) {
this.metaInfResourceJarUrls = metaInfResourceJarUrls;
}
@Override
public void close() throws IOException {
}
@Override
public @Nullable Resource getResource(String path) {
for (URL url : this.metaInfResourceJarUrls) {
URLResource resource = getMetaInfResource(url, path);
if (resource != null) {
return resource;
}
}
return null;
}
@Override
public boolean isResourceChangeListenerSupported() {
return false;
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
}
private @Nullable URLResource getMetaInfResource(URL resourceJar, String path) {
try {
String urlPath = URLEncoder.encode(ENCODED_SLASH.matcher(path).replaceAll("/"), StandardCharsets.UTF_8);
URL resourceUrl = new URL(resourceJar + "META-INF/resources" + urlPath);
URLResource resource = new URLResource(resourceUrl, path);
if (resource.getContentLength() < 0) {
return null;
}
return resource;
}
catch (Exception ex) {
return null;
}
}
}
/**
* {@link ResourceManager} to hide Spring Boot loader classes.
*/
private static final class LoaderHidingResourceManager implements ResourceManager {
private final ResourceManager delegate;
private LoaderHidingResourceManager(ResourceManager delegate) {
this.delegate = delegate;
}
@Override
public @Nullable Resource getResource(String path) throws IOException {
if (path.startsWith("/org/springframework/boot")) {
return null;
}
return this.delegate.getResource(path);
}
@Override
public boolean isResourceChangeListenerSupported() {
return this.delegate.isResourceChangeListenerSupported();
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
this.delegate.registerResourceChangeListener(listener);
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
this.delegate.removeResourceChangeListener(listener);
}
@Override
public void close() throws IOException {
this.delegate.close();
}
}
/**
* {@link HttpHandler} to apply {@link CookieSameSiteSupplier supplied}
* {@link SameSite} cookie values.
*/
private static class SuppliedSameSiteCookieHandler implements HttpHandler {
private final @Nullable HttpHandler next;
private final List<CookieSameSiteSupplier> suppliers;
SuppliedSameSiteCookieHandler(@Nullable HttpHandler next, List<CookieSameSiteSupplier> suppliers) {
this.next = next;
this.suppliers = suppliers;
}
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.addResponseCommitListener(this::beforeCommit);
if (this.next != null) {
this.next.handleRequest(exchange);
}
}
private void beforeCommit(HttpServerExchange exchange) {
for (Cookie cookie : exchange.responseCookies()) {
SameSite sameSite = getSameSite(asServletCookie(cookie));
if (sameSite == SameSite.OMITTED) {
cookie.setSameSite(false);
}
else if (sameSite != null) {
cookie.setSameSiteMode(sameSite.attributeValue());
}
}
}
@SuppressWarnings("removal")
private jakarta.servlet.http.Cookie asServletCookie(Cookie cookie) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
jakarta.servlet.http.Cookie result = new jakarta.servlet.http.Cookie(cookie.getName(), cookie.getValue());
map.from(cookie::getComment).to(result::setComment);
map.from(cookie::getDomain).to(result::setDomain);
map.from(cookie::getMaxAge).to(result::setMaxAge);
map.from(cookie::getPath).to(result::setPath);
result.setSecure(cookie.isSecure());
result.setVersion(cookie.getVersion());
result.setHttpOnly(cookie.isHttpOnly());
return result;
}
private @Nullable SameSite getSameSite(jakarta.servlet.http.Cookie cookie) {
for (CookieSameSiteSupplier supplier : this.suppliers) {
SameSite sameSite = supplier.getSameSite(cookie);
if (sameSite != null) {
return sameSite;
}
}
return null;
}
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
/**
* Servlet web server implementation backed by Undertow.
*/
@NullMarked
package org.springframework.boot.undertow.servlet;
import org.jspecify.annotations.NullMarked;

View File

@ -1,13 +0,0 @@
{
"groups": [],
"properties": [
{
"name": "server.undertow.buffers-per-region",
"type": "java.lang.Integer",
"description": "Number of buffer per region.",
"deprecation": {
"level": "error"
}
}
]
}

View File

@ -1,2 +0,0 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.undertow.UndertowWebServer$UndertowWebServerRuntimeHints

View File

@ -1,2 +0,0 @@
org.springframework.boot.undertow.autoconfigure.actuate.web.server.UndertowReactiveManagementChildContextConfiguration
org.springframework.boot.undertow.autoconfigure.actuate.web.server.UndertowServletManagementChildContextConfiguration

View File

@ -1,4 +0,0 @@
org.springframework.boot.undertow.autoconfigure.actuate.web.server.UndertowReactiveManagementContextAutoConfiguration
org.springframework.boot.undertow.autoconfigure.actuate.web.server.UndertowServletManagementContextAutoConfiguration
org.springframework.boot.undertow.autoconfigure.reactive.UndertowReactiveWebServerAutoConfiguration
org.springframework.boot.undertow.autoconfigure.servlet.UndertowServletWebServerAutoConfiguration

View File

@ -1,34 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
/**
* Helper class to provide public access to package-private methods for testing purposes.
*
* @author Andy Wilkinson
*/
public final class UndertowAccess {
private UndertowAccess() {
}
public static String getStartedLogMessage(UndertowWebServer undertowWebServer) {
return undertowWebServer.getStartedLogMessage();
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow;
import java.util.function.Predicate;
import io.undertow.Undertow;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.undertow.UndertowWebServer.UndertowWebServerRuntimeHints;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link UndertowWebServerRuntimeHints}.
*
* @author Andy Wilkinson
*/
class UndertowWebServerRuntimeHintsTests {
@Test
void registersHints() throws ClassNotFoundException {
RuntimeHints runtimeHints = new RuntimeHints();
new UndertowWebServerRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.reflection().onFieldAccess(Undertow.class, "listeners"))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onFieldAccess(Undertow.class, "channels")).accepts(runtimeHints);
assertThat(reflectionOnField("io.undertow.Undertow$ListenerConfig", "type")).accepts(runtimeHints);
assertThat(reflectionOnField("io.undertow.Undertow$ListenerConfig", "port")).accepts(runtimeHints);
assertThat(reflectionOnField("io.undertow.protocols.ssl.UndertowAcceptingSslChannel", "ssl"))
.accepts(runtimeHints);
}
private Predicate<RuntimeHints> reflectionOnField(String className, String fieldName)
throws ClassNotFoundException {
return RuntimeHintsPredicates.reflection()
.onFieldAccess(ReflectionUtils.findField(Class.forName(className), fieldName));
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure;
import java.util.Collections;
import java.util.Map;
import io.undertow.UndertowOptions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link UndertowServerProperties}.
*
* @author Andy Wilkinson
*/
class UndertowServerPropertiesTests {
private final UndertowServerProperties properties = new UndertowServerProperties();
@Test
void testCustomizeUndertowServerOption() {
bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE", "true");
assertThat(this.properties.getOptions().getServer()).containsEntry("ALWAYS_SET_KEEP_ALIVE", "true");
}
@Test
void testCustomizeUndertowSocketOption() {
bind("server.undertow.options.socket.ALWAYS_SET_KEEP_ALIVE", "true");
assertThat(this.properties.getOptions().getSocket()).containsEntry("ALWAYS_SET_KEEP_ALIVE", "true");
}
@Test
void testCustomizeUndertowIoThreads() {
bind("server.undertow.threads.io", "4");
assertThat(this.properties.getThreads().getIo()).isEqualTo(4);
}
@Test
void testCustomizeUndertowWorkerThreads() {
bind("server.undertow.threads.worker", "10");
assertThat(this.properties.getThreads().getWorker()).isEqualTo(10);
}
@Test
void undertowMaxHttpPostSizeMatchesDefault() {
assertThat(this.properties.getMaxHttpPostSize().toBytes()).isEqualTo(UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}
private void bind(Map<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("server.undertow", Bindable.ofInstance(this.properties));
}
}

View File

@ -1,268 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.UndertowOptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xnio.Option;
import org.xnio.OptionMap;
import org.xnio.Options;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.undertow.ConfigurableUndertowWebServerFactory;
import org.springframework.boot.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link UndertowWebServerFactoryCustomizer}.
*
* @author Brian Clozel
* @author Phillip Webb
* @author Artsiom Yudovin
* @author Rafiullah Hamedy
* @author HaiTao Zhang
*/
class UndertowWebServerFactoryCustomizerTests {
private final MockEnvironment environment = new MockEnvironment();
private final ServerProperties serverProperties = new ServerProperties();
private final UndertowServerProperties undertowProperties = new UndertowServerProperties();
private UndertowWebServerFactoryCustomizer customizer;
@BeforeEach
void setup() {
ConfigurationPropertySources.attach(this.environment);
this.customizer = new UndertowWebServerFactoryCustomizer(this.environment, this.serverProperties,
this.undertowProperties);
}
@Test
void customizeUndertowAccessLog() {
bind("server.undertow.accesslog.enabled=true", "server.undertow.accesslog.pattern=foo",
"server.undertow.accesslog.prefix=test_log", "server.undertow.accesslog.suffix=txt",
"server.undertow.accesslog.dir=test-logs", "server.undertow.accesslog.rotate=false");
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
this.customizer.customize(factory);
then(factory).should().setAccessLogEnabled(true);
then(factory).should().setAccessLogPattern("foo");
then(factory).should().setAccessLogPrefix("test_log");
then(factory).should().setAccessLogSuffix("txt");
then(factory).should().setAccessLogDirectory(new File("test-logs"));
then(factory).should().setAccessLogRotate(false);
}
@Test
void customMaxHttpRequestHeaderSize() {
bind("server.max-http-request-header-size=2048");
assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isEqualTo(2048);
}
@Test
void customMaxHttpRequestHeaderSizeIgnoredIfNegative() {
bind("server.max-http-request-header-size=-1");
assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull();
}
@Test
void customMaxHttpRequestHeaderSizeIgnoredIfZero() {
bind("server.max-http-request-header-size=0");
assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull();
}
@Test
void customMaxHttpPostSize() {
bind("server.undertow.max-http-post-size=256");
assertThat(boundServerOption(UndertowOptions.MAX_ENTITY_SIZE)).isEqualTo(256);
}
@Test
void customConnectionTimeout() {
bind("server.undertow.no-request-timeout=1m");
assertThat(boundServerOption(UndertowOptions.NO_REQUEST_TIMEOUT)).isEqualTo(60000);
}
@Test
void customMaxParameters() {
bind("server.undertow.max-parameters=4");
assertThat(boundServerOption(UndertowOptions.MAX_PARAMETERS)).isEqualTo(4);
}
@Test
void customMaxHeaders() {
bind("server.undertow.max-headers=4");
assertThat(boundServerOption(UndertowOptions.MAX_HEADERS)).isEqualTo(4);
}
@Test
void customMaxCookies() {
bind("server.undertow.max-cookies=4");
assertThat(boundServerOption(UndertowOptions.MAX_COOKIES)).isEqualTo(4);
}
@Test
void customizeIoThreads() {
bind("server.undertow.threads.io=4");
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
this.customizer.customize(factory);
then(factory).should().setIoThreads(4);
}
@Test
void customizeWorkerThreads() {
bind("server.undertow.threads.worker=10");
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
this.customizer.customize(factory);
then(factory).should().setWorkerThreads(10);
}
@Test
void enableSlashDecoding() {
bind("server.undertow.decode-slash=true");
assertThat(boundServerOption(UndertowOptions.DECODE_SLASH)).isTrue();
}
@Test
void disableUrlDecoding() {
bind("server.undertow.decode-url=false");
assertThat(boundServerOption(UndertowOptions.DECODE_URL)).isFalse();
}
@Test
void customUrlCharset() {
bind("server.undertow.url-charset=UTF-16");
assertThat(boundServerOption(UndertowOptions.URL_CHARSET)).isEqualTo(StandardCharsets.UTF_16.name());
}
@Test
void disableAlwaysSetKeepAlive() {
bind("server.undertow.always-set-keep-alive=false");
assertThat(boundServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse();
}
@Test
void customServerOption() {
bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE=false");
assertThat(boundServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse();
}
@Test
void customServerOptionShouldBeRelaxed() {
bind("server.undertow.options.server.always-set-keep-alive=false");
assertThat(boundServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse();
}
@Test
void customSocketOption() {
bind("server.undertow.options.socket.CONNECTION_LOW_WATER=8");
assertThat(boundSocketOption(Options.CONNECTION_LOW_WATER)).isEqualTo(8);
}
@Test
void customSocketOptionShouldBeRelaxed() {
bind("server.undertow.options.socket.connection-low-water=8");
assertThat(boundSocketOption(Options.CONNECTION_LOW_WATER)).isEqualTo(8);
}
@Test
void deduceUseForwardHeaders() {
this.environment.setProperty("DYNO", "-");
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
this.customizer.customize(factory);
then(factory).should().setUseForwardHeaders(true);
}
@Test
void defaultUseForwardHeaders() {
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
this.customizer.customize(factory);
then(factory).should().setUseForwardHeaders(false);
}
@Test
void forwardHeadersWhenStrategyIsNativeShouldConfigureValve() {
this.serverProperties.setForwardHeadersStrategy(ServerProperties.ForwardHeadersStrategy.NATIVE);
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
this.customizer.customize(factory);
then(factory).should().setUseForwardHeaders(true);
}
@Test
void forwardHeadersWhenStrategyIsNoneShouldNotConfigureValve() {
this.environment.setProperty("DYNO", "-");
this.serverProperties.setForwardHeadersStrategy(ServerProperties.ForwardHeadersStrategy.NONE);
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
this.customizer.customize(factory);
then(factory).should().setUseForwardHeaders(false);
}
private <T> T boundServerOption(Option<T> option) {
Builder builder = Undertow.builder();
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
this.customizer.customize(factory);
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder, "serverOptions")).getMap();
return map.get(option);
}
private <T> T boundSocketOption(Option<T> option) {
Builder builder = Undertow.builder();
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
this.customizer.customize(factory);
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder, "socketOptions")).getMap();
return map.get(option);
}
private ConfigurableUndertowWebServerFactory mockFactory(Builder builder) {
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
willAnswer((invocation) -> {
Object argument = invocation.getArgument(0);
Arrays.stream((argument instanceof UndertowBuilderCustomizer undertowCustomizer)
? new UndertowBuilderCustomizer[] { undertowCustomizer } : (UndertowBuilderCustomizer[]) argument)
.forEach((customizer) -> customizer.customize(builder));
return null;
}).given(factory).addBuilderCustomizers(any());
return factory;
}
private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
Binder binder = new Binder(ConfigurationPropertySources.get(this.environment));
binder.bind("server", Bindable.ofInstance(this.serverProperties));
binder.bind("server.undertow", Bindable.ofInstance(this.undertowProperties));
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.actuate.web;
import org.junit.jupiter.api.Test;
import org.springframework.boot.undertow.autoconfigure.actuate.web.server.UndertowManagementServerProperties;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link UndertowManagementServerProperties}.
*
* @author Andy Wilkinson
*/
class UndertowManagementServerPropertiesTests {
@Test
void accessLogsArePrefixedByDefault() {
UndertowManagementServerProperties properties = new UndertowManagementServerProperties();
assertThat(properties.getAccesslog().getPrefix()).isEqualTo("management_");
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.reactive;
import io.undertow.Undertow.Builder;
import org.junit.jupiter.api.Test;
import org.springframework.boot.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.undertow.reactive.UndertowReactiveWebServerFactory;
import org.springframework.boot.undertow.servlet.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.autoconfigure.reactive.AbstractReactiveWebServerAutoConfigurationTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link UndertowReactiveWebServerAutoConfiguration}.
*
* @author Brian Clozel
* @author Raheela Aslam
* @author Madhura Bhave
* @author Scott Frederick
*/
class UndertowReactiveWebServerAutoConfigurationTests extends AbstractReactiveWebServerAutoConfigurationTests {
UndertowReactiveWebServerAutoConfigurationTests() {
super(UndertowReactiveWebServerAutoConfiguration.class);
}
@Test
void undertowBuilderCustomizerBeanIsAddedToFactory() {
this.serverRunner.withUserConfiguration(UndertowBuilderCustomizerConfiguration.class).run((context) -> {
UndertowReactiveWebServerFactory factory = context.getBean(UndertowReactiveWebServerFactory.class);
assertThat(factory.getBuilderCustomizers())
.contains(context.getBean("builderCustomizer", UndertowBuilderCustomizer.class));
});
}
@Test
void undertowBuilderCustomizerRegisteredAsBeanAndViaFactoryIsOnlyCalledOnce() {
this.serverRunner.withUserConfiguration(DoubleRegistrationUndertowBuilderCustomizerConfiguration.class)
.run((context) -> {
UndertowReactiveWebServerFactory factory = context.getBean(UndertowReactiveWebServerFactory.class);
UndertowBuilderCustomizer customizer = context.getBean("builderCustomizer",
UndertowBuilderCustomizer.class);
assertThat(factory.getBuilderCustomizers()).contains(customizer);
then(customizer).should().customize(any(Builder.class));
});
}
@Configuration(proxyBeanMethods = false)
static class UndertowBuilderCustomizerConfiguration {
@Bean
UndertowBuilderCustomizer builderCustomizer() {
return (builder) -> {
};
}
}
@Configuration(proxyBeanMethods = false)
static class DoubleRegistrationUndertowBuilderCustomizerConfiguration {
private final UndertowBuilderCustomizer customizer = mock(UndertowBuilderCustomizer.class);
@Bean
UndertowBuilderCustomizer builderCustomizer() {
return this.customizer;
}
@Bean
WebServerFactoryCustomizer<UndertowReactiveWebServerFactory> undertowCustomizer() {
return (undertow) -> undertow.addBuilderCustomizers(this.customizer);
}
}
@Configuration(proxyBeanMethods = false)
static class UndertowDeploymentInfoCustomizerConfiguration {
@Bean
UndertowDeploymentInfoCustomizer deploymentInfoCustomizer() {
return (deploymentInfo) -> {
};
}
}
}

View File

@ -1,158 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.servlet;
import io.undertow.Undertow.Builder;
import io.undertow.servlet.api.DeploymentInfo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.undertow.servlet.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.autoconfigure.servlet.AbstractServletWebServerAutoConfigurationTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link UndertowServletWebServerAutoConfiguration}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Raheela Aslam
* @author Madhura Bhave
*/
class UndertowServletWebServerAutoConfigurationTests extends AbstractServletWebServerAutoConfigurationTests {
UndertowServletWebServerAutoConfigurationTests() {
super(UndertowServletWebServerAutoConfiguration.class);
}
@Test
void undertowDeploymentInfoCustomizerBeanIsAddedToFactory() {
this.serverRunner.withUserConfiguration(UndertowDeploymentInfoCustomizerConfiguration.class).run((context) -> {
UndertowServletWebServerFactory factory = context.getBean(UndertowServletWebServerFactory.class);
UndertowDeploymentInfoCustomizer customizer = context.getBean("deploymentInfoCustomizer",
UndertowDeploymentInfoCustomizer.class);
assertThat(factory.getDeploymentInfoCustomizers()).contains(customizer);
});
}
@Test
void undertowDeploymentInfoCustomizerRegisteredAsBeanAndViaFactoryIsOnlyCalledOnce() {
this.serverRunner.withUserConfiguration(DoubleRegistrationUndertowDeploymentInfoCustomizerConfiguration.class)
.run((context) -> {
UndertowServletWebServerFactory factory = context.getBean(UndertowServletWebServerFactory.class);
UndertowDeploymentInfoCustomizer customizer = context.getBean("deploymentInfoCustomizer",
UndertowDeploymentInfoCustomizer.class);
assertThat(factory.getDeploymentInfoCustomizers()).contains(customizer);
then(customizer).should().customize(any(DeploymentInfo.class));
});
}
@Test
void undertowBuilderCustomizerRegisteredAsBeanAndViaFactoryIsOnlyCalledOnce() {
this.serverRunner.withConfiguration(AutoConfigurations.of(UndertowServletWebServerAutoConfiguration.class))
.withUserConfiguration(DoubleRegistrationUndertowBuilderCustomizerConfiguration.class)
.run((context) -> {
UndertowServletWebServerFactory factory = context.getBean(UndertowServletWebServerFactory.class);
UndertowBuilderCustomizer customizer = context.getBean("builderCustomizer",
UndertowBuilderCustomizer.class);
assertThat(factory.getBuilderCustomizers()).contains(customizer);
then(customizer).should().customize(any(Builder.class));
});
}
@Test
void undertowBuilderCustomizerBeanIsAddedToFactory() {
this.serverRunner.withUserConfiguration(UndertowBuilderCustomizerConfiguration.class).run((context) -> {
UndertowServletWebServerFactory factory = context.getBean(UndertowServletWebServerFactory.class);
assertThat(factory.getBuilderCustomizers())
.contains(context.getBean("builderCustomizer", UndertowBuilderCustomizer.class));
});
}
@Test
void undertowServletWebServerFactoryCustomizerIsAutoConfigured() {
this.serverRunner.withUserConfiguration(UndertowBuilderCustomizerConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(UndertowServletWebServerFactoryCustomizer.class));
}
@Configuration(proxyBeanMethods = false)
static class UndertowBuilderCustomizerConfiguration {
@Bean
UndertowBuilderCustomizer builderCustomizer() {
return (builder) -> {
};
}
}
@Configuration(proxyBeanMethods = false)
static class DoubleRegistrationUndertowBuilderCustomizerConfiguration {
private final UndertowBuilderCustomizer customizer = mock(UndertowBuilderCustomizer.class);
@Bean
UndertowBuilderCustomizer builderCustomizer() {
return this.customizer;
}
@Bean
WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowCustomizer() {
return (undertow) -> undertow.addBuilderCustomizers(this.customizer);
}
}
@Configuration(proxyBeanMethods = false)
static class UndertowDeploymentInfoCustomizerConfiguration {
@Bean
UndertowDeploymentInfoCustomizer deploymentInfoCustomizer() {
return (deploymentInfo) -> {
};
}
}
@Configuration(proxyBeanMethods = false)
static class DoubleRegistrationUndertowDeploymentInfoCustomizerConfiguration {
private final UndertowDeploymentInfoCustomizer customizer = mock(UndertowDeploymentInfoCustomizer.class);
@Bean
UndertowDeploymentInfoCustomizer deploymentInfoCustomizer() {
return this.customizer;
}
@Bean
WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowCustomizer() {
return (undertow) -> undertow.addDeploymentInfoCustomizers(this.customizer);
}
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.servlet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.undertow.autoconfigure.UndertowServerProperties;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link UndertowServletWebServerFactoryCustomizer}
*
* @author Andy Wilkinson
*/
class UndertowServletWebServerFactoryCustomizerTests {
@Test
void eagerFilterInitCanBeDisabled() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory(0);
assertThat(factory.isEagerFilterInit()).isTrue();
UndertowServerProperties undertowProperties = new UndertowServerProperties();
undertowProperties.setEagerFilterInit(false);
new UndertowServletWebServerFactoryCustomizer(undertowProperties).customize(factory);
assertThat(factory.isEagerFilterInit()).isFalse();
}
@Test
void preservePathOnForwardCanBeEnabled() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory(0);
assertThat(factory.isPreservePathOnForward()).isFalse();
UndertowServerProperties undertowProperties = new UndertowServerProperties();
undertowProperties.setPreservePathOnForward(true);
new UndertowServletWebServerFactoryCustomizer(undertowProperties).customize(factory);
assertThat(factory.isPreservePathOnForward()).isTrue();
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.autoconfigure.servlet;
import jakarta.servlet.ServletContextListener;
import org.springframework.boot.undertow.servlet.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.servlet.AbstractServletWebServerServletContextListenerTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Tests for Undertow driving {@link ServletContextListener}s correctly.
*
* @author Andy Wilkinson
*/
class UndertowServletWebServerServletContextListenerTests extends AbstractServletWebServerServletContextListenerTests {
UndertowServletWebServerServletContextListenerTests() {
super(UndertowConfiguration.class);
}
@Configuration(proxyBeanMethods = false)
static class UndertowConfiguration {
@Bean
UndertowServletWebServerFactory webServerFactory() {
return new UndertowServletWebServerFactory(0);
}
}
}

View File

@ -1,173 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.reactive;
import java.io.File;
import java.time.Duration;
import java.util.Arrays;
import io.undertow.Undertow;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InOrder;
import reactor.core.publisher.Mono;
import org.springframework.boot.undertow.UndertowAccess;
import org.springframework.boot.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.undertow.UndertowWebServer;
import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.reactive.AbstractReactiveWebServerFactoryTests;
import org.springframework.boot.web.server.reactive.ConfigurableReactiveWebServerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException.ServiceUnavailable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link UndertowReactiveWebServerFactory} and {@link UndertowWebServer}.
*
* @author Brian Clozel
* @author Madhura Bhave
*/
class UndertowReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests {
@TempDir
File tempDir;
@Override
protected UndertowReactiveWebServerFactory getFactory() {
return new UndertowReactiveWebServerFactory(0);
}
@Test
void setNullBuilderCustomizersShouldThrowException() {
UndertowReactiveWebServerFactory factory = getFactory();
assertThatIllegalArgumentException().isThrownBy(() -> factory.setBuilderCustomizers(null))
.withMessageContaining("'customizers' must not be null");
}
@Test
void addNullBuilderCustomizersShouldThrowException() {
UndertowReactiveWebServerFactory factory = getFactory();
assertThatIllegalArgumentException()
.isThrownBy(() -> factory.addBuilderCustomizers((UndertowBuilderCustomizer[]) null))
.withMessageContaining("'customizers' must not be null");
}
@Test
void builderCustomizersShouldBeInvoked() {
UndertowReactiveWebServerFactory factory = getFactory();
HttpHandler handler = mock(HttpHandler.class);
UndertowBuilderCustomizer[] customizers = new UndertowBuilderCustomizer[4];
Arrays.setAll(customizers, (i) -> mock(UndertowBuilderCustomizer.class));
factory.setBuilderCustomizers(Arrays.asList(customizers[0], customizers[1]));
factory.addBuilderCustomizers(customizers[2], customizers[3]);
this.webServer = factory.getWebServer(handler);
InOrder ordered = inOrder((Object[]) customizers);
for (UndertowBuilderCustomizer customizer : customizers) {
ordered.verify(customizer).customize(any(Undertow.Builder.class));
}
}
@Test
void useForwardedHeaders() {
UndertowReactiveWebServerFactory factory = getFactory();
factory.setUseForwardHeaders(true);
assertForwardHeaderIsUsed(factory);
}
@Test
void accessLogCanBeEnabled() {
testAccessLog(null, null, "access_log.log");
}
@Test
void accessLogCanBeCustomized() {
testAccessLog("my_access.", "logz", "my_access.logz");
}
@Test
void whenServerIsShuttingDownGracefullyThenNewConnectionsAreRejectedWithServiceUnavailable() {
UndertowReactiveWebServerFactory factory = getFactory();
factory.setShutdown(Shutdown.GRACEFUL);
BlockingHandler blockingHandler = new BlockingHandler();
this.webServer = factory.getWebServer(blockingHandler);
this.webServer.start();
this.webServer.shutDownGracefully((result) -> {
});
WebClient webClient = getWebClient(this.webServer.getPort()).build();
Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> {
blockingHandler.stopBlocking();
try {
webClient.get().retrieve().toBodilessEntity().block();
return false;
}
catch (RuntimeException ex) {
return ex instanceof ServiceUnavailable;
}
});
this.webServer.stop();
}
private void testAccessLog(String prefix, String suffix, String expectedFile) {
UndertowReactiveWebServerFactory factory = getFactory();
factory.setAccessLogEnabled(true);
factory.setAccessLogPrefix(prefix);
factory.setAccessLogSuffix(suffix);
File accessLogDirectory = this.tempDir;
factory.setAccessLogDirectory(accessLogDirectory);
assertThat(accessLogDirectory).isEmptyDirectory();
this.webServer = factory.getWebServer(new EchoHandler());
this.webServer.start();
WebClient client = getWebClient(this.webServer.getPort()).build();
Mono<String> result = client.post()
.uri("/test")
.contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromValue("Hello World"))
.retrieve()
.bodyToMono(String.class);
assertThat(result.block(Duration.ofSeconds(30))).isEqualTo("Hello World");
File accessLog = new File(accessLogDirectory, expectedFile);
awaitFile(accessLog);
assertThat(accessLogDirectory.listFiles()).contains(accessLog);
}
private void awaitFile(File file) {
Awaitility.waitAtMost(Duration.ofSeconds(10)).until(file::exists, is(true));
}
@Override
protected String startedLogMessage() {
return UndertowAccess.getStartedLogMessage((UndertowWebServer) this.webServer);
}
@Override
protected void addConnector(int port, ConfigurableReactiveWebServerFactory factory) {
((UndertowReactiveWebServerFactory) factory)
.addBuilderCustomizers((builder) -> builder.addHttpListener(port, "0.0.0.0"));
}
}

View File

@ -1,97 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.File;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import io.undertow.servlet.api.SessionPersistenceManager.PersistentSession;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link FileSessionPersistence}.
*
* @author Phillip Webb
*/
class FileSessionPersistenceTests {
private File dir;
private FileSessionPersistence persistence;
private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
private final Date expiration = new Date(System.currentTimeMillis() + 10000);
@BeforeEach
void setup(@TempDir File tempDir) {
this.dir = tempDir;
this.dir.mkdir();
this.persistence = new FileSessionPersistence(this.dir);
}
@Test
void loadsNullForMissingFile() {
Map<String, PersistentSession> attributes = this.persistence.loadSessionAttributes("test", this.classLoader);
assertThat(attributes).isNull();
}
@Test
void persistAndLoad() {
Map<String, PersistentSession> sessionData = new LinkedHashMap<>();
Map<String, Object> data = new LinkedHashMap<>();
data.put("spring", "boot");
PersistentSession session = new PersistentSession(this.expiration, data);
sessionData.put("abc", session);
this.persistence.persistSessions("test", sessionData);
Map<String, PersistentSession> restored = this.persistence.loadSessionAttributes("test", this.classLoader);
assertThat(restored).isNotNull();
assertThat(restored.get("abc").getExpiration()).isEqualTo(this.expiration);
assertThat(restored.get("abc").getSessionData()).containsEntry("spring", "boot");
}
@Test
void dontRestoreExpired() {
Date expired = new Date(System.currentTimeMillis() - 1000);
Map<String, PersistentSession> sessionData = new LinkedHashMap<>();
Map<String, Object> data = new LinkedHashMap<>();
data.put("spring", "boot");
PersistentSession session = new PersistentSession(expired, data);
sessionData.put("abc", session);
this.persistence.persistSessions("test", sessionData);
Map<String, PersistentSession> restored = this.persistence.loadSessionAttributes("test", this.classLoader);
assertThat(restored).isNotNull();
assertThat(restored).doesNotContainKey("abc");
}
@Test
void deleteFileOnClear() {
File sessionFile = new File(this.dir, "test.session");
Map<String, PersistentSession> sessionData = new LinkedHashMap<>();
this.persistence.persistSessions("test", sessionData);
assertThat(sessionFile).exists();
this.persistence.clear("test");
assertThat(sessionFile).doesNotExist();
}
}

View File

@ -1,103 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceManager;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JarResourceManager}.
*
* @author Andy Wilkinson
*/
class JarResourceManagerTests {
@TempDir
static File tempDir;
@ResourceManagersTest
void emptyPathIsHandledCorrectly(String filename, ResourceManager resourceManager) throws IOException {
Resource resource = resourceManager.getResource("");
assertThat(resource).isNotNull();
assertThat(resource.isDirectory()).isTrue();
}
@ResourceManagersTest
void rootPathIsHandledCorrectly(String filename, ResourceManager resourceManager) throws IOException {
Resource resource = resourceManager.getResource("/");
assertThat(resource).isNotNull();
assertThat(resource.isDirectory()).isTrue();
}
@ResourceManagersTest
void resourceIsFoundInJarFile(String filename, ResourceManager resourceManager) throws IOException {
Resource resource = resourceManager.getResource("/hello.txt");
assertThat(resource).isNotNull();
assertThat(resource.isDirectory()).isFalse();
assertThat(resource.getContentLength()).isEqualTo(5);
}
@ResourceManagersTest
void resourceIsFoundInJarFileWithoutLeadingSlash(String filename, ResourceManager resourceManager)
throws IOException {
Resource resource = resourceManager.getResource("hello.txt");
assertThat(resource).isNotNull();
assertThat(resource.isDirectory()).isFalse();
assertThat(resource.getContentLength()).isEqualTo(5);
}
static List<Arguments> resourceManagers() throws IOException {
File jar = new File(tempDir, "test.jar");
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) {
out.putNextEntry(new ZipEntry("hello.txt"));
out.write("hello".getBytes());
}
File troublesomeNameJar = new File(tempDir, "test##1.0.jar");
FileCopyUtils.copy(jar, troublesomeNameJar);
return Arrays.asList(Arguments.of(jar.getName(), new JarResourceManager(jar)),
Arguments.of(troublesomeNameJar.getName(), new JarResourceManager(troublesomeNameJar)));
}
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("resourceManagers")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
private @interface ResourceManagersTest {
}
}

View File

@ -1,399 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.ServletContainer;
import jakarta.servlet.ServletRegistration.Dynamic;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.jasper.servlet.JspServlet;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.mockito.InOrder;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.boot.testsupport.web.servlet.ExampleServlet;
import org.springframework.boot.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.web.error.ErrorPage;
import org.springframework.boot.web.server.GracefulShutdownResult;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.servlet.AbstractServletWebServerFactoryTests;
import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link UndertowServletWebServerFactory}.
*
* @author Ivan Sopov
* @author Andy Wilkinson
*/
class UndertowServletWebServerFactoryTests extends AbstractServletWebServerFactoryTests {
@Override
protected UndertowServletWebServerFactory getFactory() {
return new UndertowServletWebServerFactory(0);
}
@AfterEach
void awaitClosureOfSslRelatedInputStreams() {
// https://issues.redhat.com/browse/UNDERTOW-1705
File resource = new File(this.tempDir, "test.txt");
Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> (!resource.isFile()) || resource.delete());
}
@Test
void errorPage404() throws Exception {
ConfigurableServletWebServerFactory factory = getFactory();
factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/hello"));
this.webServer = factory.getWebServer(new ServletRegistrationBean<>(new ExampleServlet(), "/hello"));
this.webServer.start();
assertThat(getResponse(getLocalUrl("/hello"))).isEqualTo("Hello World");
assertThat(getResponse(getLocalUrl("/not-found"))).isEqualTo("Hello World");
}
@Test
void setNullBuilderCustomizersThrows() {
UndertowServletWebServerFactory factory = getFactory();
assertThatIllegalArgumentException().isThrownBy(() -> factory.setBuilderCustomizers(null))
.withMessageContaining("'customizers' must not be null");
}
@Test
void addNullAddBuilderCustomizersThrows() {
UndertowServletWebServerFactory factory = getFactory();
assertThatIllegalArgumentException()
.isThrownBy(() -> factory.addBuilderCustomizers((UndertowBuilderCustomizer[]) null))
.withMessageContaining("'customizers' must not be null");
}
@Test
void builderCustomizers() {
UndertowServletWebServerFactory factory = getFactory();
UndertowBuilderCustomizer[] customizers = new UndertowBuilderCustomizer[4];
Arrays.setAll(customizers, (i) -> mock(UndertowBuilderCustomizer.class));
factory.setBuilderCustomizers(Arrays.asList(customizers[0], customizers[1]));
factory.addBuilderCustomizers(customizers[2], customizers[3]);
this.webServer = factory.getWebServer();
InOrder ordered = inOrder((Object[]) customizers);
for (UndertowBuilderCustomizer customizer : customizers) {
ordered.verify(customizer).customize(any(Builder.class));
}
}
@Test
void setNullDeploymentInfoCustomizersThrows() {
UndertowServletWebServerFactory factory = getFactory();
assertThatIllegalArgumentException().isThrownBy(() -> factory.setDeploymentInfoCustomizers(null))
.withMessageContaining("'customizers' must not be null");
}
@Test
void addNullAddDeploymentInfoCustomizersThrows() {
UndertowServletWebServerFactory factory = getFactory();
assertThatIllegalArgumentException()
.isThrownBy(() -> factory.addDeploymentInfoCustomizers((UndertowDeploymentInfoCustomizer[]) null))
.withMessageContaining("'customizers' must not be null");
}
@Test
void deploymentInfo() {
UndertowServletWebServerFactory factory = getFactory();
UndertowDeploymentInfoCustomizer[] customizers = new UndertowDeploymentInfoCustomizer[4];
Arrays.setAll(customizers, (i) -> mock(UndertowDeploymentInfoCustomizer.class));
factory.setDeploymentInfoCustomizers(Arrays.asList(customizers[0], customizers[1]));
factory.addDeploymentInfoCustomizers(customizers[2], customizers[3]);
this.webServer = factory.getWebServer();
InOrder ordered = inOrder((Object[]) customizers);
for (UndertowDeploymentInfoCustomizer customizer : customizers) {
ordered.verify(customizer).customize(any(DeploymentInfo.class));
}
}
@Test
@WithPackageResources("test.jks")
void basicSslClasspathKeyStore() throws Exception {
testBasicSslWithKeyStore("classpath:test.jks");
}
@Test
void defaultContextPath() {
UndertowServletWebServerFactory factory = getFactory();
final AtomicReference<String> contextPath = new AtomicReference<>();
factory.addDeploymentInfoCustomizers((deploymentInfo) -> contextPath.set(deploymentInfo.getContextPath()));
this.webServer = factory.getWebServer();
assertThat(contextPath.get()).isEqualTo("/");
}
@Test
void useForwardHeaders() throws Exception {
UndertowServletWebServerFactory factory = getFactory();
factory.setUseForwardHeaders(true);
assertForwardHeaderIsUsed(factory);
}
@Test
void eachFactoryUsesADiscreteServletContainer() {
assertThat(getServletContainerFromNewFactory()).isNotEqualTo(getServletContainerFromNewFactory());
}
@Test
void accessLogCanBeEnabled() throws IOException, URISyntaxException {
testAccessLog(null, null, "access_log.log");
}
@Test
void accessLogCanBeCustomized() throws IOException, URISyntaxException {
testAccessLog("my_access.", "logz", "my_access.logz");
}
@Test
void whenServerIsShuttingDownGracefullyThenRequestsAreRejectedWithServiceUnavailable() throws Exception {
ConfigurableServletWebServerFactory factory = getFactory();
factory.setShutdown(Shutdown.GRACEFUL);
BlockingServlet blockingServlet = new BlockingServlet();
this.webServer = factory.getWebServer((context) -> {
Dynamic registration = context.addServlet("blockingServlet", blockingServlet);
registration.addMapping("/blocking");
registration.setAsyncSupported(true);
});
this.webServer.start();
int port = this.webServer.getPort();
Future<Object> request = initiateGetRequest(port, "/blocking");
blockingServlet.awaitQueue();
AtomicReference<GracefulShutdownResult> result = new AtomicReference<>();
this.webServer.shutDownGracefully(result::set);
assertThat(result.get()).isNull();
blockingServlet.admitOne();
assertThat(request.get()).isInstanceOf(HttpResponse.class);
Object rejectedResult = initiateGetRequest(port, "/").get();
assertThat(rejectedResult).isInstanceOf(HttpResponse.class);
assertThat(((HttpResponse) rejectedResult).getCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value());
this.webServer.stop();
}
@Test
void whenServerIsShuttingDownARequestOnAnIdleConnectionAreRejectedWithServiceUnavailable() throws Exception {
ConfigurableServletWebServerFactory factory = getFactory();
factory.setShutdown(Shutdown.GRACEFUL);
BlockingServlet blockingServlet = new BlockingServlet();
this.webServer = factory.getWebServer((context) -> {
Dynamic registration = context.addServlet("blockingServlet", blockingServlet);
registration.addMapping("/blocking");
registration.setAsyncSupported(true);
});
HttpClient httpClient = HttpClients.createMinimal();
this.webServer.start();
int port = this.webServer.getPort();
Future<Object> keepAliveRequest = initiateGetRequest(httpClient, port, "/blocking");
blockingServlet.awaitQueue();
blockingServlet.admitOne();
assertThat(keepAliveRequest.get()).isInstanceOf(HttpResponse.class);
Future<Object> request = initiateGetRequest(port, "/blocking");
blockingServlet.awaitQueue();
this.webServer.shutDownGracefully((result) -> {
});
HttpResponse idleConnectionResponse = (HttpResponse) initiateGetRequest(httpClient, port, "/").get();
assertThat(idleConnectionResponse.getCode()).isEqualTo(503);
blockingServlet.admitOne();
Object response = request.get();
assertThat(response).isInstanceOf(HttpResponse.class);
this.webServer.stop();
}
@Test
@Override
@Disabled("https://issues.redhat.com/browse/UNDERTOW-2420")
protected void portClashOfSecondaryConnectorResultsInPortInUseException() throws Exception {
super.portClashOfSecondaryConnectorResultsInPortInUseException();
}
@Test
@Override
@Disabled("Restart after stop is not supported with Undertow")
protected void restartAfterStop() {
}
@Test
@Override
@Disabled("Undertow's architecture prevents separating stop and destroy")
protected void servletContextListenerContextDestroyedIsNotCalledWhenContainerIsStopped() {
}
private void testAccessLog(String prefix, String suffix, String expectedFile)
throws IOException, URISyntaxException {
UndertowServletWebServerFactory factory = getFactory();
factory.setAccessLogEnabled(true);
factory.setAccessLogPrefix(prefix);
factory.setAccessLogSuffix(suffix);
File accessLogDirectory = this.tempDir;
factory.setAccessLogDirectory(accessLogDirectory);
assertThat(accessLogDirectory).isEmptyDirectory();
this.webServer = factory.getWebServer(new ServletRegistrationBean<>(new ExampleServlet(), "/hello"));
this.webServer.start();
assertThat(getResponse(getLocalUrl("/hello"))).isEqualTo("Hello World");
File accessLog = new File(accessLogDirectory, expectedFile);
awaitFile(accessLog);
assertThat(accessLogDirectory.listFiles()).contains(accessLog);
}
@Override
protected void addConnector(int port, ConfigurableServletWebServerFactory factory) {
((UndertowServletWebServerFactory) factory)
.addBuilderCustomizers((builder) -> builder.addHttpListener(port, "0.0.0.0"));
}
@Test
@WithPackageResources("restricted.jks")
void sslRestrictedProtocolsEmptyCipherFailure() {
assertThatIOException()
.isThrownBy(() -> testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.2" },
new String[] { "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" }))
.isInstanceOfAny(SSLException.class, SSLHandshakeException.class, SocketException.class);
}
@Test
@WithPackageResources("restricted.jks")
void sslRestrictedProtocolsECDHETLS1Failure() {
assertThatIOException()
.isThrownBy(() -> testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1" },
new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" }))
.isInstanceOfAny(SSLException.class, SocketException.class);
}
@Test
@WithPackageResources("restricted.jks")
void sslRestrictedProtocolsECDHESuccess() throws Exception {
testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.2" },
new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" });
}
@Test
@WithPackageResources("restricted.jks")
@DisabledForJreRange(min = JRE.JAVA_24)
void sslRestrictedProtocolsRSATLS12Success() throws Exception {
testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.2" },
new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256" });
}
@Test
@WithPackageResources("restricted.jks")
void sslRestrictedProtocolsRSATLS11Failure() {
assertThatIOException()
.isThrownBy(() -> testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.1" },
new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256" }))
.isInstanceOfAny(SSLException.class, SocketException.class);
}
@Override
protected JspServlet getJspServlet() {
return null; // Undertow does not support JSPs
}
private void awaitFile(File file) {
Awaitility.waitAtMost(Duration.ofSeconds(10)).until(file::exists, is(true));
}
private ServletContainer getServletContainerFromNewFactory() {
UndertowServletWebServer container = (UndertowServletWebServer) getFactory().getWebServer();
try {
return container.getDeploymentManager().getDeployment().getServletContainer();
}
finally {
container.stop();
}
}
@Override
protected Map<String, String> getActualMimeMappings() {
return ((UndertowServletWebServer) this.webServer).getDeploymentManager()
.getDeployment()
.getMimeExtensionMappings();
}
@Override
protected Charset getCharset(Locale locale) {
DeploymentInfo info = ((UndertowServletWebServer) this.webServer).getDeploymentManager()
.getDeployment()
.getDeploymentInfo();
String charsetName = info.getLocaleCharsetMapping().get(locale.toString());
return (charsetName != null) ? Charset.forName(charsetName) : null;
}
@Override
protected void handleExceptionCausedByBlockedPortOnPrimaryConnector(RuntimeException ex, int blockedPort) {
assertThat(ex).isInstanceOf(PortInUseException.class);
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
Undertow undertow = (Undertow) ReflectionTestUtils.getField(this.webServer, "undertow");
assertThat(undertow.getWorker()).isNull();
}
@Override
protected void handleExceptionCausedByBlockedPortOnSecondaryConnector(RuntimeException ex, int blockedPort) {
handleExceptionCausedByBlockedPortOnPrimaryConnector(ex, blockedPort);
}
@Override
protected String startedLogMessage() {
return ((UndertowServletWebServer) this.webServer).getStartedLogMessage();
}
private void testRestrictedSSLProtocolsAndCipherSuites(String[] protocols, String[] ciphers) throws Exception {
ConfigurableServletWebServerFactory factory = getFactory();
factory.setSsl(getSsl(null, "password", "classpath:restricted.jks", null, protocols, ciphers));
this.webServer = factory.getWebServer(new ServletRegistrationBean<>(new ExampleServlet(true, false), "/hello"));
this.webServer.start();
HttpComponentsClientHttpRequestFactory requestFactory = createHttpComponentsRequestFactory(
createTrustSelfSignedTlsSocketStrategy());
assertThat(getResponse(getLocalUrl("https", "/hello"), requestFactory)).contains("scheme=https");
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.boot.undertow.servlet;
import org.springframework.boot.undertow.UndertowWebServer;
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AbstractServletWebServerMvcIntegrationTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Integration tests for {@link ServletWebServerApplicationContext} and
* {@link UndertowWebServer} running Spring MVC.
*/
class UndertowServletWebServerMvcIntegrationTests extends AbstractServletWebServerMvcIntegrationTests {
protected UndertowServletWebServerMvcIntegrationTests() {
super(UndertowConfig.class);
}
@Configuration(proxyBeanMethods = false)
static class UndertowConfig {
@Bean
UndertowServletWebServerFactory webServerFactory() {
return new UndertowServletWebServerFactory(0);
}
}
}

View File

@ -616,8 +616,8 @@ public abstract class AbstractReactiveWebServerFactoryTests {
ConfigurableReactiveWebServerFactory factory = getFactory(); ConfigurableReactiveWebServerFactory factory = getFactory();
this.webServer = factory.getWebServer(new EchoHandler()); this.webServer = factory.getWebServer(new EchoHandler());
this.webServer.start(); this.webServer.start();
assertThat(startedLogMessage()).matches( assertThat(startedLogMessage())
"(Jetty|Netty|Tomcat|Undertow) started on port " + this.webServer.getPort() + " \\(http(/1.1)?\\)"); .matches("(Jetty|Netty|Tomcat) started on port " + this.webServer.getPort() + " \\(http(/1.1)?\\)");
} }
@Test @Test
@ -626,7 +626,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
addConnector(0, factory); addConnector(0, factory);
this.webServer = factory.getWebServer(new EchoHandler()); this.webServer = factory.getWebServer(new EchoHandler());
this.webServer.start(); this.webServer.start();
assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on ports " + this.webServer.getPort() assertThat(startedLogMessage()).matches("(Jetty|Tomcat) started on ports " + this.webServer.getPort()
+ " \\(http(/1.1)?\\), [0-9]+ \\(http(/1.1)?\\)"); + " \\(http(/1.1)?\\), [0-9]+ \\(http(/1.1)?\\)");
} }

View File

@ -1372,7 +1372,7 @@ public abstract class AbstractServletWebServerFactoryTests {
ConfigurableServletWebServerFactory factory = getFactory(); ConfigurableServletWebServerFactory factory = getFactory();
this.webServer = factory.getWebServer(); this.webServer = factory.getWebServer();
this.webServer.start(); this.webServer.start();
assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on port " + this.webServer.getPort() assertThat(startedLogMessage()).matches("(Jetty|Tomcat) started on port " + this.webServer.getPort()
+ " \\(http(/1.1)?\\) with context path '/'"); + " \\(http(/1.1)?\\) with context path '/'");
} }
@ -1382,7 +1382,7 @@ public abstract class AbstractServletWebServerFactoryTests {
factory.setContextPath("/test"); factory.setContextPath("/test");
this.webServer = factory.getWebServer(); this.webServer = factory.getWebServer();
this.webServer.start(); this.webServer.start();
assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on port " + this.webServer.getPort() assertThat(startedLogMessage()).matches("(Jetty|Tomcat) started on port " + this.webServer.getPort()
+ " \\(http(/1.1)?\\) with context path '/test'"); + " \\(http(/1.1)?\\) with context path '/test'");
} }
@ -1392,7 +1392,7 @@ public abstract class AbstractServletWebServerFactoryTests {
addConnector(0, factory); addConnector(0, factory);
this.webServer = factory.getWebServer(); this.webServer = factory.getWebServer();
this.webServer.start(); this.webServer.start();
assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on ports " + this.webServer.getPort() assertThat(startedLogMessage()).matches("(Jetty|Tomcat) started on ports " + this.webServer.getPort()
+ " \\(http(/1.1)?\\), [0-9]+ \\(http(/1.1)?\\) with context path '/'"); + " \\(http(/1.1)?\\), [0-9]+ \\(http(/1.1)?\\) with context path '/'");
} }

View File

@ -40,7 +40,6 @@ dependencies {
optional(project(":module:spring-boot-metrics")) optional(project(":module:spring-boot-metrics"))
optional(project(":module:spring-boot-observation")) optional(project(":module:spring-boot-observation"))
optional(project(":module:spring-boot-tomcat")) optional(project(":module:spring-boot-tomcat"))
optional(project(":module:spring-boot-undertow"))
optional(project(":module:spring-boot-validation")) optional(project(":module:spring-boot-validation"))
optional(project(":module:spring-boot-web-server")) optional(project(":module:spring-boot-web-server"))
optional("com.fasterxml.jackson.core:jackson-databind") optional("com.fasterxml.jackson.core:jackson-databind")

View File

@ -21,14 +21,12 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import io.undertow.servlet.api.DeploymentManager;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import org.apache.catalina.Container; import org.apache.catalina.Container;
import org.apache.catalina.Context; import org.apache.catalina.Context;
import org.apache.catalina.core.StandardWrapper; import org.apache.catalina.core.StandardWrapper;
import org.springframework.boot.tomcat.TomcatWebServer; import org.springframework.boot.tomcat.TomcatWebServer;
import org.springframework.boot.undertow.servlet.UndertowServletWebServer;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext; import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -48,10 +46,6 @@ final class DispatcherServletHandlerMappings {
private static final boolean TOMCAT_WEB_SERVER_PRESENT = ClassUtils.isPresent( private static final boolean TOMCAT_WEB_SERVER_PRESENT = ClassUtils.isPresent(
"org.springframework.boot.tomcat.TomcatWebServer", DispatcherServletHandlerMappings.class.getClassLoader()); "org.springframework.boot.tomcat.TomcatWebServer", DispatcherServletHandlerMappings.class.getClassLoader());
private static final boolean UNDERTOW_WEB_SERVER_PRESENT = ClassUtils.isPresent(
"org.springframework.boot.undertow.UndertowWebServer",
DispatcherServletHandlerMappings.class.getClassLoader());
private final String name; private final String name;
private final DispatcherServlet dispatcherServlet; private final DispatcherServlet dispatcherServlet;
@ -79,10 +73,7 @@ final class DispatcherServletHandlerMappings {
return; return;
} }
WebServer webServer = webServerApplicationContext.getWebServer(); WebServer webServer = webServerApplicationContext.getWebServer();
if (UNDERTOW_WEB_SERVER_PRESENT && webServer instanceof UndertowServletWebServer undertowServletWebServer) { if (TOMCAT_WEB_SERVER_PRESENT && webServer instanceof TomcatWebServer tomcatWebServer) {
new UndertowServletInitializer(undertowServletWebServer).initializeServlet(this.name);
}
else if (TOMCAT_WEB_SERVER_PRESENT && webServer instanceof TomcatWebServer tomcatWebServer) {
new TomcatServletInitializer(tomcatWebServer).initializeServlet(this.name); new TomcatServletInitializer(tomcatWebServer).initializeServlet(this.name);
} }
} }
@ -124,26 +115,4 @@ final class DispatcherServletHandlerMappings {
} }
private static final class UndertowServletInitializer {
private final UndertowServletWebServer webServer;
private UndertowServletInitializer(UndertowServletWebServer webServer) {
this.webServer = webServer;
}
void initializeServlet(String name) {
try {
DeploymentManager deploymentManager = this.webServer.getDeploymentManager();
if (deploymentManager != null) {
deploymentManager.getDeployment().getServlets().getManagedServlet(name).forceInit();
}
}
catch (ServletException ex) {
// Continue
}
}
}
} }

View File

@ -2153,7 +2153,6 @@ bom {
"spring-boot-starter-test", "spring-boot-starter-test",
"spring-boot-starter-thymeleaf", "spring-boot-starter-thymeleaf",
"spring-boot-starter-tomcat", "spring-boot-starter-tomcat",
"spring-boot-starter-undertow",
"spring-boot-starter-validation", "spring-boot-starter-validation",
"spring-boot-starter-web", "spring-boot-starter-web",
"spring-boot-starter-web-services", "spring-boot-starter-web-services",
@ -2170,7 +2169,6 @@ bom {
"spring-boot-tomcat", "spring-boot-tomcat",
"spring-boot-tracing", "spring-boot-tracing",
"spring-boot-tx", "spring-boot-tx",
"spring-boot-undertow",
"spring-boot-validation", "spring-boot-validation",
"spring-boot-webclient", "spring-boot-webclient",
"spring-boot-webflux", "spring-boot-webflux",
@ -2652,18 +2650,6 @@ bom {
releaseNotes("https://github.com/pingidentity/ldapsdk/releases/tag/{version}") releaseNotes("https://github.com/pingidentity/ldapsdk/releases/tag/{version}")
} }
} }
library("Undertow", "2.3.18.Final") {
group("io.undertow") {
modules = [
"undertow-core",
"undertow-servlet",
"undertow-websockets-jsr"
]
}
links {
releaseNotes("https://github.com/undertow-io/undertow/releases/tag/{version}")
}
}
library("Versions Maven Plugin", "2.18.0") { library("Versions Maven Plugin", "2.18.0") {
group("org.codehaus.mojo") { group("org.codehaus.mojo") {
plugins = [ plugins = [

View File

@ -160,7 +160,6 @@ include "module:spring-boot-thymeleaf"
include "module:spring-boot-tomcat" include "module:spring-boot-tomcat"
include "module:spring-boot-tracing" include "module:spring-boot-tracing"
include "module:spring-boot-tx" include "module:spring-boot-tx"
include "module:spring-boot-undertow"
include "module:spring-boot-validation" include "module:spring-boot-validation"
include "module:spring-boot-web-server" // FIXME move to core? include "module:spring-boot-web-server" // FIXME move to core?
include "module:spring-boot-web-server-test" // FIXME move to core? include "module:spring-boot-web-server-test" // FIXME move to core?
@ -252,7 +251,6 @@ include "starter:spring-boot-starter-sql"
include "starter:spring-boot-starter-test" include "starter:spring-boot-starter-test"
include "starter:spring-boot-starter-thymeleaf" include "starter:spring-boot-starter-thymeleaf"
include "starter:spring-boot-starter-tomcat" include "starter:spring-boot-starter-tomcat"
include "starter:spring-boot-starter-undertow"
include "starter:spring-boot-starter-validation" include "starter:spring-boot-starter-validation"
include "starter:spring-boot-starter-web" include "starter:spring-boot-starter-web"
include "starter:spring-boot-starter-web-services" include "starter:spring-boot-starter-web-services"
@ -355,8 +353,6 @@ include ":smoke-test:spring-boot-smoke-test-tomcat-jsp"
include ":smoke-test:spring-boot-smoke-test-tomcat-multi-connectors" include ":smoke-test:spring-boot-smoke-test-tomcat-multi-connectors"
include ":smoke-test:spring-boot-smoke-test-tomcat-ssl" include ":smoke-test:spring-boot-smoke-test-tomcat-ssl"
include ":smoke-test:spring-boot-smoke-test-traditional" include ":smoke-test:spring-boot-smoke-test-traditional"
include ":smoke-test:spring-boot-smoke-test-undertow"
include ":smoke-test:spring-boot-smoke-test-undertow-ssl"
include ":smoke-test:spring-boot-smoke-test-war" include ":smoke-test:spring-boot-smoke-test-war"
include ":smoke-test:spring-boot-smoke-test-web-application-type" include ":smoke-test:spring-boot-smoke-test-web-application-type"
include ":smoke-test:spring-boot-smoke-test-web-freemarker" include ":smoke-test:spring-boot-smoke-test-web-freemarker"
@ -374,7 +370,6 @@ include ":smoke-test:spring-boot-smoke-test-webflux-coroutines"
include ":smoke-test:spring-boot-smoke-test-webservices" include ":smoke-test:spring-boot-smoke-test-webservices"
include ":smoke-test:spring-boot-smoke-test-websocket-jetty" include ":smoke-test:spring-boot-smoke-test-websocket-jetty"
include ":smoke-test:spring-boot-smoke-test-websocket-tomcat" include ":smoke-test:spring-boot-smoke-test-websocket-tomcat"
include ":smoke-test:spring-boot-smoke-test-websocket-undertow"
include ":smoke-test:spring-boot-smoke-test-xml" include ":smoke-test:spring-boot-smoke-test-xml"
include ":integration-test:spring-boot-actuator-integration-tests" include ":integration-test:spring-boot-actuator-integration-tests"

View File

@ -1,32 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
plugins {
id "java"
}
description = "Spring Boot Undertow SSL smoke test"
dependencies {
implementation(project(":starter:spring-boot-starter-webmvc")) {
exclude module: "spring-boot-starter-tomcat"
}
implementation(project(":starter:spring-boot-starter-undertow"))
testImplementation(project(":starter:spring-boot-starter-restclient"))
testImplementation(project(":starter:spring-boot-starter-test"))
testImplementation("org.apache.httpcomponents.client5:httpclient5")
}

View File

@ -1,29 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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 smoketest.undertow.ssl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleUndertowSslApplication {
public static void main(String[] args) {
SpringApplication.run(SampleUndertowSslApplication.class, args);
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
@NullMarked
package smoketest.undertow.ssl;
import org.jspecify.annotations.NullMarked;

View File

@ -1,30 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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 smoketest.undertow.ssl.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController {
@GetMapping("/")
public String helloWorld() {
return "Hello World";
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
@NullMarked
package smoketest.undertow.ssl.web;
import org.jspecify.annotations.NullMarked;

View File

@ -1,4 +0,0 @@
server.port = 8443
server.ssl.key-store = classpath:sample.jks
server.ssl.key-store-password = secret
server.ssl.key-password = password

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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 smoketest.undertow.ssl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
import org.springframework.boot.web.server.test.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Basic integration tests for demo application.
*
* @author Ivan Sopov
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleUndertowSslApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private AbstractConfigurableWebServerFactory webServerFactory;
@Test
void testSsl() {
assertThat(this.webServerFactory.getSsl().isEnabled()).isTrue();
}
@Test
void testHome() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("Hello World");
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed 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
*
* https://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.
*/
plugins {
id "java"
}
description = "Spring Boot Undertow smoke test"
dependencies {
implementation(project(":starter:spring-boot-starter-webmvc")) {
exclude module: "spring-boot-starter-tomcat"
}
implementation(project(":starter:spring-boot-starter-undertow"))
testImplementation(project(":starter:spring-boot-starter-restclient"))
testImplementation(project(":starter:spring-boot-starter-test"))
}

Some files were not shown because too many files have changed in this diff Show More