package automately.core.services.sdk; import automately.core.services.core.AutomatelyService; import com.hazelcast.core.IMap; import io.jsync.Async; import io.jsync.AsyncResult; import io.jsync.Handler; import io.jsync.app.core.Cluster; import io.jsync.app.core.Config; import io.jsync.app.core.Logger; import io.jsync.eventbus.EventBus; import io.jsync.eventbus.Message; import io.jsync.json.JsonArray; import io.jsync.json.JsonObject; import io.jsync.sockjs.EventBusBridgeHook; import io.jsync.sockjs.SockJSServer; import io.jsync.sockjs.SockJSSocket; import java.util.*; /** * The SdkSockJSServer is the default SockJS server for handling any SDK Operations */ public class SdkSockJSServer extends AutomatelyService { // TODO Complete JavaDocs // We want to set the sessions private io.jsync.http.HttpServer httpServer; private Logger logger; @Override public void start(Cluster owner) { Async async = owner.async(); Config config = owner.config(); if (!coreConfig().containsField("sdk_httpserver_host")) coreConfig().putValue("sdk_httpserver_host", config.clusterHost()); if (!coreConfig().containsField("sdk_httpserver_port")) coreConfig().putValue("sdk_httpserver_port", 9080); config.save(); this.logger = owner.logger(); httpServer = async.createHttpServer(); JsonObject bridgeConfig = new JsonObject(); bridgeConfig.putString("auth_address", "automately.sdk.auth.authorise"); // Increased auth_timeout bridgeConfig.putNumber("auth_timeout", 24 * 60 * 1000); // We are increasing the amount to 25. bridgeConfig.putNumber("max_handlers_per_socket", 25); // Handlers we can send messages to from the client side.. JsonArray confInbound = new JsonArray(); // Handlers we can bind to from the client side.. JsonArray confOutbound = new JsonArray(); JsonObject sdkAuth = new JsonObject(); sdkAuth.putString("address_re", "automately.sdk.auth..+"); confInbound.add(sdkAuth); // Used to retrieve info about jobs and such JsonObject sdkJob = new JsonObject(); sdkJob.putString("address_re", "automately.sdk.job..+"); sdkJob.putBoolean("requires_auth", true); confInbound.add(sdkJob); // Allow messageBus messages to be sent to public handlers JsonObject sdkMessageBusOutbound = new JsonObject(); sdkMessageBusOutbound.putString("address", "automately.sdk.messageBus"); sdkMessageBusOutbound.putBoolean("requires_auth", true); confInbound.add(sdkMessageBusOutbound); JsonObject sdkMessageBusInbound = new JsonObject(); sdkMessageBusInbound.putString("address_re", "automately.sdk.messageBus..+"); sdkMessageBusInbound.putBoolean("requires_auth", true); confOutbound.add(sdkMessageBusInbound); SockJSServer sockJSServer = owner.async().createSockJSServer(httpServer); // We utilize a hook so we can intercept all handlers that need to be mapped to the client sockJSServer.setHook(new EventBusBridgeHook() { private IMap<String, String> clientMessageBusHandlers = cluster().data().getMap("sdk.messageBus.handlers"); private HashMap<SockJSSocket, List<Map.Entry<String, Map.Entry<String, Handler<Message>>>>> registeredHandlers = new HashMap<>(); private EventBus eventBus = cluster().eventBus(); public boolean handleSocketCreated(SockJSSocket sock) { return true; } @Override public void handleSocketClosed(SockJSSocket sock) { List<Map.Entry<String, Map.Entry<String, Handler<Message>>>> handlers = registeredHandlers.get(sock); if (handlers != null) { for (Map.Entry<String, Map.Entry<String, Handler<Message>>> entry : handlers) { Map.Entry<String, Handler<Message>> entry1 = entry.getValue(); eventBus.unregisterHandler(entry1.getKey(), entry1.getValue()); } } registeredHandlers.remove(sock); } @Override public boolean handleSendOrPub(SockJSSocket sock, boolean send, JsonObject msg, String address) { return true; } @Override public boolean handlePreRegister(SockJSSocket sock, String address) { if (address.startsWith("automately.sdk.messageBus.")) { // If this address is not stored in the map then // we will not allow it to be registered.. This means // people could spoof thingies.. if (!clientMessageBusHandlers.containsKey(address)) { return false; } String internalIdentifier = clientMessageBusHandlers.get(address); Handler<Message> handler = event -> eventBus.send(address, event.body()); eventBus.registerHandler(internalIdentifier, handler); List<Map.Entry<String, Map.Entry<String, Handler<Message>>>> list = registeredHandlers.get(sock); if (list == null) { registeredHandlers.put(sock, list = new ArrayList<>()); } list.add(new AbstractMap.SimpleEntry<>(address, new AbstractMap.SimpleEntry<>(internalIdentifier, handler))); clientMessageBusHandlers.remove(address); return true; } // It may seem weird that we assume it's okay to register a handler... It's generally not. // So the best idea is to not trust the handlers registered on the SockJS return true; } @Override public void handlePostRegister(SockJSSocket sock, String address) { } @Override public boolean handleUnregister(SockJSSocket sock, String address) { List<Map.Entry<String, Map.Entry<String, Handler<Message>>>> handlers = registeredHandlers.get(sock); if (handlers != null) { for (Map.Entry<String, Map.Entry<String, Handler<Message>>> entry : handlers) { if (entry.getKey().equals(address)) { Map.Entry<String, Handler<Message>> routeHandler = entry.getValue(); eventBus.unregisterHandler(routeHandler.getKey(), routeHandler.getValue()); } } } return true; } @Override public boolean handleAuthorise(JsonObject message, String sessionID, Handler<AsyncResult<Boolean>> handler) { return false; } }); sockJSServer.bridge(new JsonObject().putString("prefix", "/automately.sdk.eventBus"), confInbound, confOutbound, bridgeConfig); // Optimization. httpServer.setMaxWebSocketFrameSize(262144); // Done with our exposing.. httpServer.listen(coreConfig().getValue("sdk_httpserver_port"), coreConfig().getValue("sdk_httpserver_host"), asyncResult -> { if (!asyncResult.succeeded()) { logger.fatal("Could not start DefaultHttpServer on " + coreConfig().getValue("sdk_httpserver_host") + ":" + coreConfig().getValue("sdk_httpserver_port")); return; } logger.info("SdkSockJSServer Started on " + coreConfig().getValue("sdk_httpserver_host") + ":" + coreConfig().getValue("sdk_httpserver_port")); }); } @Override public void stop() { httpServer.close(asyncResult -> { if (asyncResult.succeeded()) { logger.info("SdkSockJSServer Stopped"); } }); } @Override public String name() { return getClass().getCanonicalName(); } }