78. 嵌入式 Web 服务器

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

78.1 使用其他 Web 服务器

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

  • 对于 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 服务器提供了单独的启动器,以帮助简化此过程。

以下 Maven 示例显示了如何排除 Tomcat 并包括 Jetty for Spring MVC:

<properties>
	<servlet-api.version>3.1.0</servlet-api.version>
</properties>
<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>

Note

Servlet API 的版本已被覆盖,因为与 Tomcat 9 和 Undertow 2.0 不同,Jetty 9.4 不支持 Servlet 4.0.

以下 Gradle 示例显示了如何排除 Netty 并为 Spring WebFlux 包括 Undertow:

configurations {
	// exclude Reactor Netty
	compile.exclude module: 'spring-boot-starter-reactor-netty'
}

dependencies {
	compile 'org.springframework.boot:spring-boot-starter-webflux'
	// Use Undertow instead
	compile 'org.springframework.boot:spring-boot-starter-undertow'
	// ...
}

Note

必须使用spring-boot-starter-reactor-netty才能使用WebClient类,因此即使需要包含其他 HTTP 服务器,也可能需要保持对 Netty 的依赖。

78.2 禁用 Web 服务器

如果您的 Classpath 包含启动 Web 服务器所需的位,则 Spring Boot 将自动启动它。要禁用此行为,请在application.properties中配置WebApplicationType,如以下示例所示:

spring.main.web-application-type=none

78.3 更改 HTTP 端口

在独立应用程序中,主 HTTP 端口默认为8080,但可以设置为server.port(例如,在application.properties中或作为系统属性)。得益于Environment值的轻松绑定,您还可以使用SERVER_PORT(例如,作为 OS 环境变量)。

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

有关更多详细信息,请参阅“ Spring Boot 功能”部分中的“ 第 28.4.4 节“自定义嵌入式 Servlet 容器””或ServerProperties源代码。

78.4 使用随机未分配的 HTTP 端口

要扫描可用端口(使用 os 本机来防止冲突),请使用server.port=0

78.5 在运行时发现 HTTP 端口

您可以从日志输出或ServletWebServerApplicationContext通过其WebServer访问服务器正在运行的端口。最好的方法是确保它已初始化,是添加类型为ApplicationListener<ServletWebServerInitializedEvent>@Bean,并在事件发布时将其从事件中拉出。

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

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyWebIntegrationTests {

	@Autowired
	ServletWebServerApplicationContext server;

	@LocalServerPort
	int port;

	// ...

}

Note

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

78.6 启用 HTTP 响应压缩

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

server.compression.enabled=true

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

默认情况下,仅当响应的 Content Type 为以下之一时,它们才被压缩:

  • text/html

  • text/xml

  • text/plain

  • text/css

  • text/javascript

  • application/javascript

  • application/json

  • application/xml

您可以通过设置server.compression.mime-types属性来配置此行为。

78.7 配置 SSL

可以通过设置各种server.ssl.*属性(通常在application.propertiesapplication.yml中)来声明性地配置 SSL。以下示例显示了在application.properties中设置 SSL 属性:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret

有关所有受支持属性的详细信息,请参见Ssl

使用前面示例中的配置意味着应用程序不再在端口 8080 上支持纯 HTTP 连接器。SpringBoot 不支持通过application.properties配置 HTTP 连接器和 HTTPS 连接器。如果要同时拥有两者,则需要以编程方式配置它们之一。我们建议使用application.properties来配置 HTTPS,因为 HTTP 连接器在两者中以编程方式进行配置比较容易。有关示例,请参见spring-boot-sample-tomcat-multi-connectors示例项目。

78.8 配置 HTTP/2

您可以使用server.http2.enabled配置属性在 Spring Boot 应用程序中启用 HTTP/2 支持。这种支持取决于所选的 Web 服务器和应用程序环境,因为 JDK8 不立即支持该协议。

Note

Spring Boot 不支持h2c,即 HTTP/2 协议的明文版本。因此,您必须首先配置 SSL

具有 Undertow 的 78.8.1 HTTP/2

从 Undertow 1.4.0 开始,在 JDK8 上无需任何其他要求即可支持 HTTP/2.

78.8.2 使用 Jetty 的 HTTP/2

从 Jetty 9.4.8 开始,Conscrypt library还支持 HTTP/2.要启用该支持,您的应用程序需要具有两个附加依赖项:org.eclipse.jetty:jetty-alpn-conscrypt-serverorg.eclipse.jetty.http2:http2-server

带有 Tomcat 的 78.8.3 HTTP/2

默认情况下,Spring Boot 随 Tomcat 9.0.x 一起提供,当使用 JDK 9 或更高版本时,Tomcat 9.0.x 支持 HTTP/2.另外,如果libtcnative库及其依赖项已安装在主机 os 上,则可以在 JDK 8 上使用 HTTP/2.

如果没有,则必须使库文件夹可用于 JVM 库路径。您可以使用 JVM 参数(例如-Djava.library.path=/usr/local/opt/tomcat-native/lib)来执行此操作。有关更多信息,请参见Tomcat 官方文档

