2012-06-04 2 views
14

Я зарегистрировал типичную SSE при загрузке страницы:SSE и Servlet 3.0

Клиент:

sseTest: function(){ 

var source = new EventSource('mySSE'); 
source.onopen = function(event){ 
console.log("eventsource opened!"); 
}; 

source.onmessage = function(event){ 
var data = event.data; 
console.log(data); 
document.getElementById('sse').innerHTML+=event.data + "<br />"; 
}; 
} 

Мой Javascript-Debugger говорит, что "! EventSource открыл" был успешно.

Мой код сервера является Servlet 3.0:

import java.io.IOException; 
import java.io.PrintWriter; 
import java.util.Random; 

import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 

@WebServlet(urlPatterns={"/mySSE"}, name = "hello-sse", asyncSupported=true) 
public class MyServletSSE extends HttpServlet { 

@Override 
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 

resp.setContentType("text/event-stream"); 
resp.setCharacterEncoding("UTF-8"); 

Random random = new Random(); 
PrintWriter out = resp.getWriter(); 

//AsyncContext aCtx = req.startAsync(req, resp); 
//ServletRequest sReq = aCtx.getRequest(); 

String next = "data: " + String.valueOf(random.nextInt(100) + 1) + "\n\n"; 
//out.print("retry: 600000\n"); //set the timeout to 10 mins in milliseconds 
out.write(next); 
out.flush(); 
// do not close the stream as EventSource is listening 
//out.close(); 
//super.doGet(req, resp); 
} 
} 

код работает! Клиент-код запускает метод doGet() каждые 3 секунды и извлекает новые данные.

Вопросы: Однако, интересно как я могу сделать этот код лучше с помощью новой Servlet 3.0, такие как фьючерсы асинхронным-поддержки или asyncContext.addListener (asyncListener) или что-то еще, что я не знаю. Как я никогда не закрываю поток, мне кажется , как мой сервер будет масштабироваться?

Теоретически наилучшим подходом было бы инициировать метод doGet() через серверный код явно при наличии новых данных, поэтому клиенту не нужно запускать клиентскую «onmessage()» - Метод и, следовательно, сторона сервера «doGet()» - метод каждые 3 секунды для новых данных.

+0

Этого является одним из лучших вопросов, которые я видел в SO, хотя я ответил на вопрос, я действительно многому научился, особенно о EventSource! –

+0

Если есть 1000 клиентов, означают ли они, что на сервере будет 1000 соединений? – Harun

ответ

11

Это отличный вопрос, вот полный рабочий пример (Servlet 3.0/Java EE 6)

Некоторые примечания:

  1. он обрабатывает "вкладка браузера/окно закрыто" с помощью out.checkError(), что также называют flush()
  2. Я написал это быстро, так что я уверен, что он может быть улучшен, только POC, не использует в производстве перед тестированием

Servlet: (опущенный импорт для краткости будет обновлять полную суть скоро)

@WebServlet(urlPatterns = {"/mySSE"}, asyncSupported = true) 
public class MyServletSSE extends HttpServlet { 

    private final Queue<AsyncContext> ongoingRequests = new ConcurrentLinkedQueue<>(); 
    private ScheduledExecutorService service; 

    @Override 
    public void init(ServletConfig config) throws ServletException { 
    final Runnable notifier = new Runnable() { 
     @Override 
     public void run() { 
     final Iterator<AsyncContext> iterator = ongoingRequests.iterator(); 
     //not using for : in to allow removing items while iterating 
     while (iterator.hasNext()) { 
      AsyncContext ac = iterator.next(); 
      Random random = new Random(); 
      final ServletResponse res = ac.getResponse(); 
      PrintWriter out; 
      try { 
      out = res.getWriter(); 
      String next = "data: " + String.valueOf(random.nextInt(100) + 1) + "num of clients = " + ongoingRequests.size() + "\n\n"; 
      out.write(next); 
      if (out.checkError()) { //checkError calls flush, and flush() does not throw IOException 
       iterator.remove(); 
      } 
      } catch (IOException ignored) { 
      iterator.remove(); 
      } 
     } 
     } 
    }; 
    service = Executors.newScheduledThreadPool(10); 
    service.scheduleAtFixedRate(notifier, 1, 1, TimeUnit.SECONDS); 
    } 

    @Override 
    public void doGet(HttpServletRequest req, HttpServletResponse res) { 
    res.setContentType("text/event-stream"); 
    res.setCharacterEncoding("UTF-8"); 

    final AsyncContext ac = req.startAsync(); 
    ac.setTimeout(60 * 1000); 
    ac.addListener(new AsyncListener() { 
     @Override public void onComplete(AsyncEvent event) throws IOException {ongoingRequests.remove(ac);} 
     @Override public void onTimeout(AsyncEvent event) throws IOException {ongoingRequests.remove(ac);} 
     @Override public void onError(AsyncEvent event) throws IOException {ongoingRequests.remove(ac);} 
     @Override public void onStartAsync(AsyncEvent event) throws IOException {} 
    }); 
    ongoingRequests.add(ac); 
    } 
} 

JSP:

<%@page contentType="text/html" pageEncoding="UTF-8"%> 
<!DOCTYPE html> 
<html> 
    <head> 
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
     <title>JSP Page</title> 
     <script> 
      function test() { 
       var source = new EventSource('mySSE'); 
       source.onopen = function(event) { 
        console.log("eventsource opened!"); 
       }; 

       source.onmessage = function(event) { 
        var data = event.data; 
        console.log(data); 
        document.getElementById('sse').innerHTML += event.data + "<br />"; 
       }; 
      } 
      window.addEventListener("load", test); 
     </script> 
    </head> 
    <body> 
     <h1>Hello SSE!</h1> 
     <div id="sse"></div> 
    </body> 
</html> 
+1

Одно медленное чтение клиента замедляет работу другого клиента, поскольку этот метод использует блокировку записи. (что было подходящим ответом еще в 2013 году). Современные приложения, вероятно, должны использовать Servlet 3.1 с записью ввода-вывода Async. –

+0

Можете ли вы дать ссылку на «Сервлет 3.1 с записью ввода-вывода Async»? – Harun

+1

Joakim: Это действительно проблема? Я только что сделал тестовый прогон. Два клиента, один из которых блокируется в alert(). Сервер записывает короткое сообщение каждые 30 секунд. Рана ночь без сучка и задоринки. Возможно, в цепочке много буферизации, но все же ... –

1

полезного пример.

Люди могут получить "IllegalStateException: Не поддерживаюсь" для startAsync(), в этом случае либо не забывает:

@WebServlet(urlPatterns = "/Sse", asyncSupported=true) 

или использовать

request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true); 

от this поста

+0

И на всех применимых фильтрах тоже. –

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

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