嵌入式 Web 服务器
每个 Spring Boot Web 应用程序都包含一个嵌入式 Web 服务器。此功能引发了一些常见问题,例如如何更换嵌入式服务器以及如何配置嵌入式服务器。本节将回答这些问题
使用其他 Web 服务器
许多 Spring Boot 起步依赖包含默认的嵌入式容器。
- 对于基于 Servlet 栈的应用程序,
spring-boot-starter-web通过包含spring-boot-starter-tomcat来默认使用 Tomcat,但你也可以选择使用spring-boot-starter-jetty或spring-boot-starter-undertow - 对于基于响应式栈的应用程序,
spring-boot-starter-webflux通过包含spring-boot-starter-reactor-netty来默认使用 Reactor Netty,但你也可以选择使用spring-boot-starter-tomcat、spring-boot-starter-jetty或spring-boot-starter-undertow。
在切换到其他 HTTP 服务器时,你需要替换默认的依赖为你需要的依赖。为简化此过程,Spring Boot 为每个支持的 HTTP 服务器提供了单独的起步依赖
以下 Maven 示例展示了如何排除 Tomcat 并为 Spring MVC 包含 Jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>以下 Gradle 示例配置了必要的依赖,并通过模块替换将 Spring WebFlux 中的 Reactor Netty 替换为 Undertow:
dependencies {
implementation "org.springframework.boot:spring-boot-starter-undertow"
implementation "org.springframework.boot:spring-boot-starter-webflux"
modules {
module("org.springframework.boot:spring-boot-starter-reactor-netty") {
replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Reactor Netty")
}
}
}spring-boot-starter-reactor-netty 是使用 WebClient 类所必需的,因此即使需要包含不同的 HTTP 服务器,你可能仍需保留对 Netty 的依赖
禁用 Web 服务器
如果 classpath 中包含启动 Web 服务器所需的组件,Spring Boot 将自动启动它。要禁用此行为,可以在 application.properties 中配置 WebApplicationType ,如下示例所示:
spring.main.web-application-type=none更改 HTTP 端口
在独立应用中,默认的主 HTTP 端口是 8080 ,但可以通过 server.port 设置(例如在 application.properties 中或作为系统属性)。由于 Spring Environment 的宽松绑定,你也可以使用 SERVER_PORT (例如作为操作系统环境变量)。
如果你想完全关闭 HTTP 端点,但仍然创建一个 WebApplicationContext ,可以使用 server.port=-1 (这种做法有时在测试中很有用)。
更多详情请参阅 “Spring Boot Features” 章节中的 Customizing Embedded Servlet Containers 或查看 ServerProperties 类
使用随机未分配的 HTTP 端口
要扫描一个空闲端口(使用操作系统本地功能以防止冲突),可以设置 server.port=0
在运行时发现 HTTP 端口
你可以从日志输出中或通过 WebServerApplicationContext 的 WebServer 访问服务器正在运行的端口。为了确保它已经被初始化,最好的方法是添加一个 @Bean ,类型为 ApplicationListener<WebServerInitializedEvent> ,并在事件发布时从事件中获取容器
使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 的测试还可以通过 @LocalServerPort 注解将实际端口注入到字段中,如以下示例所示:
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyWebIntegrationTests {
@LocalServerPort
int port;
// ...
}@LocalServerPort 是 @Value("${local.server.port}") 的元注解。不要尝试在常规应用程序中注入端口。正如我们刚刚所看到的,该值仅在容器初始化之后才会被设置。与测试不同,应用程序代码的回调会在值实际可用之前(较早阶段)处理
启用 HTTP 响应压缩
HTTP 响应压缩由 Jetty、Tomcat、Reactor Netty 和 Undertow 支持。可以在 application.properties 中启用,如下所示:
server.compression.enabled=true默认情况下,只有响应长度至少为 2048 字节时才会进行压缩。你可以通过设置 server.compression.min-response-size 属性来配置此行为。
默认情况下,只有当响应的内容类型是以下之一时,才会进行压缩:
text/htmltext/xmltext/plaintext/csstext/javascriptapplication/javascriptapplication/jsonapplication/xml
你可以通过设置 server.compression.mime-types 属性来配置此行为。
配置 SSL
SSL 可以通过设置各种 server.ssl.* 属性来声明式配置,通常在 application.properties 或 application.yaml 中进行配置。有关所有支持的属性,请参阅 Ssl。
以下示例展示了如何使用 Java KeyStore 文件来设置 SSL 属性:
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret使用如上所示的配置意味着应用程序不再支持在端口 8080 上的普通 HTTP 连接器。Spring Boot 不支持通过 application.properties 配置同时使用 HTTP 和 HTTPS 连接器。如果你需要同时配置这两个连接器,你需要通过编程方式配置其中一个。我们推荐使用 application.properties 配置 HTTPS,因为 HTTP 连接器更容易通过编程方式进行配置
使用 PEM 编码文件
你可以使用 PEM 编码的文件代替 Java KeyStore 文件。建议尽可能使用 PKCS#8 密钥文件。PEM 编码的 PKCS#8 密钥文件以 -----BEGIN PRIVATE KEY----- 或 -----BEGIN ENCRYPTED PRIVATE KEY----- 头部开始
如果你有其他格式的文件,例如 PKCS#1 ( -----BEGIN RSA PRIVATE KEY----- ) 或 SEC 1 ( -----BEGIN EC PRIVATE KEY----- ),你可以使用 OpenSSL 将它们转换为 PKCS#8 格式:
openssl pkcs8 -topk8 -nocrypt -in <input file> -out <output file>以下示例展示了如何使用 PEM 编码的证书和私钥文件设置 SSL 属性:
server.port=8443
server.ssl.certificate=classpath:my-cert.crt
server.ssl.certificate-private-key=classpath:my-cert.key
server.ssl.trust-certificate=classpath:ca-cert.crt使用 SSL Bundles
另外,SSL 授信材料可以配置在 SSL 捆绑包中,并应用到 Web 服务器,示例如下:
server.port=8443
server.ssl.bundle=exampleserver.ssl.bundle 属性不能与 server.ssl 下的独立 Java KeyStore 或 PEM 属性选项一起使用
配置服务器名称指示 (SNI)
Tomcat、Netty 和 Undertow 可以配置使用独特的 SSL 信任材料来支持服务器名称指示 (SNI)。Jetty 不支持 SNI 配置,但如果提供多个证书,Jetty 可以自动设置 SNI。
假设已经配置了名为 web、web-alt1 和 web-alt2 的 SSL 包,以下配置可以用于将每个包分配给由嵌入式 Web 服务器提供的主机名:
server.port=8443
server.ssl.bundle=web
server.ssl.server-name-bundles[0].server-name=alt1.example.com
server.ssl.server-name-bundles[0].bundle=web-alt1
server.ssl.server-name-bundles[1].server-name=alt2.example.com
server.ssl.server-name-bundles[1].bundle=web-alt2通过 server.ssl.bundle 指定的 SSL 包将用于默认主机,并且适用于任何支持 SNI 的客户端。如果配置了 server.ssl.server-name-bundles ,则必须配置此默认包。
配置 HTTP/2
你可以通过配置 server.http2.enabled 属性启用 Spring Boot 应用程序的 HTTP/2 支持。支持 h2 (通过 TLS 的 HTTP/2)和 h2c (通过 TCP 的 HTTP/2)。要使用 h2 ,必须启用 SSL。如果未启用 SSL,则将使用 h2c 。例如,当应用程序运行在执行 TLS 终止的代理服务器后面时,你可能想要使用 h2c
- h2 需要 SSL/TLS 加密,是 HTTP/2 的标准实现,适用于安全通信。
- h2c 不需要加密,适用于没有加密的 HTTP 通信,通常用于内部通信或在代理服务器进行 TLS 终止时
HTTP/2 与 Tomcat
Spring Boot 默认使用 Tomcat 10.1.x,支持开箱即用的 h2c 和 h2 。或者,如果在主机操作系统上安装了 libtcnative 库及其依赖项,你也可以使用该库来支持 h2 。
必须确保库目录可用于 JVM 库路径。如果尚未可用,可以通过 JVM 参数(如 -Djava.library.path=/usr/local/opt/tomcat-native/lib )来配置。有关更多信息,请参阅官方 Tomcat 文档
HTTP/2 与 Jetty
要支持 HTTP/2,Jetty 需要额外的 org.eclipse.jetty.http2:jetty-http2-server 依赖项。要使用 h2c ,不需要其他依赖项。如果使用 h2 ,还需要选择以下依赖项之一,具体取决于你的部署:
org.eclipse.jetty:jetty-alpn-java-server(使用 JDK 内置支持)org.eclipse.jetty:jetty-alpn-conscrypt-server和 Conscrypt 库
HTTP/2 与 Reactor Netty
spring-boot-webflux-starter 默认使用 Reactor Netty 作为服务器,Reactor Netty 支持开箱即用的 h2c 和 h2 。为了获得最佳运行时性能,该服务器还支持带有本地库的 h2 。要启用这一功能,应用程序需要添加一个额外的依赖项
Spring Boot 管理 io.netty:netty-tcnative-boringssl-static 的版本,该库包含所有平台的本地库。开发者可以选择仅导入所需的依赖项,使用分类符(参见 Netty 官方文档)。
HTTP/2 与 Undertow
Undertow 开箱即用支持 h2c 和 h2
配置 Web 服务器
通常,你应该首先考虑使用众多可用的配置键,通过在 application.properties 或 application.yaml 文件中添加新的条目来定制你的 Web 服务器。可以参考 发现内置选项的外部属性 。 server.* 命名空间在这里非常有用,它包括像 server.tomcat.* 、 server.jetty.* 等子命名空间,用于配置特定服务器的功能。详情请参见常见应用程序属性的列表
前面几节已经覆盖了许多常见的用例,如压缩、SSL 或 HTTP/2。但是,如果没有现成的配置键来满足你的需求,你应该考虑使用 WebServerFactoryCustomizer。你可以声明这样的组件,并获取与你选择的服务器工厂相关的访问权限:你应该根据选择的服务器(Tomcat、Jetty、Reactor Netty、Undertow)和 Web 堆栈(Servlet 或 Reactive)选择对应的变体
下面的示例是针对使用 spring-boot-starter-web (Servlet 堆栈)的 Tomcat 配置:
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyTomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
// customize the factory here
}
}Spring Boot 内部使用该基础设施来自动配置服务器。自动配置的 WebServerFactoryCustomizer beans 的顺序是 0 ,并且会在任何用户定义的自定义器之前处理,除非它具有显式的顺序来指明其他的处理顺序
一旦你通过自定义器访问了 WebServerFactory ,你可以使用它来配置特定的部分,比如连接器、服务器资源或服务器本身——所有这些都可以通过特定服务器的 API 来完成
此外,Spring Boot 提供了:
| Server | Servlet stack | Reactive stack |
| Tomcat | TomcatServletWebServerFactory | TomcatReactiveWebServerFactory |
| Jetty | JettyServletWebServerFactory | JettyReactiveWebServerFactory |
| Undertow | UndertowServletWebServerFactory | UndertowReactiveWebServerFactory |
| Reactor | N/A | NettyReactiveWebServerFactory |
作为最后的手段,你也可以声明你自己的 WebServerFactory bean,这将覆盖 Spring Boot 提供的工厂。当你这样做时,自动配置的自定义器仍然会应用到你的自定义工厂中,所以在使用此选项时要小心
向应用程序添加 Servlet、Filter 或 Listener
在 Servlet 栈应用程序中,也就是使用 spring-boot-starter-web 时,有两种方式可以将 Servlet 、 Filter 、 ServletContextListener 以及 Servlet API 支持的其他监听器添加到你的应用程序
通过 Spring Bean 添加 Servlet、Filter 或 Listener
为了通过 Spring Bean 添加 Servlet 、 Filter 或 Servlet *Listener ,你必须为它们提供一个 @Bean 定义。这样做对于想要注入配置或依赖的场景非常有用。然而,你必须非常小心,确保它们不会导致过早初始化过多的其他 Bean,因为它们必须在应用程序生命周期的很早阶段就被安装到容器中。例如,最好避免让它们依赖于 DataSource 或 JPA 配置等 Bean。你可以通过将 Bean 设置为在首次使用时懒初始化,而不是在初始化时加载,来解决这种限制。
在 Filters 和 Servlets 的情况下,你还可以通过 FilterRegistrationBean 或 ServletRegistrationBean 来添加映射和初始化参数,作为或附加到底层组件
如果没有在 Filter 注册时指定 dispatcherType ,默认使用 REQUEST ,这与 Servlet 规范中的默认调度类型一致
与其他 Spring Bean 一样,你可以定义 Servlet、Filter Bean 的加载顺序。确保查看 Registering Servlets, Filters, and Listeners as Spring Beans 部分,了解如何控制 Bean 的顺序
禁用 Servlet 或 Filter 的注册
如前所述,任何 Servlet 或 Filter bean 都会自动注册到 servlet 容器中。要禁用特定 Filter 或 Servlet bean 的注册,可以为其创建一个注册 bean 并将其标记为禁用,如下所示:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> registration(MyFilter filter) {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}
}通过 classpath 扫描添加 Servlets、Filters 和 Listeners
通过在 @Configuration 类上使用 @ServletComponentScan 注解,并指定包含要注册的组件的包,可以自动将带有 @WebServlet 、 @WebFilter 和 @WebListener 注解的类注册到嵌入式 Servlet 容器中。默认情况下, @ServletComponentScan 会从注解所在的类的包开始扫描
配置访问日志
可以通过各自的命名空间为 Tomcat、Undertow 和 Jetty 配置访问日志。
例如,以下设置使用自定义模式在 Tomcat 上记录访问日志
server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a %r %s (%D microseconds)日志的默认位置是相对于 Tomcat 基目录的 logs 目录。默认情况下, logs 目录是一个临时目录,因此你可能需要固定 Tomcat 的基准目录或使用日志的绝对路径。在前面的示例中,日志位于相对于应用程序工作目录的 my-tomcat/logs 中
Undertow 的访问日志可以以类似的方式进行配置,如下所示:
server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a %r %s (%D milliseconds)
server.undertow.options.server.record-request-start-time=true请注意,除了启用访问日志并配置其模式外,还启用了记录请求开始时间。这在访问日志模式中包含响应时间( %D )时是必需的。日志存储在相对于应用程序工作目录的 logs 目录中。你可以通过设置 server.undertow.accesslog.dir 属性来自定义此位置
最后,Jetty 的访问日志配置如下:
server.jetty.accesslog.enabled=true
server.jetty.accesslog.filename=/var/log/jetty-access.log默认情况下,日志被重定向到 System.err 。更多详情,请参见 Jetty 文档。
在前端代理服务器后运行
如果你的应用程序运行在代理、负载均衡器后面,或者在云中,请求信息(如主机、端口、协议等)可能会在传输过程中发生变化。你的应用程序可能运行在 10.10.10.10:8080 ,但 HTTP 客户端应该只看到 example.org 。
RFC7239 “Forwarded Headers” 定义了 Forwarded HTTP 头部;代理可以使用这个头部提供有关原始请求的信息。你可以配置你的应用程序读取这些头部,并在创建链接并发送给客户端时,自动使用这些信息,比如在 HTTP 302 响应、JSON 文档或 HTML 页面中。有一些非标准的头部,比如 X-Forwarded-Host 、 X-Forwarded-Port 、 X-Forwarded-Proto 、 X-Forwarded-Ssl 和 X-Forwarded-Prefix 。
如果代理添加了常用的 X-Forwarded-For 和 X-Forwarded-Proto 头部,设置 server.forward-headers-strategy 为 NATIVE 就足以支持这些头部。使用此选项,Web 服务器本身会原生支持此功能;你可以查看它们的具体文档来了解具体的行为。
如果这还不够,Spring 框架提供了一个 ForwardedHeaderFilter(用于 servlet 栈)和一个 ForwardedHeaderTransformer(用于反应栈)。你可以通过将 server.forward-headers-strategy 设置为 FRAMEWORK 来在应用程序中使用它们
如果你使用 Tomcat 并且在代理处终止 SSL,应该将 server.tomcat.redirect-context-root 设置为 false 。这允许在执行任何重定向之前先处理 X-Forwarded-Proto 头部。
如果你的应用程序运行在支持的云平台中, server.forward-headers-strategy 属性默认为 NATIVE 。在其他情况下,默认为 NONE 。
自定义 Tomcat 的代理配置
如果你使用 Tomcat,你还可以配置用于携带“转发”信息的头部名称,如下所示:
server.tomcat.remoteip.remote-ip-header=x-your-remote-ip-header
server.tomcat.remoteip.protocol-header=x-your-protocol-headerTomcat 还配置了一个正则表达式,用于匹配需要信任的内部代理。请参阅附录中的 server.tomcat.remoteip.internal-proxies 条目,了解其默认值。你可以通过在 application.properties 中添加条目来定制该阀门的配置,如下所示:
server.tomcat.remoteip.internal-proxies=192\.168\.\d{1,3}\.\d{1,3}你可以通过将 internal-proxies 设置为空来信任所有代理(但不要在生产环境中这样做)。
你还可以通过关闭自动配置来完全控制 Tomcat 的 RemoteIpValve 配置(方法是设置 server.forward-headers-strategy=NONE ),然后使用 WebServerFactoryCustomizer bean 添加一个新的阀门实例
启用 Tomcat 的多连接器
你可以将 org.apache.catalina.connector.Connector 添加到 TomcatServletWebServerFactory 中,这样可以允许多个连接器,包括 HTTP 和 HTTPS 连接器,如下例所示:
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyTomcatConfiguration {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> connectorCustomizer() {
return (tomcat) -> tomcat.addAdditionalTomcatConnectors(createConnector());
}
private Connector createConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(8081);
return connector;
}
}启用 Tomcat 的 MBean 注册表
嵌入式 Tomcat 的 MBean 注册表默认是禁用的。这是为了最小化 Tomcat 的内存占用。如果你希望使用 Tomcat 的 MBeans,例如使其能够被 Micrometer 用来暴露度量信息,你必须使用 server.tomcat.mbeanregistry.enabled 属性来启用它,如下例所示:
server.tomcat.mbeanregistry.enabled=true启用 Undertow 的多监听器
将 UndertowBuilderCustomizer 添加到 UndertowServletWebServerFactory ,并向构建器添加一个监听器,如下例所示:
import io.undertow.Undertow.Builder;
import org.springframework.boot.web.embedded.undertow.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");
}
}使用 @ServerEndpoint 创建 WebSocket 端点
如果你想在使用嵌入式容器的 Spring Boot 应用程序中使用 @ServerEndpoint ,你必须声明一个 ServerEndpointExporter 的 @Bean,如下所示:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration(proxyBeanMethods = false)
public class MyWebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}上面示例中的 @Bean 会将所有 @ServerEndpoint 注解的 bean 注册到底层的 WebSocket 容器中。当部署到独立的 servlet 容器时,这个角色由 servlet 容器初始化器执行, ServerEndpointExporter bean 就不再需要了