在没有该本机支持的情况下,在 JDK 8 上启动 Tomcat 9.0.x 会记录以下错误:

ERROR 8787 --- [           main] o.a.coyote.http11.Http11NioProtocol      : The upgrade handler [org.apache.coyote.http2.Http2Protocol] for [h2] only supports upgrade via ALPN but has been configured for the ["https-jsse-nio-8443"] connector that does not support ALPN.

此错误不是致命错误,并且该应用程序仍以 HTTP/1.1 SSL 支持开头。

具有 Reactor Netty 的 78.8.4 HTTP/2

spring-boot-webflux-starter默认使用 Reactor Netty 作为服务器。使用 JDK 9 或更高版本的 JDK 支持,可以将 Reactor Netty 配置为 HTTP/2.对于 JDK 8 环境或最佳运行时性能,此服务器还支持带有本机库的 HTTP/2.为此,您的应用程序需要具有其他依赖项。

Spring Boot Managementio.netty:netty-tcnative-boringssl-static“超级 jar”的版本,其中包含所有平台的本机库。开发人员可以选择使用分类器仅导入所需的依赖项(请参见Netty 官方文档)。

78.9 配置 Web 服务器

通常,您应该首先考虑使用许多可用的配置密钥之一,并通过在application.properties(或application.yml或环境等,请参见“ 第 77.8 节“发现外部属性的内置选项””)中添加新条目来自定义 Web 服务器。 server.*命名空间在这里非常有用,它包括server.tomcat.*server.jetty.*等名称空间,用于特定于服务器的功能。请参阅附录 A,通用应用程序属性的列表。

前面的部分已经介绍了许多常见的用例,例如压缩,SSL 或 HTTP/2.但是,如果您的用例不存在配置密钥,则应查看WebServerFactoryCustomizer。您可以声明一个这样的组件并访问与您选择的服务器相关的工厂:您应该为所选服务器(Tomcat,Jetty,Reactor Netty,Undertow)和所选 Web 堆栈(Servlet 或 Reactive)选择变体。

以下示例适用于具有spring-boot-starter-web(Servlet 堆栈)的 Tomcat:

@Component
public class MyTomcatWebServerCustomizer
		implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

	@Override
	public void customize(TomcatServletWebServerFactory factory) {
		// customize the factory here
	}
}

此外,Spring Boot 还提供:

ServerServlet stackReactive stack
TomcatTomcatServletWebServerFactoryTomcatReactiveWebServerFactory
JettyJettyServletWebServerFactoryJettyReactiveWebServerFactory
UndertowUndertowServletWebServerFactoryUndertowReactiveWebServerFactory
ReactorN/ANettyReactiveWebServerFactory

获得WebServerFactory的访问权限后,通常可以向其添加定制程序以配置特定的部分,例如连接器,服务器资源或服务器本身-全部使用服务器特定的 API。

作为最后的选择,您还可以声明自己的WebServerFactory组件,它将覆盖 Spring Boot 提供的组件。在这种情况下,您不能再依赖server名称空间中的配置属性。

78.10 将 Servlet,过滤器或侦听器添加到应用程序

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

78.10.1 使用 Spring Bean 添加 Servlet,过滤器或侦听器

要使用 Spring bean 添加ServletFilter或 Servlet *Listener,您必须为其提供@Bean定义。当您要注入配置或依赖项时,这样做非常有用。但是,您必须非常小心,以免引起过多其他 bean 的急切初始化,因为必须在应用程序生命周期的早期就将它们安装在容器中。 (例如,让它们依赖于您的DataSource或 JPA 配置不是一个好主意.)您可以通过在首次使用 Bean 时(而不是在初始化时)延迟初始化 Bean 来解决这些限制。

对于FiltersServlets,您还可以通过添加FilterRegistrationBeanServletRegistrationBean来代替基础组件或在基础组件之外添加 Map 和 init 参数。

Note

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

像其他任何 Spring bean 一样,您可以定义 Servlet 过滤器 bean 的 Sequences。请确保选中“ 名为“将 Servlet,过滤器和侦听器注册为 Spring Bean”的部分”部分。

禁用 Servlet 或过滤器的注册

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

@Bean
public FilterRegistrationBean registration(MyFilter filter) {
	FilterRegistrationBean registration = new FilterRegistrationBean(filter);
	registration.setEnabled(false);
	return registration;
}

78.10.2 使用 Classpath 扫描添加 Servlet,过滤器和侦听器

通过使用@ServletComponentScanComments@Configuration类并指定包含要注册的组件的包,可以自动将@WebServlet@WebFilter@WebListenerComments 的类注册到嵌入式 servlet 容器中。默认情况下,@ServletComponentScan从带 Comments 的类的包中扫描。

78.11 配置访问日志

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

例如,以下设置使用custom pattern记录对 Tomcat 的访问。

server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)

Note

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

可以用类似的方式配置 Undertow 的访问日志,如以下示例所示:

server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a "%r" %s (%D ms)

日志存储在相对于应用程序工作目录的logs目录中。您可以通过设置server.undertow.accesslog.directory属性来自定义此位置。

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

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

