Подтвердить что ты не робот

Как работает Server-Sent-Events

Я попробовал SSE (Server-Sent-Events) с помощью java на tomcat 8.0. Вот несколько вещей, которые я заметил.

Я нажимаю кнопку, которая автоматически делает запрос на сервлет. Выполняется метод GET сервлета, который возвращает поток событий. Как только полный поток будет получен, страница снова автоматически сделает другой запрос, который снова получит те же данные!!! У меня нет бесконечного цикла!

  • Что на самом деле происходит на сервере? В обычных сценариях tomcat создает поток для обработки каждого запроса. Что происходит сейчас?

  • Каков правильный способ гарантировать, что поток событий отправляется только один раз в тот же сеанс соединения/браузера?

  • Каков правильный способ убедиться, что поток событий закрыт, и на сервере не наложены служебные данные?

  • Как различать запросы GET и POST. Почему он выбрал GET?

  • Слишком рано использовать SSE на Tomcat? Любые проблемы с производительностью?

Вот код для любознательного,

@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //content type must be set to text/event-stream
        response.setContentType("text/event-stream"); 
        //cache must be set to no-cache
        response.setHeader("Cache-Control", "no-cache");     
        //encoding is set to UTF-8
        response.setCharacterEncoding("UTF-8");

        PrintWriter writer = response.getWriter();

        for(int i=0; i<10; i++) {
            System.out.println(i);
            writer.write("data: "+ i +"\n\n");
            writer.flush();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        writer.close(); 
    }
}

Javascript на странице (у меня больше ничего нет на странице),

<button onclick="start()">Start</button>

<script type="text/javascript">
    function start() {
        var eventSource = new EventSource("TestServlet");
        eventSource.onmessage = function(event) {
            console.log("data: "+event.data)
            document.getElementById('foo').innerHTML = event.data;
        };
    }
</script>

Пробовал это, используя CURL. И ответ пришел только один раз. Я использую хром, так что это должна быть проблема с chorme??

EDIT:

То, что я узнал и изучаю, теперь задокументировано в моем блоге - События, отправленные сервером

4b9b3361

Ответ 1

Измените эту строку

writer.write("data: "+ i +"\n\n");

к

writer.write("data: "+ i +"\r\n");

Кстати, ваш код будет иметь серьезную проблему с производительностью, потому что он будет удерживать поток до тех пор, пока не будут отправлены все события. Вместо этого используйте API асинхронной обработки. например.

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    AsyncContext actx = req.startAsync();
    actx.setTimeout(30*1000);
    //save actx and use it when we need sent data to the client.
}

Затем мы можем использовать AsyncContext позже

//write some data to client when a certain event happens
actx.getResponse().getWriter().write("data: " + mydata + "\r\n");
actx.getResponse().getWriter().flush();

если все отправленные события можно закрыть,

actx.complete();

ОБНОВЛЕНИЕ 1:

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

eventSource.close();

Возможно, помогает другой метод, а именно: мы установили довольно большое время повтора, но я не пробовал, например.

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    AsyncContext actx = req.startAsync();
    actx.getResponse().getWriter().write("retry: 36000000000\r\n"); // 10000 hours!
    actx.getResponse().getWriter().flush();
    //save actx and use it when we need sent data to the client.
}

ОБНОВЛЕНИЕ 2:

Я думаю, что Websocket, возможно, лучше для вашего дела.

ОБНОВЛЕНИЕ 3: (ответьте на вопросы)

  • Что на самом деле происходит на сервере? В обычных сценариях tomcat создает поток для обработки каждого запроса. Что происходит сейчас?

Если используется NIO-коннектор, который по умолчанию используется в Tomcat 8.0.X, в течение всего цикла обработки HTTP-ввод-вывод о запросе не будет содержать поток. Если использовать BIO, поток будет удерживаться до завершения всего цикла обработки. Все потоки из пула потоков, tomcat не будет создавать поток для каждого запроса.

  1. Каков правильный способ обеспечить отправку потока событий только один раз в тот же сеанс соединения/браузера?

Do eventSource.close() на стороне браузера - лучший выбор.

  1. Каков правильный способ обеспечения того, чтобы поток событий был закрыт, и на сервере не наложены дополнительные ресурсы?

Не забывайте вызывать AsyncContext.complete() на стороне сервера.

  1. Как различать запросы GET и POST. Почему он выбрал GET?

API EventSource в браузере поддерживает только запросы GET, но на стороне сервера нет такого ограничения. SSE в основном используется для получения данных событий с сервера. Если произойдет событие, браузер может получить его вовремя и не нужно создавать новый запрос для опроса. Если вам нужна полнодуплексная связь, попробуйте WebSocket в SSE.

  1. Слишком рано использовать SSE на Tomcat? Любые проблемы с производительностью?

Не должно быть проблем с производительностью, если мы используем NIO-соединитель и API асинхронной обработки. Я не знаю, является ли разъем Tomcat NIO зрелым или нет, но что-то никогда не будет известно, если мы не попробуем его.