Я не думаю, что вы можете выполнить «правильное» решение здесь из-за проблем, упомянутых в question and answer, связанных Ivan. По крайней мере, мой Chrome и Firefox могут визуализировать самый последний контент, который они получают по строкам, без каких-либо усилий, но, как было сказано выше, ему нужны либо хаки, либо изменяются требования, чтобы сделать его более прозрачным.
Первое, что нужно сделать, это извлечь, но подавить первый ведущий n байт, чтобы вызвать рендеринг браузера.
Если вы идете с text/plain
, вы можете полагаться только на то, как выводимый текст отображается конкретным браузером. Чтобы подавить первый вывод фиктивного блока, вы можете просто визуализировать пробелы, поскольку они не должны анализироваться ни человеком, ни браузером (по крайней мере, я так думаю, потому что вы хотите получать в браузере, что, возможно, интерпретируемый). Трюк здесь заключается в написании Unicode \u200B
(zero width space), надеясь, что целевой браузер будет использовать его, ничего не отображая в окне вывода. К сожалению, мой экземпляр Firefox не распознает символ и не делает по умолчанию неизвестным символом. Однако Chrome полностью игнорирует эти символы, и визуально они выглядят как ничего! И кажется, что вам нужно. Итак, общий алгоритм здесь:
- Обнаружение пользовательского агента для определения длины блока заголовка (вам необходимо знать эти предопределенные значения).
- Напишите спецификацию UTF-8 (
0xEF
, 0xBB
, 0xBF
), чтобы убедиться, что Chrome won't start the download the remote output to a file.
- Написать
\u200B
характер п раз, где п определяется в предварительно предыдущем пункте и промывать выход.
- Создайте некоторое фиктивное содержимое с помощью пауз, чтобы получать новые строки контента. n секунд промывки сразу после каждой строки.
Однако, если вы хотите, чтобы не иметь никакого вывода рендеринга вопросы, как этот Firefox один для \u200B
характера, вы можете переключиться на text/html
. HTML поддерживает комментарии к разметке, поэтому мы можем исключить некоторый контент из визуализации. Это позволяет полностью полагаться на HTML, а не на специфику браузера. Зная это, алгоритм становится несколько иным:
- Обнаружить пользовательский агент, чтобы определить длину блока заголовка.
- Отметьте начало блока с
<!--
, затем некоторые n пробелы (но по крайней мере один, насколько я помню, или любой комментарий HTML), а затем . n должен быть длиной блока выше минус длины маркеров начала/окончания комментария.
- Создайте какой-нибудь фиктивный вывод, где каждая строка имеет HTML-экранирование, завершена
<br/>
или <br>
, а затем сразу же очищена.
Этот подход отлично подходит как для Chrome, так и для Firefox. Если вы хорошо с некоторыми Java, вот код, который реализует указанный выше:
@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();
}
}
}
}
Кроме того, подход вы хотите достичь не будет прокручивать окно браузера. Возможно, вы захотите использовать скрипт пользователя в Chrome, чтобы автоматически прокручивать страницы определенных URL-адресов, но, насколько я знаю, это не сработало бы для вывода text/plain
.
Я по-прежнему проверю, как будет работать chunked/transfer-encoding для вас и хром. В противном случае, вы изучили веб-сокеты и потоковое вещание? – Ivan
Да, я знаком с websockets, но надеялся найти более простое решение для простой текстовой потоковой передачи. Что касается chunked transfer-encoding, вы знаете, могу ли я использовать это для обычного текстового потока, и знаете ли вы, будут ли браузеры показывать поток в реальном времени? –
Я предполагаю, что это должно сработать, но вам придется тестировать, поскольку пробег может отличаться. Например. см. [сколько данных должно быть отправлено до начала рендеринга браузеров (примеры текста/html, image/jpeg)] (http://stackoverflow.com/questions/16909227/using-transfer-encoding-chunked-how-much-data- must-be-sent-before-browsers-s/16909228 # 16909228) – Ivan