/ / SSE e Servlet 3.0 - html5, servlets, eventos enviados pelo servidor

SSE e Servlet 3.0 - html5, servlets, servidor-enviado-eventos

Registrei um SSE típico quando a página carrega:

Cliente:

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 />";
};
}

Meu Javascript-Debugger diz que "fonte de eventos aberta!" foi com sucesso.

Meu código de servidor é um 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) + "nn";
//out.print("retry: 600000n"); //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);
}
}

O código funciona! O Client-Code dispara o método doGet () - a cada 3 segundos e recupera os novos dados.

Questões: No entanto, eu me pergunto como posso tornar este código melhor usando novos Servlet 3.0 Futures, como Async-Support ou asyncContext.addListener (asyncListener) ou algo que eu não conheço. Como nunca fecho o riacho, fico pensando como meu servidor irá escalar?

Teoricamente, a melhor abordagem seriaacionar o doGet () - Método através do código do lado do servidor explicitamente quando novos dados estiverem lá, então o cliente não precisa acionar o lado do cliente "onmessage ()" - Método e, portanto, do lado do servidor "doGet ()" - Método a cada 3 segundos para novos dados.

Respostas:

12 para resposta № 1

Esta é uma excelente pergunta, aqui está um exemplo de trabalho completo (Servlet 3.0 / Java EE 6)

Algumas notas:

  1. lida com "guia / janela fechada do navegador" via out.checkError() isso também chama flush()
  2. Eu o escrevi rapidamente, então tenho certeza que pode ser melhorado, apenas um POC, não use na produção antes de testar

Servlet: (importações omitidas por questão de brevidade, atualizarei uma essência completa em breve)

@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() + "nn";
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 para resposta № 2

Exemplo útil.

As pessoas podem obter "IllegalStateException: Not supported" para startAsync () e, nesse caso, não se esqueça:

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

ou usar

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

a partir de esta postar