WebService中使用StreamingOutput仍遇代理超时的问题及适配需求
先来看你提供的WebService代码:
final StreamingOutput stream = new StreamingOutput() { @Override public void write(final OutputStream out) { dao.getData( query, new SdmxObserver(writerFactory.getDataWriter(sdmxFormat, out, properties), request .getRemoteAddr(), request.getHeader("User-Agent"), query.toString(), sdmxFormat .toString(), query.getlist())); } }; res = Response.ok(stream, MediaType.valueOf("application/vnd.sdmx.genericdata+xml;version=2.1")) .cacheControl(cc).lastModified(lastModified).header("Vary", "Accept,Accept-Encoding").build(); return res;
为什么StreamingOutput没起到保活作用?
核心问题在于:你虽然用了StreamingOutput的包装,但实际逻辑还是同步阻塞的——dao.getData()会先完整执行完数据库查询,直到所有数据都准备好后,才会开始往输出流写数据。而代理的超时判定是基于「是否有数据传输」,不是「连接是否存在」。前2分钟里输出流完全没有数据发往代理,它自然会判定连接超时并断开。
简单说:你只是套了流式输出的壳,但本质上还是在等所有数据准备好后才输出,真正的流式传输根本没启动。
无需修改代理的可行解决方案
既然不能调整代理设置,那就要让代理在等待数据库查询的过程中,能感知到连接是活跃的,或者真正实现边查边输出:
方案1:提前发送占位数据触发传输
利用响应是XML格式的特点,在执行数据库查询前,先往输出流写入XML的起始标签,并且立刻刷新缓冲区,让数据马上发送到代理端,维持连接存活。示例:@Override public void write(final OutputStream out) throws IOException { // 先写入XML开头,提前触发数据传输 out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><GenericData>".getBytes(StandardCharsets.UTF_8)); out.flush(); // 必须调用flush,否则数据会留在服务器缓冲区,不会立刻发送 // 再执行数据库查询与后续数据写入 dao.getData( query, new SdmxObserver(writerFactory.getDataWriter(sdmxFormat, out, properties), request .getRemoteAddr(), request.getHeader("User-Agent"), query.toString(), sdmxFormat .toString(), query.getlist())); // 最后补全XML结束标签 out.write("</GenericData>".getBytes(StandardCharsets.UTF_8)); out.flush(); }方案2:改造DAO实现真正的流式查询
如果你的数据库支持流式查询(比如JDBC可以设置setFetchSize(Integer.MIN_VALUE)开启流式获取),可以让dao.getData()每查询到一条数据就立刻写入输出流,而不是等所有数据查询完成后一次性输出。这样从查询开始就有持续的数据传输,代理的超时限制自然不会触发。方案3:尝试添加HTTP保活头(效果视代理而定)
可以在响应头里添加Connection: keep-alive和Keep-Alive: timeout=120, max=1000,不过这个方案的效果取决于代理是否识别并遵守这些头规则,优先级低于前两个方案。
为什么跳过代理能成功?
跳过代理时,客户端和你的服务直接连接,客户端的超时设置通常比代理的2分钟更长,或者客户端会一直等待直到数据开始传输,所以能正常拿到结果。但代理的超时是硬性限制,必须让它看到数据传输才能维持连接。
内容的提问来源于stack exchange,提问作者Subhajit Pal

