无法将内容类型标头添加到SSE端点

时间:2019-07-19 04:59:43

标签: java nginx jersey http-headers server-sent-events

我有一个Java服务器和一个JavaScript客户端,它们已经成功地使用服务器发送事件来发送更新。最近,我们被迫在使用内置于kubernetes中的NGINX作为服务器和客户端之间的代理之间切换,使用基于kong的代理,该代理以不同的配置扩展了NGINX。新的代理会缓冲SSE破坏协议,并且由于其他应用程序正在使用新的代理,因此我无法关闭所有缓冲,这是以前的代理解决该问题的方式。作为替代方案,我一直在尝试向SSE响应中添加3个http标头,以触发NGINX为该特定端点关闭缓冲:

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

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

以下是第一次尝试,请注意,“ Content-Type”标头的设置与RestServiceImpl中的其他两个标头相同,但是从我的日志中,添加了另外两个标头,而“ 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”标头的添加方式,如下所示:

@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”。

对于第三次尝试,我用ContainerResponse替换了HttpServletResponse

@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添加标头。

@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”标头添加到应用程序中的每个端点,但未在任何地方添加“ Content-Type”。请注意,其中一些其他api返回的响应对象允许设置“ Content-Type”,并且这些REST端点为每个特定端点正确设置了“ Content-Type”头。 SSE库返回EventOutput,不幸的是,它不允许我像Response那样设置Content-Type标头。

对于第五次尝试,我用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);
    }
}

此解决方案的工作原理与以前的相同,在该解决方案中,它向应用程序的所有端点添加了“缓存控制”和“ X-加速缓存”,但再次取消了“内容类型”。

总而言之,我似乎无法弄清楚是什么原因阻止了我向我的SSE端点成功添加“ Content-Type”标头。

此问题类似于: Why does Jersey swallow my "Content-Encoding" header 但是,给定的将“ Content-Type”添加到Response对象的解决方案不起作用,因为我使用的是SSE,该方法返回的EventOutput对象没有添加“ Content-Type”标头的方法。

1 个答案:

答案 0 :(得分:0)

事实证明,泽西岛不是未设置“ Content-Type”标头的罪魁祸首。该应用程序是作为连续部署环境的一部分而构建的,该环境针对日志记录和安全扫描之类的过滤器添加了许多过滤器。这些过滤器之一是防止使用SSE库时填充“ Content-Type”标头。

在本地计算机上运行该应用程序,然后在其基于kubernetes的开发环境中运行该应用程序,并看到本地第一个解决方案正确填充了“ Content-Type”标头之后,我能够确定这是原因。