Embedded Web Servers

每个 Spring Boot Web 应用程序都包含一个嵌入式 Web 服务器。此功能导致了许多操作问题,包括如何更改嵌入式服务器以及如何配置嵌入式服务器。本节将回答这些问题。

Use Another Web Server

许多 Spring Boot starter 都包含默认的嵌入式容器。

  • 对于 servlet 堆栈应用程序,spring-boot-starter-web 通过包含 spring-boot-starter-tomcat 包含 Tomcat,但你可以改用 spring-boot-starter-jettyspring-boot-starter-undertow

  • 对于反应堆堆栈应用程序,spring-boot-starter-webflux 通过包含 spring-boot-starter-reactor-netty 包含 Reactor Netty,但你可以改用 spring-boot-starter-tomcatspring-boot-starter-jettyspring-boot-starter-undertow

切换至其他 HTTP 服务器时,你需要用自己需要的默认依赖项替换那些默认依赖项。Spring Boot 为每个受支持的 HTTP 服务器提供了一个单独的 starter,以帮助完成此过程。

以下 Maven 示例演示了如何排除 Tomcat 并添加 Jetty 以用于 Spring MVC:

<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 示例配置了必需的依赖项和一个 {url-gradle-docs}/resolution_rules.html#sec:module_replacement[模块替换] 以便使用 Undertow 替换 Reactor Netty 以用于 Spring WebFlux:

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 的依赖项。

Disabling the Web Server

如果类路径包含启动 Web 服务器的必需部分,Spring Boot 会自动启动它。要禁用此行为,可在 application.properties 中配置 WebApplicationType,如下例所示:

spring:
  main:
    web-application-type: "none"

Change the HTTP Port

在独立应用程序中,主要 HTTP 端口默认为 8080,但可以用 configprop:server.port[] 设置(例如在 application.properties 中或作为系统属性)。由于 Environment 值的约束已被弱化,你还可以使用 configprop:server.port[format=envvar](例如作为操作系统环境变量)。

要完全关闭 HTTP 端点但仍然创建 WebApplicationContext,请使用 server.port=-1(这样做有时对于测试很有用)。

有关更多详细信息,请参阅“Spring Boot 功能部分中的 “Customizing Embedded Servlet Containers” 或 {code-spring-boot-autoconfigure-src}/web/ServerProperties.java[ServerProperties] 源代码。

Use a Random Unassigned HTTP Port

要扫描空闲端口(使用操作系统本地方法来防止冲突),请使用 server.port=0

Discover the HTTP Port at Runtime

可以通过日志输出或通过其 WebServerWebServerApplicationContext 访问服务器正在运行的端口。最佳做法是添加类型为 ApplicationListener<WebServerInitializedEvent>@Bean,并在发布事件时将容器从中移除,以便获取端口并确保已初始化。

使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 的测试还可以通过使用 @LocalServerPort 注释将实际端口注入字段,如下例所示:

@LocalServerPort@Value("${local.server.port}") 的元注释。不要尝试在常规应用程序中注入端口。正如我们刚才所看到的,该值在容器初始化之后才设置。与测试相反,应用程序代码回调在早期进行处理(在值实际可用之前)。

Enable HTTP Response Compression

HTTP 响应压缩受 Jetty、Tomcat、Reactor Netty 和 Undertow 支持。它可以在 application.properties 中启用,如下所示:

server:
  compression:
    enabled: true

默认情况下,响应长度必须至少达到 2048 个字节才能执行压缩。你可以通过设置 configprop:server.compression.min-response-size[] 属性来配置此行为。

默认情况下,只有相应内容类型为以下类型之一时,才会对其进行压缩:

  • text/html

  • text/xml

  • text/plain

  • text/css

  • text/javascript

  • application/javascript

  • application/json

  • application/xml

你可以通过设置 configprop:server.compression.mime-types[] 属性来配置此行为。

Configure SSL

可以通过设置各种 server.ssl.* 属性来以声明的方式配置 SSL,通常在 application.propertiesapplication.yaml 中设置。以下示例演示了使用 Java KeyStore 文件设置 SSL 属性:

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 连接器更容易通过编程方式进行配置。

Using PEM-encoded files

您可以使用 PEM 编码文件,而不是 Java 密钥库文件。应尽可能使用 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
  ssl:
    certificate: "classpath:my-cert.crt"
    certificate-private-key: "classpath:my-cert.key"
    trust-certificate: "classpath:ca-cert.crt"

或者,SSL 信任材料可以在 SSL bundle 中进行配置,并如示例所示应用于 Web 服务器:

server:
  port: 8443
  ssl:
    bundle: "example"

server.ssl.bundle 属性不能与 server.ssl 下的离散 Java 密钥库或 PEM 属性选项结合使用。

有关所有受支持属性的详情,请参阅 {code-spring-boot-src}/web/server/Ssl.java[Ssl]。

Configure HTTP/2

