嵌入式 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。