嵌入式 Web 服务器
每个Spring Boot Web应用程序都包含一个嵌入式Web服务器。此功能引出了许多操作问题,包括如何更改嵌入式服务器以及如何配置嵌入式服务器。本节将回答这些问题。
使用其他Web服务器
许多Spring Boot starter都包含默认的嵌入式容器。
-
对于Servlet栈应用程序,
spring-boot-starter-web通过包含spring-boot-starter-tomcat来包含Tomcat,但您也可以使用spring-boot-starter-jetty。 -
对于响应式栈应用程序,
spring-boot-starter-webflux通过包含spring-boot-starter-reactor-netty来包含Reactor Netty,但您也可以使用spring-boot-starter-tomcat或spring-boot-starter-jetty。
切换到不同的HTTP服务器时,需要将默认依赖项替换为所需的依赖项。为了帮助完成此过程,Spring Boot为每个支持的HTTP服务器提供了一个单独的starter。
以下示例演示了如何排除Tomcat并为Spring MVC包含Jetty
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</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>
dependencies {
implementation('org.springframework.boot:spring-boot-starter-webmvc') {
// Exclude the Tomcat dependency
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
// Use Jetty instead
implementation "org.springframework.boot:spring-boot-starter-jetty"
}
如果您正在创建war文件,可以使用类似的方法,但必须指明provided依赖项
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</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>
<scope>provided</scope>
</dependency>
dependencies {
implementation('org.springframework.boot:spring-boot-starter-webmvc') {
// Exclude the Tomcat dependency
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
// Use Jetty instead
implementation "org.springframework.boot:spring-boot-starter-jetty"
providedRuntime "org.springframework.boot:spring-boot-starter-jetty-runtime"
}
禁用Web服务器
如果您的类路径包含启动Web服务器所需的必要组件,Spring Boot将自动启动它。要禁用此行为,请在 application.properties 中配置 WebApplicationType,如以下示例所示
-
属性
-
YAML
spring.main.web-application-type=none
spring:
main:
web-application-type: "none"
更改HTTP端口
在独立应用程序中,主HTTP端口默认为 8080,但可以通过 server.port 进行设置(例如,在 application.properties 中或作为系统属性)。由于 Environment 值的宽松绑定,您也可以使用 SERVER_PORT(例如,作为OS环境变量)。
要完全关闭HTTP端点但仍创建 WebApplicationContext,请使用 server.port=-1(这样做有时对测试很有用)。
有关更多详细信息,请参阅“Spring Boot Features”部分中的 自定义嵌入式Servlet容器,或 ServerProperties 类。
在运行时发现HTTP端口
您可以从日志输出或通过 WebServerApplicationContext 及其 WebServer 访问服务器运行的端口。获取该端口并确保其已初始化的最佳方法是添加一个类型为 ApplicationListener<WebServerInitializedEvent> 的 @Bean,并在事件发布时从事件中拉取容器。
使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 的测试也可以通过使用 @LocalServerPort 注解将实际端口注入到字段中,如以下示例所示
-
Java
-
Kotlin
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;
// ...
}
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
var port = 0
// ...
}
|
|
启用HTTP响应压缩
Jetty、Tomcat和Reactor Netty都支持HTTP响应压缩。可以在 application.properties 中启用,如下所示
-
属性
-
YAML
server.compression.enabled=true
server:
compression:
enabled: true
默认情况下,响应长度必须至少为2048字节才能执行压缩。您可以通过设置 server.compression.min-response-size 属性来配置此行为。
默认情况下,只有当响应的内容类型是以下之一时才会被压缩
-
text/html -
text/xml -
text/plain -
text/css -
text/javascript -
application/javascript -
application/json -
application/xml
您可以通过设置 server.compression.mime-types 属性来配置此行为。
配置SSL
SSL可以通过声明性地设置各种 server.ssl.* 属性进行配置,通常在 application.properties 或 application.yaml 中。有关所有支持的属性的详细信息,请参阅 Ssl。
以下示例演示了使用Java KeyStore文件设置SSL属性
-
属性
-
YAML
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret
server:
port: 8443
ssl:
key-store: "classpath:keystore.jks"
key-store-password: "secret"
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属性
-
属性
-
YAML
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
server:
port: 8443
ssl:
certificate: "classpath:my-cert.crt"
certificate-private-key: "classpath:my-cert.key"
trust-certificate: "classpath:ca-cert.crt"
使用SSL捆绑包
或者,SSL信任材料可以在 SSL捆绑包 中配置,并应用于Web服务器,如以下示例所示
-
属性
-
YAML
server.port=8443
server.ssl.bundle=example
server:
port: 8443
ssl:
bundle: "example"
|
使用捆绑包时, |
配置服务器名称指示
Tomcat和Netty可以配置为对单个主机名使用唯一的SSL信任材料以支持服务器名称指示(SNI)。Jetty不支持SNI配置,但如果向其提供多个证书,Jetty可以 自动设置SNI。
假设已配置名为 web、web-alt1 和 web-alt2 的 SSL捆绑包,则可以使用以下配置将每个捆绑包分配给嵌入式Web服务器提供服务的主机名
-
属性
-
YAML
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:
port: 8443
ssl:
bundle: "web"
server-name-bundles:
- server-name: "alt1.example.com"
bundle: "web-alt1"
- server-name: "alt2.example.com"
bundle: "web-alt2"
server.ssl.bundle 指定的捆绑包将用于默认主机以及任何不支持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。
Tomcat的HTTP/2
Spring Boot默认捆绑Tomcat 11.0.x,它开箱即支持 h2c 和 h2。或者,如果主机操作系统上安装了 libtcnative 及其依赖项,则可以使用 libtcnative 提供 h2 支持。
如果尚未提供,必须将库目录提供给JVM库路径。您可以使用JVM参数(例如 -Djava.library.path=/usr/local/opt/tomcat-native/lib)来完成此操作。更多信息请参阅 Tomcat官方文档。
Jetty的HTTP/2
为了支持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库
Reactor Netty的HTTP/2
spring-boot-webflux-starter 默认使用Reactor Netty作为服务器。Reactor Netty开箱即支持 h2c 和 h2。为了获得最佳运行时性能,该服务器还支持带有原生库的 h2。要启用此功能,您的应用程序需要有一个额外的依赖项。
Spring Boot管理 io.netty:netty-tcnative-boringssl-static“uber jar”的版本,其中包含所有平台的原生库。开发人员可以选择使用分类器仅导入所需的依赖项(请参阅 Netty官方文档)。
配置Web服务器
通常,您应该首先考虑使用许多可用配置键之一,并通过在 application.properties 或 application.yaml 文件中添加新条目来定制您的Web服务器。请参阅 发现外部属性的内置选项)。这里的 server.* 命名空间非常有用,它包括 server.tomcat.*、server.jetty.* 等命名空间,用于服务器特定功能。请参阅 常见应用程序属性 列表。
前面的章节已经涵盖了许多常见用例,例如压缩、SSL或HTTP/2。但是,如果您的用例没有配置键,那么您应该查看 WebServerFactoryCustomizer。您可以声明这样一个组件并访问与您选择相关的服务器工厂:您应该为所选服务器(Tomcat、Jetty、Reactor Netty)和所选Web栈(servlet或reactive)选择变体。
下面的示例适用于带有 spring-boot-starter-web(servlet栈)的Tomcat
-
Java
-
Kotlin
import org.springframework.boot.tomcat.servlet.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
}
}
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
@Component
class MyTomcatWebServerCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
override fun customize(factory: TomcatServletWebServerFactory) {
// customize the factory here
}
}
Spring Boot在内部使用该基础结构自动配置服务器。自动配置的 WebServerFactoryCustomizer bean的顺序为 0,并将在任何用户定义的定制器之前处理,除非它有明确的顺序另有规定。 |
一旦您使用定制器访问了 WebServerFactory,您就可以使用它来配置特定部分,例如连接器、服务器资源或服务器本身——所有这些都使用服务器特定的API。
此外,Spring Boot还提供
| 服务器 | Servlet栈 | 响应式栈 |
|---|---|---|
Tomcat |
||
Jetty |
||
Reactor |
不适用 |
作为最后的手段,您还可以声明自己的 WebServerFactory bean,它将覆盖Spring Boot提供的bean。当您这样做时,自动配置的定制器仍然会应用于您的自定义工厂,因此请谨慎使用此选项。
向应用程序添加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来解决此类限制。
对于过滤器和servlet,您还可以通过添加 FilterRegistrationBean 或 ServletRegistrationBean 来添加映射和初始化参数,而不是或除了底层组件之外。您还可以使用 @ServletRegistration 和 @FilterRegistration 作为 ServletRegistrationBean 和 FilterRegistrationBean 的基于注解的替代方案。
|
如果未在过滤器注册中指定 |
与任何其他Spring bean一样,您可以定义servlet过滤器bean的顺序;请务必查看 将Servlet、Filter和Listener注册为Spring Bean 部分。
禁用Servlet或Filter的注册
如 前所述,任何 Servlet 或 Filter bean都会自动注册到servlet容器中。要禁用特定 Filter 或 Servlet bean 的注册,请为其创建一个注册bean并将其标记为禁用,如以下示例所示
-
Java
-
Kotlin
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;
}
}
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {
@Bean
fun registration(filter: MyFilter): FilterRegistrationBean<MyFilter> {
val registration = FilterRegistrationBean(filter)
registration.isEnabled = false
return registration
}
}
使用类路径扫描添加Servlet、Filter和Listener
通过使用 @Configuration 类并指定包含您要注册的组件的包,用 @WebServlet、@WebFilter 和 @WebListener 注解的类可以自动注册到嵌入式Servlet容器中。默认情况下,@ServletComponentScan 从注解类的包开始扫描。
配置访问日志
可以通过Tomcat和Jetty各自的命名空间配置访问日志。
例如,以下设置使用 自定义模式 记录Tomcat上的访问。
-
属性
-
YAML
server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a %r %s (%D microseconds)
server:
tomcat:
basedir: "my-tomcat"
accesslog:
enabled: true
pattern: "%t %a %r %s (%D microseconds)"
日志的默认位置是相对于Tomcat基本目录的 logs 目录。默认情况下,logs 目录是一个临时目录,因此您可能需要固定Tomcat的基本目录或使用日志的绝对路径。在上面的示例中,日志位于应用程序工作目录的 my-tomcat/logs 中。 |
最后,Jetty的访问日志也可以配置如下
-
属性
-
YAML
server.jetty.accesslog.enabled=true
server.jetty.accesslog.filename=/var/log/jetty-access.log
server:
jetty:
accesslog:
enabled: true
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 Framework为servlet栈提供了 ForwardedHeaderFilter,为响应式栈提供了 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,您还可以配置用于携带“转发”信息的头的名称,如以下示例所示
-
属性
-
YAML
server.tomcat.remoteip.remote-ip-header=x-your-remote-ip-header
server.tomcat.remoteip.protocol-header=x-your-protocol-header
server:
tomcat:
remoteip:
remote-ip-header: "x-your-remote-ip-header"
protocol-header: "x-your-protocol-header"
Tomcat还配置了一个正则表达式,用于匹配要信任的内部代理。有关其默认值,请参阅 附录中的 server.tomcat.remoteip.internal-proxies 条目。您可以通过向 application.properties 添加条目来自定义阀门的配置,如以下示例所示
-
属性
-
YAML
server.tomcat.remoteip.internal-proxies=192\.168\.\d{1,3}\.\d{1,3}
server:
tomcat:
remoteip:
internal-proxies: "192\\.168\\.\\d{1,3}\\.\\d{1,3}"
您可以通过将 internal-proxies 设置为空来信任所有代理(但在生产环境中不要这样做)。 |
您可以通过关闭自动配置(为此,将 server.forward-headers-strategy=NONE)并使用 WebServerFactoryCustomizer bean 添加新的阀门实例来完全控制Tomcat的 RemoteIpValve 的配置。
在Tomcat中启用多个连接器
您可以将 Connector 添加到 TomcatServletWebServerFactory,这可以允许多个连接器,包括HTTP和HTTPS连接器,如以下示例所示
-
Java
-
Kotlin
import org.apache.catalina.connector.Connector;
import org.springframework.boot.tomcat.servlet.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.addAdditionalConnectors(createConnector());
}
private Connector createConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(8081);
return connector;
}
}
import org.apache.catalina.connector.Connector
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyTomcatConfiguration {
@Bean
fun connectorCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
return WebServerFactoryCustomizer { tomcat: TomcatServletWebServerFactory ->
tomcat.addAdditionalConnectors(createConnector())
}
}
private fun createConnector(): Connector {
val connector = Connector("org.apache.coyote.http11.Http11NioProtocol")
connector.port = 8081
return connector
}
}
启用Tomcat的MBean注册表
嵌入式Tomcat的MBean注册表默认禁用。这最大程度地减少了Tomcat的内存占用。如果您想使用Tomcat的MBean,例如,以便它们可以被Micrometer用于公开指标,您必须使用 server.tomcat.mbeanregistry.enabled 属性来启用,如以下示例所示
-
属性
-
YAML
server.tomcat.mbeanregistry.enabled=true
server:
tomcat:
mbeanregistry:
enabled: true
使用@ServerEndpoint创建WebSocket端点
如果您想在使用了嵌入式容器的Spring Boot应用程序中使用 @ServerEndpoint,则必须声明一个 ServerEndpointExporter @Bean,如以下示例所示
-
Java
-
Kotlin
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();
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.socket.server.standard.ServerEndpointExporter
@Configuration(proxyBeanMethods = false)
class MyWebSocketConfiguration {
@Bean
fun serverEndpointExporter(): ServerEndpointExporter {
return ServerEndpointExporter()
}
}
前面示例中显示的bean将任何带有 @ServerEndpoint 注解的bean注册到底层WebSocket容器中。当部署到独立servlet容器时,此角色由servlet容器初始化器执行,并且不需要 ServerEndpointExporter bean。