您可以使用 configprop:server.http2.enabled[] 配置属性在 Spring Boot 应用程序中启用 HTTP/2 支持。h2(通过 TLS 进行的 HTTP/2)和 h2c(通过 TCP 进行的 HTTP/2)均受支持。要使用 h2,还必须启用 SSL。当 SSL 未启用时,将使用 h2c。例如,当您的应用程序是 running behind a proxy server 并执行 TLS 终结时,您可能希望使用 h2c

HTTP/2 With Tomcat

Spring Boot 默认与 Tomcat 10.1.x 捆绑,该 Tomcat 开箱即用地支持 h2ch2。或者,如果您在主机操作系统上安装了该库及其依赖项,可以使用 libtcnative 来获得 h2 支持。

如果尚未将库目录提供给 JVM 库路径,则必须提供它。可以使用 -Djava.library.path=/usr/local/opt/tomcat-native/lib 这样的 JVM 参数来实现此目的。有关此内容,请参阅 {url-tomcat-docs}/apr.html[官方 Tomcat 文档]。

HTTP/2 With 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-serverConscrypt library

HTTP/2 With Reactor Netty

spring-boot-webflux-starter 默认使用 Reactor Netty 作为服务器。Reactor Netty 开箱即用地支持 h2ch2。为了获得最佳运行时性能,此服务器还支持具有本机库的 h2。为了启用该库,您的应用程序需要具有附加的依赖项。

Spring Boot 管理用于 io.netty:netty-tcnative-boringssl-static 的“超级 jar”的版本,该“超级 jar”包含针对所有平台的本机库。开发人员可以选择仅使用分类器导入必需的依赖项(请参阅 the Netty official documentation)。

HTTP/2 With Undertow

Undertow 开箱即用地支持 h2ch2

Configure the Web Server

通常,您首先应考虑使用众多可用的配置密钥之一,并通过在 application.propertiesapplication.yaml 文件中添加新条目来自定义 Web 服务器。请参阅 “Discover Built-in Options for External Properties”)。server. namespace is quite useful here, and it includes namespaces like server.tomcat.server.jetty.* 和其他,用于与服务器相关的功能。请参阅 Common Application Properties 列表。

前面的章节已涵盖了许多常见的使用案例,例如压缩、SSL 或 HTTP/2。但是,如果不存在适用于您使用案例的配置密钥,那么您应该查看 WebServerFactoryCustomizer。您可以声明这样的组件,并获得针对您选择的合适的服务器工厂:您应该为所选的服务器(Tomcat、Jetty、Reactor Netty、Undertow)和所选的 Web 堆栈(servlet 或 reactive)选择变体。

以下示例适用于 Tomcat 和 spring-boot-starter-web(servlet 堆栈):

Spring Boot 在内部使用该基础设施来自动配置服务器。自动配置的 WebServerFactoryCustomizer Bean 具有 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 提供的 Bean。当您这样做时,自动配置的定制器仍会被应用到您的自定义工厂中,因此要小心使用该选项。

Add a Servlet, Filter, or Listener to an Application

在 servlet 堆栈应用程序中,即带有 spring-boot-starter-web 的,有两种方法可以向您的应用程序添加 ServletFilterServletContextListener 以及 Servlet API 支持的其他侦听器:

Add a Servlet, Filter, or Listener by Using a Spring Bean

要使用 Spring Bean 添加 ServletFilter 或 Servlet *Listener,您必须提供其 @Bean 定义。当您想注入配置或依赖关系时,这样做非常有用。但是,您必须非常小心,以避免过早地初始化许多其他 Bean,因为它们必须在应用程序生命周期的早期安装到容器中。(例如,让他们依赖您的 DataSource 或 JPA 配置不是一个好主意。)您可以在首次使用时而不是在初始化时懒惰地初始化 Bean 来解决此类限制。

在过滤器和 servlet 的情况下,您还可以添加映射和初始化参数,方法是添加 FilterRegistrationBeanServletRegistrationBean,而不是或除了基础组件。

如果过滤器注册中未指定 dispatcherType,则使用 REQUEST。这与 servlet 规范的默认调度程序类型一致。

与任何其他 Spring Bean 一样,您也可以定义 servlet 过滤器 Bean 的顺序;请务必查看 “Registering Servlets, Filters, and Listeners as Spring Beans” 部分。

Disable Registration of a Servlet or Filter

described earlier 一样,任何 ServletFilter Bean 都将自动注册到 servlet 容器中。要禁用特定 FilterServlet Bean 的注册,请为其创建注册 Bean 并将其标记为禁用,如下例所示:

Add Servlets, Filters, and Listeners by Using Classpath Scanning

@WebServlet@WebFilter@WebListener 注释类可以通过使用 @ServletComponentScan 注释 @Configuration 类并指定要注册的组件所包含的包来自动注册到嵌入式 servlet 容器中。默认情况下,@ServletComponentScan 从注释类的包进行扫描。

Configure Access Logging

可以通过其各自的名称空间为 Tomcat、Undertow 和 Jetty 配置访问日志。

例如,以下设置使用 {url-tomcat-docs}/config/valve.html#Access_Logging[自定义模式] 在 Tomcat 上记录访问。

