You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Stream SSE供应商仅返回初始消息无后续数据问题排查

问题:Spring Cloud Stream SSE供应商仅收到初始消息无后续推送

我尝试用以下Kotlin代码创建Spring Cloud Stream SSE供应商:

@Bean
fun sseSupplier(sseSupplierPub: Publisher<Message<Any>>): Supplier<Flux<Message<Any>>> {
    return Supplier {
        Flux.from(sseSupplierPub)
    }
}

@Bean
fun sseSupplierPub(
    sseSupplierProperties: sseSupplierProperties
): Publisher<Message<Any>> {
    return IntegrationFlow.from(channelsConfiguration.trigger())
        .enrichHeaders {
            it.header(CONTENT_TYPE, "application/json")
            it.header(ACCEPT, "text/event-stream")
        }
        .handle(sseMessageHandlerSpec())
        .log()
        .toReactivePublisher(true)
}

fun webClient(): WebClient {
    val client = HttpClient.create(provider)
        .keepAlive(true)

    val clientRegistryRepo = InMemoryReactiveClientRegistrationRepository(
        ClientRegistration
            .withRegistrationId("clientId")
            .tokenUri("https://openid-connect/token")
            .clientId("clientId")
            .clientSecret("clientSecret")
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .build())
    val clientService = InMemoryReactiveOAuth2AuthorizedClientService(clientRegistryRepo)
    val authorizedClientManager =
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistryRepo, clientService)
    val oauthFilter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
    oauthFilter.setDefaultClientRegistrationId("clientId")

    return WebClient.builder()
        .filter(oauthFilter)
        .clientConnector(ReactorClientHttpConnector(client))
        .exchangeStrategies(strategies)
        .build()
}

fun sseMessageHandlerSpec(): WebFluxMessageHandlerSpec {
    return WebFlux.outboundGateway(
        "https://api/src", webClient())
        .httpMethod(HttpMethod.POST)
        .replyPayloadToFlux(true)
        .expectedResponseType(typeReference<ServerSentEvent<String>>())
}

@Bean
fun commandLineRunner(ctx: ApplicationContext?): CommandLineRunner {
    return CommandLineRunner { _ ->
        val message = MessageBuilder.withPayload("{}").build()
        logger.info { "Trigger SSE supplier" }
        channelsConfiguration.trigger().send(message)
    }
}

目前已成功获取授权令牌并连接到https://api/src,但仅收到以下初始消息后无后续推送:

{
    "scanAvailable": true,
    "prefetch": -1
}

可能的原因及排查方向

1. SSE响应格式与内容类型不匹配

虽然请求头设置了ACCEPT: text/event-stream,但需确认服务端返回的响应头Content-Type是否为text/event-stream。若服务端返回application/json,WebClient会将整个响应解析为单条JSON对象,而非持续的SSE事件流。

可在WebClient中添加日志拦截器验证响应头:

// 在WebClient.builder()中追加
.filter(ExchangeFilterFunction.ofResponseProcessor { response ->
    logger.info("Response Content-Type: ${response.headers().contentType()}")
    Mono.just(response)
})

2. 服务端SSE事件格式不符合标准

标准SSE事件需以data:开头,多段数据用换行分隔,最终以\n\n结尾。若服务端返回的初始消息不是标准SSE格式,WebClient可能无法识别后续事件,直接终止流。

用curl直接请求服务端验证格式:

curl -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: text/event-stream" -X POST https://api/src -d "{}"

3. 连接被提前关闭

  • 服务端超时关闭:部分SSE服务在无事件推送时会超时断连,需确认服务端超时配置,是否需要客户端发送心跳包维持连接。
  • 客户端配置缺失:虽设置了keepAlive(true),但缺少超时参数可能导致连接提前断开,可补充配置:
val client = HttpClient.create(provider)
    .keepAlive(true)
    .responseTimeout(Duration.ofMinutes(30))
    .doOnConnected { conn ->
        conn.addHandlerLast(ReadTimeoutHandler(30, TimeUnit.MINUTES))
    }

4. Integration Flow订阅逻辑问题

toReactivePublisher(true)是否正确处理了SSE无限流?若下游订阅是一次性消费,会在收到第一条消息后终止订阅。需确认Supplier<Flux<Message<Any>>>的订阅逻辑是持续监听SSE流,而非仅消费一次。

另外,channelsConfiguration.trigger().send(message)是同步触发,需确认触发后的流是否被持续订阅。

5. 服务端触发条件未满足

部分SSE服务需要特定请求参数或后续交互才会推送后续事件。你发送的空{} payload可能仅触发了初始响应,未满足后续推送条件,需核对服务端文档确认请求要求。


内容的提问来源于stack exchange,提问作者user23087860

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:57:51