无法将 Content-Type Header 添加到 SSE 端点

Unable to add Content-Type Header to SSE endpoint

我有一个 Java 服务器和一个 Java 脚本客户端,它们已成功使用服务器 Sent-Events 发送更新。最近,我们被迫在使用 kubernetes 内置的 NGINX 作为服务器和客户端之间的代理与使用基于 kong 的代理之间切换,该代理使用不同的配置扩展 NGINX。新代理缓冲 SSE 通过它打破协议,并且因为其他应用程序正在使用新代理我不允许关闭所有缓冲,这是以前代理解决问题的方式。作为替代方案,我一直在尝试向 SSE 响应添加 3 个 http headers 以触发 NGINX 关闭此特定端点的缓冲:

"Content-Type" : "text/event-stream"
"Cache-Control", "no-cache"
"X-Accel-Buffering", "no"

我已经能够相对轻松地添加 "Cache-Control" 和 "X-Accel-Buffering" header,但是我尝试了几种不同的方法来添加 "Content-Type" header 没有任何效果。

下面是第一次尝试,注意 "Content-Type" header 与 RestServiceImpl 中的其他两个 header 设置相同,但从我的日志来看,其他两个 [=69=添加了 ] 而没有添加 "Content-Type"。

@Api
@Path("/service")
public interface RestService {

    @GET
    @Path("/sseconnect")
    @Produces(SseFeature.SERVER_SENT_EVENTS)
    EventOutput listenToBroadcast(@Context HttpServletResponse response);

}

@Component
public class RestServiceImpl implements RestService {

    @Autowired
    private Broadcaster broadcaster;

    public RestServiceImpl() {
    }

    @Override
    public EventOutput listenToBroadcast(HttpServletResponse response) {
        response.addHeader("Content-Type", "text/event-stream");
        response.addHeader("Cache-Control", "no-cache");
        response.addHeader("X-Accel-Buffering", "no");
        return broadcaster.add();
    }
}

public class Broadcaster extends SseBroadcaster {

    private final OutboundEvent.Builder eventBuilder =
        new OutboundEvent.Builder();

    public EventOutput add() {
        final EventOutput eventOutput = new EventOutput();
        super.add(eventOutput);
        return eventOutput;
    }

    public void sendEvents(Events events, String name) {
        final OutboundEvent outboundEvent = eventBuilder.name(name)
            .mediaType(MediaType.APPLICATION_JSON_TYPE)
            .data(Events.class, events).build();
        super.broadcast(outboundEvent);
    }
}

我第二次尝试修改 "Content-Type" header 的添加方式,如下所示:

@Override
public EventOutput listenToBroadcast(HttpServletResponse response) {
    response.setContentType("text/event-stream");
    response.addHeader("Cache-Control", "no-cache");
    response.addHeader("X-Accell-Buffering", no);
    return broadcaster.add();
}

这具有相同的效果,添加了 "Cache-Control" 和 "X-Accell-Bufferig",但没有添加 "Content-Type"。

对于第三次尝试,我将 HttpServletResponse 替换为 ContainerResponse

@Override
public EventOutput listenToBroadcast(ContainerResponse containerResponse)
{
    final MultivaluedMap<String, Object> headers =
        containerResponse.getHeaders();
    headers.add("Content-Type", "text/event-stream");
    headers.add("Cache-Control", "no-cache");
    headers.add("X-Accel-Buffering", "no");
    return broadcaster.add();
}

这个解决方案破坏了端点并最终给我一个 406 错误,所以我不能明确地说它不会起作用。可能是一个糟糕的实现,但我没有看到任何东西。

第四次尝试,我尝试添加带有 ContainerResponseFilter 的 header。

@Provider
@PreMatching
public class SseResponseFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext,
        ContainerResponseContext responseContext)
            throws IOException {

        final MultivaluedMap<String, Object> headers =
            responseContext.getHeaders();
        final List<Object> contentTypeValues = new ArrayList<Object>();
        contentTypeValues.add("text/event-stream");
        headers.put("Content-Type", contentTypeValues);

        final List<Object> cacheControlValues = new ArrayList<Object>();
        cacheControlValues.add("no-cache");
        headers.put("Cache-Control", cacheControlValues);

        final List<Object> xAccelBufferingValues =
            new ArrayList<Object>();
        xAccelBufferingValues.add("no");
        headers.put("X-Accel-Buffering", xAccelBufferingValues);
    }
}

此尝试将 "Cache-Control" 和 "X-Accel-Buffering" header 添加到应用程序中的每个端点,但没有在任何地方添加 "Content-Type"。请注意,其他一些 api 正在返回允许设置 "Content-Type" 的响应 object,并且这些 REST 端点正在获得 "Content-Type" header为每个特定端点正确设置。不幸的是,SSE 库 returns EventOutput 不允许我像 Response 那样设置 Content-Type header。

对于第五次尝试,我用 WriterInterceptor 替换了 ContainerResponseFilter

@Provider
public class SseWriterInterceptor implements WriterInterceptor {

    @Override
    public void arroundWriteTo(WriterInterceptorContext context)
        throws IOException, WebApplicationException {

        final MultivaluedMap<String, Object> headers =
            context.getHeaders();
        final List<Object> contentTypeValues = new ArrayList<Object>();
        contentTypeValues.add("text/event-stream");
        headers.put("Content-Type", contentTypeValues);

        final List<Object> cacheControlValues = new ArrayList<Object>();
        cacheControlValues.add("no-cache");
        headers.put("Cache-Control", cacheControlValues);

        final List<Object> xAccelBufferingValues =
            new ArrayList<Object>();
        xAccelBufferingValues.add("no");
        headers.put("X-Accel-Buffering", xAccelBufferingValues);
    }
}

此解决方案与之前的解决方案一样工作,它向应用程序中的所有端点添加了 "Cache-Control" 和 "X-Accel-Buffering",但再次停止了 "Content-Type"。

总而言之,我似乎无法弄清楚是什么阻止我将 "Content-Type" header 成功添加到我的 SSE 端点。

这个问题类似于: Why does Jersey swallow my "Content-Encoding" header 但是,将 "Content-Type" 添加到 Response object 的给定解决方案不起作用,因为我使用的 SSE returns 一个 EventOutput object 没有办法添加 "Content-Type" header.

事实证明,泽西岛并不是 "Content-Type" header 未设置的罪魁祸首。该应用程序是作为持续部署环境的一部分构建的,该环境为诸如日志记录和安全扫描之类的事情添加了许多过滤器。其中一个过滤器阻止 "Content-Type" header 在使用 SSE 库时被填充。

我能够确定这是 运行 我本地机器上的应用程序与 运行 它在基于 kubernetes 的开发环境中的应用程序并看到 "Content-Type" header 使用本地的第一个解决方案正确填充。