server:
  tomcat:
    basedir: "my-tomcat"
    accesslog:
      enabled: true
      pattern: "%t %a %r %s (%D microseconds)"

日志的默认位置是 Tomcat 基本目录相对路径的 logs 目录。默认情况下,logs 目录是一个临时目录,因此您可能希望修复 Tomcat 的基本目录或使用日志的绝对路径。在前面的示例中,日志在应用程序工作目录相对路径的 my-tomcat/logs 中提供。

Undertow 的访问日志记录可以按照类似的方式进行配置,如下例所示:

server:
  undertow:
    accesslog:
      enabled: true
      pattern: "%t %a %r %s (%D milliseconds)"
    options:
      server:
        record-request-start-time: true

请注意,除了启用访问日志记录和配置其模式外,还启用了记录请求开始时间。在访问日志模式中包含响应时间 (%D) 时需要这样做。日志存储在应用程序工作目录相对路径的 logs 目录中。您可以通过设置 configprop:server.undertow.accesslog.dir[] 属性来自定义此位置。

最后,还可以如下配置 Jetty 的访问日志记录:

server:
  jetty:
    accesslog:
      enabled: true
      filename: "/var/log/jetty-access.log"

默认情况下,会将日志重定向至 System.err。有关更多详细信息,请参阅 Jetty 文档。

Running Behind a Front-end Proxy Server

如果你的应用程序在代理、负载均衡器或云端后面运行,请求信息(如主机、端口、方案……)可能会发生更改。你的应用程序可能在 10.10.10.10:8080 上运行,但 HTTP 客户端只能看到 example.org

RFC7239 "Forwarded Headers" 定义了 Forwarded HTTP 标头;代理可使用此标头提供有关原始请求的信息。你可以配置应用程序来读取这些标头,并在 HTTP 302 响应、JSON 文档或 HTML 页面中创建链接并将其发送给客户端时自动使用该信息。还有一些非标准标头,如 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefix

如果代理添加常用的 X-Forwarded-ForX-Forwarded-Proto 标头,将 server.forward-headers-strategy 设置为 NATIVE 足以支持这些标头。通过此选项,Web 服务器本身会本机化支持此功能;你可以查阅其特定文档,了解具体行为。

如果这还不够,Spring Framework 会为 servlet 堆栈提供一个 {url-spring-framework-docs}/web/webmvc/filters.html#filters-forwarded-headers[ForwardedHeaderFilter],为响应式堆栈提供一个 {url-spring-framework-docs}/web/webflux/reactive-spring.html#webflux-forwarded-headers[ForwardedHeaderTransformer]。你可以将它们用于你的应用程序,方法是将 configprop:server.forward-headers-strategy[] 设置为 FRAMEWORK

如果你正在使用 Tomcat 并终止代理处的 SSL,则应该将 configprop:server.tomcat.redirect-context-root[] 设置为 false。这使得在执行任何重定向之前,可以处理 X-Forwarded-Proto 标头。

如果你的应用程序在 Cloud Foundry、Heroku 或 Kubernetes 中运行,configprop:server.forward-headers-strategy[] 属性默认为 NATIVE。在所有其他情况下,其默认为 NONE

Customize Tomcat’s Proxy Configuration

如果你使用 Tomcat,你可以另外配置用于承载 “forwarded” 信息的标头的名称,如下面的示例所示:

server:
  tomcat:
    remoteip:
      remote-ip-header: "x-your-remote-ip-header"
      protocol-header: "x-your-protocol-header"

Tomcat 还会配置一个用于匹配要信任的内部代理的正则表达式。有关其默认值,请参阅附录中的 configprop: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 添加新阀值实例。

Enable Multiple Connectors with Tomcat

你可以向 TomcatServletWebServerFactory 中添加一个 org.apache.catalina.connector.Connector,该 org.apache.catalina.connector.Connector 可以允许多个连接器,包括 HTTP 和 HTTPS 连接器,如下面的示例所示:

Enable Tomcat’s MBean Registry

默认情况下,禁用 Embedded Tomcat 的 MBean 注册表。这会最大程度减少 Tomcat 的内存占用。如果你希望使用 Tomcat 的 MBean(例如以使 Micrometer 可以使用它们来公开指标),你必须使用 configprop:server.tomcat.mbeanregistry.enabled[] 属性来执行此操作,如下面的示例所示:

server:
  tomcat:
    mbeanregistry:
      enabled: true

Enable Multiple Listeners with Undertow

UndertowServletWebServerFactory 中添加一个 UndertowBuilderCustomizer,并向 Builder 中添加一个侦听器,如下面的示例所示:

Create WebSocket Endpoints Using @ServerEndpoint

如果你希望在使用嵌入式容器的 Spring Boot 应用程序中使用 @ServerEndpoint,你必须声明单个 ServerEndpointExporter @Bean,如下面的示例所示:

在前面的示例中显示的 bean 会使用底层 WebSocket 容器注册任何带 @ServerEndpoint 注释的 bean。当部署到独立的 servlet 容器时,servlet 容器初始化程序会执行此角色,不需要 ServerEndpointExporter bean。