2016-12-22 6 views
7

Я хочу передать набор сообщений журнала через HTTP. Я хочу отправлять сообщения по одной строке за раз, возможно с задержками между строками, и я хочу, чтобы каждая строка отображалась в браузере как можно скорее после отправки сервера.потоковый текстовый текст по HTTP

Мой текущий подход состоит в том, чтобы установить Content-Type в text/plain; charset=UTF-8 в ответ и просто начать потоковые линии с задержками между ними при необходимости с сервера. После каждой записи я должен очистить все соответствующие выходные потоки.

Поведение, которое я наблюдаю в Chrome, заключается в том, что он ждет, пока ответ не будет полностью закончен, прежде чем показывать что-либо. Но поведение, которое я хочу, - видеть каждую строку по мере ее отправки. Это возможно?

Я столкнулся с многочисленными вопросами stackoverflow по этой теме, но никто не ответил на мой вопрос. Я не думаю, что Transfer-Encoding имеет отношение ко мне, потому что это похоже на загрузку больших файлов (исправьте меня, если я ошибаюсь).

Это не вопрос вопрос о загрузке файлов, потому что я хочу, чтобы строки отображались непосредственно в браузере.

+0

Я по-прежнему проверю, как будет работать chunked/transfer-encoding для вас и хром. В противном случае, вы изучили веб-сокеты и потоковое вещание? – Ivan

+0

Да, я знаком с websockets, но надеялся найти более простое решение для простой текстовой потоковой передачи. Что касается chunked transfer-encoding, вы знаете, могу ли я использовать это для обычного текстового потока, и знаете ли вы, будут ли браузеры показывать поток в реальном времени? –

+1

Я предполагаю, что это должно сработать, но вам придется тестировать, поскольку пробег может отличаться. Например. см. [сколько данных должно быть отправлено до начала рендеринга браузеров (примеры текста/html, image/jpeg)] (http://stackoverflow.com/questions/16909227/using-transfer-encoding-chunked-how-much-data- must-be-sent-before-browsers-s/16909228 # 16909228) – Ivan

ответ

8

Я не думаю, что вы можете выполнить «правильное» решение здесь из-за проблем, упомянутых в 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.

 Смежные вопросы

  • Нет связанных вопросов^_^