默认情况下,日志重定向到System.err。有关更多详细信息,请参见Jetty 文件

78.12 在前端代理服务器后面运行

您的应用程序可能需要发送302重定向或使用绝对链接将内容渲染回自身。当在代理后面运行时,调用者需要链接到代理,而不要链接到托管您的应用的计算机的物理地址。通常,此类情况是通过与代理之间的 Contract 来处理的,该代理添加了 Headers,以告诉后端如何构造与自身的链接。

如果代理添加了常规的X-Forwarded-ForX-Forwarded-ProtoHeaders(大多数代理服务器都这样做),则在application.properties中将server.use-forward-headers设置为true的情况下,应正确渲染绝对链接。

Note

如果您的应用程序在 Cloud Foundry 或 Heroku 中运行,则server.use-forward-headers属性默认为true。在所有其他情况下,它默认为false

78.12.1 自定义 Tomcat 的代理配置

如果使用 Tomcat,则可以另外配置用于携带“转发的”信息的 Headers 名称,如以下示例所示:

server.tomcat.remote-ip-header=x-your-remote-ip-header
server.tomcat.protocol-header=x-your-protocol-header

Tomcat 还配置有默认正则表达式,该正则表达式与要信任的内部代理匹配。默认情况下,10/8192.168/16169.254/16127/8中的 IP 地址是受信任的。您可以通过在application.properties上添加一个条目来定制阀门的配置,如以下示例所示:

server.tomcat.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}

Note

仅当使用属性文件进行配置时,才需要双反斜杠。如果使用 YAML,则单个反斜杠就足够了,并且与上一个示例中所示的值相等的值为192\.168\.\d{1,3}\.\d{1,3}

Note

您可以通过将internal-proxies设置为空来信任所有代理(但在生产环境中不要这样做)。

您可以完全关闭 Tomcat RemoteIpValve的配置,方法是关闭自动开关(为此,设置server.use-forward-headers=false)并在TomcatServletWebServerFactory bean 中添加新的 Valve 实例。

78.13 使用 Tomcat 启用多个连接器

您可以将org.apache.catalina.connector.Connector添加到TomcatServletWebServerFactory,这可以允许多个连接器,包括 HTTP 和 HTTPS 连接器,如以下示例所示:

@Bean
public ServletWebServerFactory servletContainer() {
	TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
	tomcat.addAdditionalTomcatConnectors(createSslConnector());
	return tomcat;
}

private Connector createSslConnector() {
	Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
	Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
	try {
		File keystore = new ClassPathResource("keystore").getFile();
		File truststore = new ClassPathResource("keystore").getFile();
		connector.setScheme("https");
		connector.setSecure(true);
		connector.setPort(8443);
		protocol.setSSLEnabled(true);
		protocol.setKeystoreFile(keystore.getAbsolutePath());
		protocol.setKeystorePass("changeit");
		protocol.setTruststoreFile(truststore.getAbsolutePath());
		protocol.setTruststorePass("changeit");
		protocol.setKeyAlias("apitester");
		return connector;
	}
	catch (IOException ex) {
		throw new IllegalStateException("can't access keystore: [" + "keystore"
				+ "] or truststore: [" + "keystore" + "]", ex);
	}
}

78.14 使用 Tomcat 的 LegacyCookieProcessor

默认情况下,Spring Boot 使用的嵌入式 Tomcat 不支持 Cookie 格式的“版本 0”,因此您可能会看到以下错误:

java.lang.IllegalArgumentException: An invalid character [32] was present in the Cookie value

如果有可能,您应该考虑将代码更新为仅存储符合以后 Cookie 规范的值。但是,如果无法更改 cookie 的编写方式,则可以将 Tomcat 配置为使用LegacyCookieProcessor。要切换到LegacyCookieProcessor,请使用添加了TomcatContextCustomizerWebServerFactoryCustomizer bean,如以下示例所示:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() {
	return (factory) -> factory.addContextCustomizers(
			(context) -> context.setCookieProcessor(new LegacyCookieProcessor()));
}

78.15 使用 Undertow 启用多个侦听器

UndertowBuilderCustomizer添加到UndertowServletWebServerFactory并将侦听器添加到Builder,如以下示例所示:

@Bean
public UndertowServletWebServerFactory servletWebServerFactory() {
	UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
	factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {

		@Override
		public void customize(Builder builder) {
			builder.addHttpListener(8080, "0.0.0.0");
		}

	});
	return factory;
}

78.16 使用@ServerEndpoint 创建 WebSocket 端点

如果要在使用嵌入式容器的 Spring Boot 应用程序中使用@ServerEndpoint,则必须声明一个ServerEndpointExporter @Bean,如以下示例所示:

@Bean
public ServerEndpointExporter serverEndpointExporter() {
	return new ServerEndpointExporter();
}

前面示例中显示的 Bean 在基础 WebSocket 容器中注册了所有带有@ServerEndpointComments 的 Bean。当部署到独立 servlet 容器时,此角色由 servlet 容器初始化程序执行,并且不需要ServerEndpointExporter bean。