/* * COMSAT * Copyright (C) 2014-2016, Parallel Universe Software Co. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 3.0 * as published by the Free Software Foundation. */ package co.paralleluniverse.comsat.webactors; import co.paralleluniverse.actors.*; import co.paralleluniverse.fibers.SuspendExecution; import co.paralleluniverse.strands.channels.Channels; import co.paralleluniverse.strands.channels.SendPort; import com.google.common.base.Function; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class MyWebActor extends BasicActor<WebMessage, Void> { // There is one actor for each client private static final Set<ActorRef<WebMessage>> actors = Collections.newSetFromMap(new ConcurrentHashMap<ActorRef<WebMessage>, Boolean>()); // The client representation of this actor private SendPort<WebDataMessage> peer; @Override protected final Void doRun() throws InterruptedException, SuspendExecution { actors.add(self()); try { //noinspection InfiniteLoopStatement for (;;) { final Object message = receive(); if (message instanceof HttpRequest) { final HttpRequest msg = (HttpRequest) message; switch (msg.getRequestURI()) { case "/": msg.getFrom().send(HttpResponse.ok(self(), msg, "httpResponse").setContentType("text/html").build()); break; case "/notfound": msg.getFrom().send(HttpResponse.error(self(), msg, 404, "Not found").setContentType("text/plain").build()); break; case "/die": throw new RuntimeException("die"); case "/redirect": msg.getFrom().send(HttpResponse.redirect(msg, "/foo").build()); break; case "/ssepublish": postMessage(new WebDataMessage(self(), msg.getStringBody())); msg.getFrom().send(HttpResponse.ok(self(), msg, "").build()); break; case "/ssechannel": msg.getFrom().send(SSE.startSSE(self(), msg).build()); break; } } // -------- WebSocket/SSE opened -------- else if (message instanceof WebStreamOpened) { final WebStreamOpened msg = (WebStreamOpened) message; watch(msg.getFrom()); // will call handleLifecycleMessage with ExitMessage when the session ends SendPort<WebDataMessage> p = msg.getFrom(); if (msg instanceof HttpStreamOpened) p = wrapAsSSE(p); this.peer = p; // p.send(new WebDataMessage(self(), "Welcome. " + actors.size() + " listeners")); } // -------- WebSocket message received -------- else if (message instanceof WebDataMessage) { postMessage((WebDataMessage) message); } } } finally { actors.remove(self()); } } private SendPort<WebDataMessage> wrapAsSSE(SendPort<WebDataMessage> actor) { return Channels.mapSend(actor, new Function<WebDataMessage, WebDataMessage>() { @Override public final WebDataMessage apply(WebDataMessage f) { return new WebDataMessage(f.getFrom(), SSE.event(f.getStringBody())); } }); } private void postMessage(final WebDataMessage webDataMessage) throws InterruptedException, SuspendExecution { if (peer != null) peer.send(webDataMessage); if (webDataMessage.getFrom().equals(peer)) for (final SendPort actor : actors) if (actor != self()) //noinspection unchecked actor.send(webDataMessage); } @Override protected final WebMessage handleLifecycleMessage(LifecycleMessage m) { // while listeners might contain an SSE actor wrapped with Channels.map, the wrapped SendPort maintains the original actors hashCode and equals behavior if (m instanceof ExitMessage) actors.remove(((ExitMessage) m).getActor()); return super.handleLifecycleMessage(m); } }