Skip to content

Instantly share code, notes, and snippets.

@chRyNaN
Last active March 24, 2023 23:11
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save chRyNaN/59a525276e7b148b1ce6 to your computer and use it in GitHub Desktop.
Save chRyNaN/59a525276e7b148b1ce6 to your computer and use it in GitHub Desktop.
Server-Sent Event Java Servlet backend
public interface DefaultListener {
public void onNotification(NotificationEvent event);
}
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.inject.Inject;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = {"/notify"}, asyncSupported = true)
public class NotificationChannel extends HttpServlet implements DefaultListener{
private static final long serialVersionUID = -2827663265593547983L;
private ConcurrentMap<String, AsyncContext> contexts = new ConcurrentHashMap<>();
@Inject
NotificationListener listener;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//initialize for server-sent events
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
final String userId = request.getParameter("userId"); //retrieve the user Id
if (userId == null){
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
//to clear threads and allow for asynchronous execution
final AsyncContext asyncContext = request.startAsync(request, response);
asyncContext.setTimeout(0);
//add context to list for later use
if (contexts == null || contexts.isEmpty() || !contexts.containsKey(userId)){
if (contexts == null){
contexts = new ConcurrentHashMap<String, AsyncContext>();
}
contexts.put(userId, asyncContext);
}
listener.addListener(this);
sendNotification(userId, "connected");
}
protected void sendNotification(String userId, String notification){
try{
if(contexts.containsKey(userId)){
AsyncContext asyncContext = contexts.get(userId);
PrintWriter writer = asyncContext.getResponse().getWriter();
writer.write("data: " + notification + "\n\n");
writer.flush();
}
}catch (Exception e){
e.printStackTrace();
//try again before disconnecting the user
try{
if(contexts.containsKey(userId)){
AsyncContext asyncContext = contexts.get(userId);
PrintWriter writer = asyncContext.getResponse().getWriter();
writer.write("data: " + notification + "\n\n");
writer.flush();
}
}catch (Exception e2){
e2.printStackTrace();
AsyncContext asyncContext = contexts.remove(userId);
asyncContext.complete();
}
}
}
@Override
public void onNotification(NotificationEvent event) {
this.sendNotification(event.getUserId(), event.getJSONEvent());
}
}
import org.json.JSONException;
import org.json.JSONObject;
public class NotificationEvent {
private String type;
private String message;
private String toUserId;
public NotificationEvent(String type, String message, String toUserId){
this.type = type;
this.message = message;
this.toUserId = toUserId;
}
public NotificationEvent(String type, String toUserId){
this.type = type;
this.message = "";
this.toUserId = toUserId;
}
public String getType(){
return this.type;
}
public String getMessage(){
return this.message;
}
public String getUserId(){
return this.toUserId;
}
public String getJSONEvent(){
JSONObject obj = new JSONObject();
try {
obj.put("type", type);
obj.put("message", message);
} catch (JSONException e) {
e.printStackTrace();
}
return obj.toString();
}
}
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Asynchronous;
import javax.ejb.Stateful;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
@Stateful
@ApplicationScoped
public class NotificationListener {
List<DefaultListener> listeners = new ArrayList<>();
public void addListener(DefaultListener l){
listeners.add(l);
}
@Asynchronous
public void listenForNotifications(@Observes NotificationEvent event){
//this method is called by the container whenever a NotificationEvent is fired
for (DefaultListener l : listeners){
l.onNotification(event);
}
}
@Asynchronous
public void checkForUpdates(String userId){
//manually check the database for updates
}
}
(function(){
console.log("notify.js");
var eventSource = new EventSource("notify?userId=123456");//need a way to access userId from server, then apply it here
eventSource.onopen = function(e){
console.log("Connection Opened");
};
eventSource.onmessage = function(e){
console.log("Message: " + e.data);
};
eventSource.onerror = function(e){
console.log("Error");
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment