通过HTTP

时间:2016-12-22 05:25:18

标签: http stream http-headers

我希望通过HTTP流式传输一组日志消息。我想一次发送一行消息,可能在行之间有延迟,我希望每一行在服务器发送后尽快显示在浏览器中。

我目前的做法是在响应中将Content-Type设置为text/plain; charset=UTF-8,然后根据需要从服务器开始直接启动流线。我确保在每次写入后刷新所有相关的输出流。

我在Chrome中观察到的行为是,它会等到响应完全结束后再显示任何内容。但我想要的行为是看到发送的每一行。这可能吗?

我已经发现了很多有关此主题的stackoverflow问题,但没有一个问题已经回答了我的问题。我认为Transfer-Encoding与我无关,因为这似乎是为了下载大文件(如果我错了,请纠正我)。

这是关于下载文件的问题,因为我希望直接在浏览器中呈现这些行。

1 个答案:

答案 0 :(得分:8)

我认为你无法完成“最快”的事情。这里的解决方案是由question and answer链接的Ivan中提到的问题。至少我的Chrome和Firefox 可以逐行呈现他们收到的最新内容,但是,如上所述,它需要黑客攻击或更改要求以使其更加透明。

这里要做的第一件事就是获取但是抑制第一个 n 字节,以便触发浏览器呈现。

如果使用text/plain,则只能依赖于特定浏览器如何呈现输出文本。为了抑制第一个虚拟块输出,你可以只渲染空格,因为它们不是由人或浏览器解析(至少我是这么认为的,因为你想要在浏览器中输出,因此可能不会使它成为机器 - 可解析)。这里的一个技巧是编写Unicode \u200Bzero width space),希望目标浏览器使用它在输出窗口中不呈现任何内容。不幸的是,我的Firefox实例无法识别该字符,并且确实呈现了默认的未知字符占位符。但是,Chrome完全忽略了这些角色,在视觉上它们看起来什么也没有!它似乎是你需要的。所以,这里的通用算法是:

  • 检测用户代理以确定标头块长度(您需要知道这些预定义值)。
  • 撰写UTF-8 BOM(0xEF0xBB0xBF)以确保Chrome won't start the download the remote output to a file
  • \u200B个字符 n 次,其中 n 在前一个项目中确定并刷新输出。
  • 生成一些带有暂停的虚拟内容,以便在每行 n 秒后立即刷新新的内容行。

但是,如果您希望没有像\u200B字符的Firefox一样的输出呈现问题,您可能希望切换到text/html。 HTML支持标记注释,因此我们可以排除某些内容的呈现。这允许完全依赖HTML,而不是特定的浏览器细节。知道了,算法变得有些不同了:

  • 检测用户代理以确定标头块长度。
  • 使用<!--渲染块的开头,然后渲染一些 n 空格(但至少有一个我记得的;或者任何HTML注释),然后{{1} }。 n 应该是上面块的长度减去注释开始/结束标记的长度。
  • 生成一些虚拟输出,其中每一行都是HTML转义的,以--><br/>终止,然后立即刷新。

这种方法在Chrome和Firefox中都能正常使用。如果您对某些Java不满意,可以使用以下代码来实现上述内容:

<br>

此外,您要完成的方法不会向下滚动浏览器窗口。您可能希望在Chrome中使用用户脚本自动向下滚动特定的网页,但据我所知,它不适用于@RestController @RequestMapping("/messages") public final class MessagesController { private static final List<String> lines = asList( "Lorem ipsum dolor sit amet,", "consectetur adipiscing elit,", "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." ); @RequestMapping(value = "html", method = GET, produces = "text/html") public void getHtml(final HttpServletRequest request, final ServletResponse response) throws IOException, InterruptedException { render(Renderers.HTML, request, response); } @RequestMapping(value = "text", method = GET, produces = "text/plain") public void getText(final HttpServletRequest request, final ServletResponse response) throws IOException, InterruptedException { render(Renderers.PLAIN, request, response); } private static void render(final IRenderer renderer, final HttpServletRequest request, final ServletResponse response) throws IOException, InterruptedException { final int stubLength = getStubLength(request); final ServletOutputStream outputStream = response.getOutputStream(); renderer.renderStub(stubLength, outputStream); renderInfiniteContent(renderer, outputStream); } private static int getStubLength(final HttpServletRequest request) { final String userAgent = request.getHeader("User-Agent"); if ( userAgent == null ) { return 0; } if ( userAgent.contains("Chrome") ) { return 1024; } if ( userAgent.contains("Firefox") ) { return 1024; } return 0; } private static void renderInfiniteContent(final IRenderer renderer, final ServletOutputStream outputStream) throws IOException, InterruptedException { for ( ; ; ) { for ( final String line : lines ) { renderer.renderLine(line, outputStream); sleep(5000); } } } private interface IRenderer { void renderStub(int length, ServletOutputStream outputStream) throws IOException; void renderLine(String line, ServletOutputStream outputStream) throws IOException; } private enum Renderers implements IRenderer { HTML { private static final String HTML_PREFIX = "<!-- "; private static final String HTML_SUFFIX = " -->"; private final int HTML_PREFIX_SUFFIX_LENGTH = HTML_PREFIX.length() + HTML_SUFFIX.length(); @Override public void renderStub(final int length, final ServletOutputStream outputStream) throws IOException { outputStream.print(HTML_PREFIX); for ( int i = 0; i < length - HTML_PREFIX_SUFFIX_LENGTH; i++ ) { outputStream.write('\u0020'); } outputStream.print(HTML_SUFFIX); outputStream.flush(); } @Override public void renderLine(final String line, final ServletOutputStream outputStream) throws IOException { outputStream.print(htmlEscape(line, "UTF-8")); outputStream.print("<br/>"); } }, PLAIN { private static final char ZERO_WIDTH_CHAR = '\u200B'; private final byte[] bom = { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF }; @Override public void renderStub(final int length, final ServletOutputStream outputStream) throws IOException { outputStream.write(bom); for ( int i = 0; i < length; i++ ) { outputStream.write(ZERO_WIDTH_CHAR); } outputStream.flush(); } @Override public void renderLine(final String line, final ServletOutputStream outputStream) throws IOException { outputStream.println(line); outputStream.flush(); } } } } 输出。