启用 SSL 的 Spring Boot 阻塞连接池

smo*_*tic 5 java ssl spring spring-data-jpa spring-boot

我有一个使用自签名证书启用 SSL 的 Spring Boot 应用程序(版本 2.5.3)。一个端点用于使用 StreamingResponseBody 下载客户端中的文件。

问题

问题是,当用户通过此端点请求文件时,连接池不会被清理。在这里展示问题的工作示例:https : //github.com/smotastic/blocked-connection-pool

@GetMapping("/ping")
public ResponseEntity<StreamingResponseBody> ping() {
    // service.findSomeFile(...)
    StreamingResponseBody body = new StreamingResponseBody() {
        
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // outputStream.write(someFile);
        }
    };
    return ResponseEntity.ok(body);
}
Run Code Online (Sandbox Code Playgroud)

调用此方法并打开 Hikari 连接池的调试日志后,将记录以下内容。

Pool stats (total=10, active=1, idle=9, waiting=0)
Run Code Online (Sandbox Code Playgroud)

如您所见,一个连接处于活动状态,并且永远不会再次释放。如果我再调用此端点 9 次,则连接池已满,任何后续请求都将超时。

到目前为止我发现了什么

具有启用 ssl 的应用程序、对服务或存储库的调用以及 StreamingResponseBody 的返回值的组合在这里似乎是我的问题。一旦我关闭这些因素之一,问题就会消失并且连接会按预期释放。

// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().permitAll();
    http.requiresChannel().anyRequest().requiresSecure(); // turning this off 'solves' the problem
}
Run Code Online (Sandbox Code Playgroud)

在示例中,我提供了一个简单的 FooEntity 和一个 FooRepository,我只是在其中调用了 count 方法。我猜这会打开一个新事务并在此处引发错误。

// Controller.java
@Autowired
FooRepository fooRepo;

@GetMapping("/ping")
public ResponseEntity<StreamingResponseBody> ping() {
    fooRepo.count(); // removing this line 'solves' the error
    StreamingResponseBody body = new StreamingResponseBody() {
            
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
          // outStream.write(...)
        }
    };
    return ResponseEntity.ok(body);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,FooRepository 只是一个来自 Spring 的简单 CrudRepository,没有额外的功能。

// FooRepository.java
@Repository
public interface FooRepository extends CrudRepository<Foo, Long>{

}
Run Code Online (Sandbox Code Playgroud)

FooEntity 是一个带有 id 的简单实体。

// Foo.java
@Entity
public class Foo {

    @GeneratedValue
    @Id
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我使用了一个 h2 数据源,但我也用 postgres 尝试过。两者都不起作用。

# application.properties
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
Run Code Online (Sandbox Code Playgroud)

ssl 通过以下配置启用。

# https via self signed certificate
server.ssl.key-store-type=${SSL_KEY_STORE_TYPE:PKCS12}
# The path to the keystore containing the certificate
server.ssl.key-store=${SSL_KEY_STORE:classpath:identity.p12}
# The password used to generate the certificate
server.ssl.key-store-password=${SSL_KEY_STORE_PASSWORD:hello}
# The alias mapped to the certificate
server.ssl.key-alias=${SSL_KEY_ALIAS:mykey}
server.ssl.enabled=${SSL_ENABLED:true}
Run Code Online (Sandbox Code Playgroud)

该证书是在我的 Windows 10 机器上创建的自签名证书。

此外,Hikari 连接池似乎不是问题。我还尝试了 Tomcat JDBC 连接池。池仍然被阻塞。

# application.properties
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
Run Code Online (Sandbox Code Playgroud)