+
+ private void handleEventsEndpoint(HttpExchange httpExchange) throws IOException {
+ if (!"/api/v1/events".equals(httpExchange.getRequestURI().getPath())) {
+ sendResponse(404, null, httpExchange);
+ return;
+ }
+ if (!"GET".equals(httpExchange.getRequestMethod())) {
+ sendResponse(405, null, httpExchange);
+ return;
+ }
+
+ try {
+ final var queryString = httpExchange.getRequestURI().getQuery();
+ final var query = queryString == null ? Map.<String, String>of() : Util.getQueryMap(queryString);
+
+ List<Manager> managers = getManagerFromQuery(query);
+ if (managers == null) {
+ sendResponse(400, null, httpExchange);
+ return;
+ }
+
+ httpExchange.getResponseHeaders().add("Content-Type", "text/event-stream");
+ httpExchange.sendResponseHeaders(200, 0);
+ final var sender = new ServerSentEventSender(httpExchange.getResponseBody());
+
+ final var shouldStop = new AtomicBoolean(false);
+ final var handlers = subscribeReceiveHandlers(managers, sender, () -> {
+ shouldStop.set(true);
+ synchronized (this) {
+ this.notifyAll();
+ }
+ });
+
+ try {
+ while (true) {
+ synchronized (this) {
+ wait(15_000);
+ }
+ if (shouldStop.get() || shutdown.get()) {
+ break;
+ }
+
+ try {
+ sender.sendKeepAlive();
+ } catch (IOException e) {
+ break;
+ }
+ }
+ } finally {
+ for (final var pair : handlers) {
+ unsubscribeReceiveHandler(pair);
+ }
+ try {
+ httpExchange.getResponseBody().close();
+ } catch (IOException ignored) {
+ }
+ }
+ } catch (Throwable aEx) {
+ logger.error("Failed to process request.", aEx);
+ sendResponse(500, null, httpExchange);
+ }
+ }
+
+ private void handleCheckEndpoint(HttpExchange httpExchange) throws IOException {
+ if (!"/api/v1/check".equals(httpExchange.getRequestURI().getPath())) {
+ sendResponse(404, null, httpExchange);
+ return;
+ }
+ if (!"GET".equals(httpExchange.getRequestMethod())) {
+ sendResponse(405, null, httpExchange);
+ return;
+ }
+
+ sendResponse(200, null, httpExchange);
+ }
+
+ private List<Manager> getManagerFromQuery(final Map<String, String> query) {
+ if (m != null) {
+ return List.of(m);
+ }
+ if (c != null) {
+ final var account = query.get("account");
+ if (account == null || account.isEmpty()) {
+ return c.getManagers();
+ } else {
+ final var manager = c.getManager(account);
+ if (manager == null) {
+ return null;
+ }
+ return List.of(manager);
+ }
+ }
+ throw new AssertionError("Unreachable state");
+ }
+
+ private List<Pair<Manager, Manager.ReceiveMessageHandler>> subscribeReceiveHandlers(
+ final List<Manager> managers, final ServerSentEventSender sender, Callable unsubscribe
+ ) {
+ return managers.stream().map(m1 -> {
+ final var receiveMessageHandler = new JsonReceiveMessageHandler(m1, s -> {
+ try {
+ sender.sendEvent(null, "receive", List.of(objectMapper.writeValueAsString(s)));
+ } catch (IOException e) {
+ unsubscribe.call();
+ }
+ });
+ m1.addReceiveHandler(receiveMessageHandler);
+ return new Pair<>(m1, (Manager.ReceiveMessageHandler) receiveMessageHandler);
+ }).toList();
+ }
+
+ private void unsubscribeReceiveHandler(final Pair<Manager, Manager.ReceiveMessageHandler> pair) {
+ final var m = pair.first();
+ final var handler = pair.second();
+ m.removeReceiveHandler(handler);
+ }
+
+ private interface Callable {
+
+ void call();
+ }