diff --git a/labs/vertx-akka/.gitignore b/labs/vertx-akka/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2e78b79d531f0c06fd8b54a7a2d39b6511ce1e6d --- /dev/null +++ b/labs/vertx-akka/.gitignore @@ -0,0 +1,51 @@ +.metadata +.project +.classpath +.settings +.history +.worksheet +gen +**/*.swp +**/*~.nib +**/build/ +**/*.pbxuser +**/*.perspective +**/*.perspectivev3 +*.xcworkspace +*.xcuserdatad +*.iml +project/*.ipr +project/*.iml +project/*.iws +project/out +project/*/target +project/target +project/*/bin +project/*/build +project/*.iml +project/*/*.iml +project/.idea +project/.idea/* +.idea/ +.DS_Store +project/.DS_Store +project/*/.DS_Store +tm.out +tmlog*.log +*.tm*.epoch +out/ +provisioning/.vagrant +provisioning/*/.vagrant +provisioning/*/*.known +/sbt/akka-patterns-store/ +/daemon/src/build/ +*.lock +logs/ +tmp/ +build/ +akka-patterns-store/ +lib_managed/ +.cache +bin/ +.vertx/ +target/ diff --git a/labs/vertx-akka/README.md b/labs/vertx-akka/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/labs/vertx-akka/build.sbt b/labs/vertx-akka/build.sbt new file mode 100755 index 0000000000000000000000000000000000000000..ae9f47785f15f61bc9e0e4502cc27e5fb2287d3c --- /dev/null +++ b/labs/vertx-akka/build.sbt @@ -0,0 +1,99 @@ +enablePlugins(JavaServerAppPackaging) + +name := "vertx-akka" + +organization := "org.bigbluebutton" + +version := "0.0.2" + +scalaVersion := "2.11.6" + +scalacOptions ++= Seq( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" +) + +resolvers ++= Seq( + "spray repo" at "http://repo.spray.io/", + "rediscala" at "http://dl.bintray.com/etaty/maven", + "blindside-repos" at "http://blindside.googlecode.com/svn/repository/" +) + +publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/dev/repo/maven-repo/releases" )) ) + +// We want to have our jar files in lib_managed dir. +// This way we'll have the right path when we import +// into eclipse. +retrieveManaged := true + +unmanagedResourceDirectories in Compile += { baseDirectory.value / "src/main/webapp" } + +testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", "junitxml") + +testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") + +libraryDependencies ++= { + val akkaVersion = "2.4.0" + Seq( + "com.typesafe.akka" %% "akka-actor" % akkaVersion, + "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", + "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, + "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime", + "org.pegdown" % "pegdown" % "1.4.0", + "junit" % "junit" % "4.11", + "commons-codec" % "commons-codec" % "1.8", + "joda-time" % "joda-time" % "2.3", + "com.google.code.gson" % "gson" % "1.7.1", + "io.vertx" % "vertx-web" % "3.1.0", + "io.vertx" % "vertx-auth-common" % "3.1.0", + "io.vertx" % "vertx-auth-shiro" % "3.1.0" + )} + +seq(Revolver.settings: _*) + +scalariformSettings + + +//----------- +// Packaging +// +// Reference: +// https://github.com/muuki88/sbt-native-packager-examples/tree/master/akka-server-app +// http://www.scala-sbt.org/sbt-native-packager/index.html +//----------- +mainClass := Some("org.bigbluebutton.Boot") + +maintainer in Linux := "Richard Alam <ritzalam@gmail.com>" + +packageSummary in Linux := "vertx akka example" + +packageDescription := """Vertx Akka Example.""" + +val user = "bigbluebutton" + +val group = "bigbluebutton" + +// user which will execute the application +daemonUser in Linux := user + +// group which will execute the application +daemonGroup in Linux := group + +mappings in Universal <+= (packageBin in Compile, sourceDirectory ) map { (_, src) => + // Move the application.conf so the user can override settings here + val appConf = src / "main" / "resources" / "application.conf" + appConf -> "conf/application.conf" +} + +mappings in Universal <+= (packageBin in Compile, sourceDirectory ) map { (_, src) => + // Move logback.xml so the user can override settings here + val logConf = src / "main" / "resources" / "logback.xml" + logConf -> "conf/logback.xml" +} + +debianPackageDependencies in Debian ++= Seq("java8-runtime-headless", "bash") diff --git a/labs/vertx-akka/nginx/vertx-akka b/labs/vertx-akka/nginx/vertx-akka new file mode 100644 index 0000000000000000000000000000000000000000..c9ba908c380aa68bca7957d7bac72831327aa98f --- /dev/null +++ b/labs/vertx-akka/nginx/vertx-akka @@ -0,0 +1,23 @@ +server { + listen 80; + server_name 192.168.23.33; + + access_log /var/log/nginx/vertx-akka.access.log; + + # Vertx-Akka landing page. + location / { + root /var/www/vertx-akka; + index index.html index.htm; + expires 1m; + } + + #error_page 404 /404.html; + + # Redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/www/nginx-default; + } +} + diff --git a/labs/vertx-akka/nginx/webroot/chat.html b/labs/vertx-akka/nginx/webroot/chat.html new file mode 100644 index 0000000000000000000000000000000000000000..e393d2ef123c330bf3239f3c8271270c622184b1 --- /dev/null +++ b/labs/vertx-akka/nginx/webroot/chat.html @@ -0,0 +1,71 @@ +<!-- + #%L + distributed-chat-service + %% + Copyright (C) 2015 Zanclus Consulting + %% + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + #L% + --> +<html> +<head> + <title>Distributed Chat Service</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <script src="https://code.jquery.com/jquery-1.11.2.min.js"></script> + <script src="//cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script> + <script src="vertxbus.js"></script> + <style> + .inset { + box-shadow: inset 0 0 4px #000000; + -moz-box-shadow: inset 0 0 4px #000000; + -webkit-box-shadow: inset 0 0 4px #000000; + width: 400px; + border-width: 4px; + padding: 5px; + } + + input.inset { + height: 40px; + } + + div.inset { + height: 500px; + white-space: pre-wrap + } + </style> +</head> +<body> +<script> + var eb = new vertx.EventBus("http://192.168.23.33:3001/eventbus"); + eb.onopen = function () { + eb.registerHandler("chat.to.client", function (msg) { + $('#chat').append(msg + "\n"); + }); + }; + + function send(event) { + if (event.keyCode == 13 || event.which == 13) { + var message = $('#input').val(); + if (message.length > 0) { + console.log($('#input')); + eb.publish("chat.to.server", message); + $('#input').val(""); + } + } + } +</script> +<div id="chat" class="inset"></div> +<input id="input" type="text" onkeydown="send(event)" class="inset"> +</body> +</html> diff --git a/labs/vertx-akka/nginx/webroot/index.html b/labs/vertx-akka/nginx/webroot/index.html new file mode 100755 index 0000000000000000000000000000000000000000..f13421c94c1dacc1972cb32ba6e07d142b80acd5 --- /dev/null +++ b/labs/vertx-akka/nginx/webroot/index.html @@ -0,0 +1,35 @@ +<html> +<head> + <title></title> + <script src="https://code.jquery.com/jquery-1.11.2.min.js"></script> + <script src="//cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script> + <script src="vertxbus.js"></script> +</head> + +<style> + .news { + font-size: 20pt; + } +</style> + +<body> + +<div class="news">Latest news: </div><br> +<div id="status" class="news"></div> + +<script> + + var eb = new vertx.EventBus("http://192.168.23.33:3000/eventbus"); + + eb.onopen = function() { + eb.registerHandler("news-feed", function(msg) { + var str = "<code>" + msg + "</code><br>"; + $('#status').prepend(str); + }) + } + + +</script> + +</body> +</html> diff --git a/labs/vertx-akka/nginx/webroot/vertxbus.js b/labs/vertx-akka/nginx/webroot/vertxbus.js new file mode 100755 index 0000000000000000000000000000000000000000..59df3af1ad0d1abe10af5989d2f1179f0e7d25d8 --- /dev/null +++ b/labs/vertx-akka/nginx/webroot/vertxbus.js @@ -0,0 +1,228 @@ +/* + * Copyright 2014 Red Hat, Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ + +/* + * Copyright (c) 2011-2013 The original author or authors + * ------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ + +var vertx = vertx || {}; + +!function(factory) { + if (typeof define === "function" && define.amd) { + // Expose as an AMD module with SockJS dependency. + // "vertxbus" and "sockjs" names are used because + // AMD module names are derived from file names. + define("vertxbus", ["sockjs"], factory); + } else { + // No AMD-compliant loader + factory(SockJS); + } +}(function(SockJS) { + + vertx.EventBus = function(url, options) { + + var that = this; + var sockJSConn = new SockJS(url, undefined, options); + var handlerMap = {}; + var replyHandlers = {}; + var state = vertx.EventBus.CONNECTING; + var pingTimerID = null; + var pingInterval = null; + if (options) { + pingInterval = options['vertxbus_ping_interval']; + } + if (!pingInterval) { + pingInterval = 5000; + } + + that.onopen = null; + that.onclose = null; + + that.send = function(address, message, replyHandler) { + sendOrPub("send", address, message, replyHandler) + } + + that.publish = function(address, message) { + sendOrPub("publish", address, message, null) + } + + that.registerHandler = function(address, handler) { + checkSpecified("address", 'string', address); + checkSpecified("handler", 'function', handler); + checkOpen(); + var handlers = handlerMap[address]; + if (!handlers) { + handlers = [handler]; + handlerMap[address] = handlers; + // First handler for this address so we should register the connection + var msg = { type : "register", + address: address }; + sockJSConn.send(JSON.stringify(msg)); + } else { + handlers[handlers.length] = handler; + } + } + + that.unregisterHandler = function(address, handler) { + checkSpecified("address", 'string', address); + checkSpecified("handler", 'function', handler); + checkOpen(); + var handlers = handlerMap[address]; + if (handlers) { + var idx = handlers.indexOf(handler); + if (idx != -1) handlers.splice(idx, 1); + if (handlers.length == 0) { + // No more local handlers so we should unregister the connection + + var msg = { type : "unregister", + address: address}; + sockJSConn.send(JSON.stringify(msg)); + delete handlerMap[address]; + } + } + } + + that.close = function() { + checkOpen(); + state = vertx.EventBus.CLOSING; + sockJSConn.close(); + } + + that.readyState = function() { + return state; + } + + sockJSConn.onopen = function() { + // Send the first ping then send a ping every pingInterval milliseconds + sendPing(); + pingTimerID = setInterval(sendPing, pingInterval); + state = vertx.EventBus.OPEN; + if (that.onopen) { + that.onopen(); + } + }; + + sockJSConn.onclose = function() { + state = vertx.EventBus.CLOSED; + if (pingTimerID) clearInterval(pingTimerID); + if (that.onclose) { + that.onclose(); + } + }; + + sockJSConn.onmessage = function(e) { + var msg = e.data; + var json = JSON.parse(msg); + var type = json.type; + if (type === 'err') { + console.error("Error received on connection: " + json.body); + return; + } + var body = json.body; + var replyAddress = json.replyAddress; + var address = json.address; + var replyHandler; + if (replyAddress) { + replyHandler = function(reply, replyHandler) { + // Send back reply + that.send(replyAddress, reply, replyHandler); + }; + } + var handlers = handlerMap[address]; + if (handlers) { + // We make a copy since the handler might get unregistered from within the + // handler itself, which would screw up our iteration + var copy = handlers.slice(0); + for (var i = 0; i < copy.length; i++) { + copy[i](body, replyHandler); + } + } else { + // Might be a reply message + var handler = replyHandlers[address]; + if (handler) { + delete replyHandlers[address]; + handler(body, replyHandler); + } + } + } + + function sendPing() { + var msg = { + type: "ping" + } + sockJSConn.send(JSON.stringify(msg)); + } + + function sendOrPub(sendOrPub, address, message, replyHandler) { + checkSpecified("address", 'string', address); + checkSpecified("replyHandler", 'function', replyHandler, true); + checkOpen(); + var envelope = { type : sendOrPub, + address: address, + body: message }; + if (replyHandler) { + var replyAddress = makeUUID(); + envelope.replyAddress = replyAddress; + replyHandlers[replyAddress] = replyHandler; + } + var str = JSON.stringify(envelope); + sockJSConn.send(str); + } + + function checkOpen() { + if (state != vertx.EventBus.OPEN) { + throw new Error('INVALID_STATE_ERR'); + } + } + + function checkSpecified(paramName, paramType, param, optional) { + if (!optional && !param) { + throw new Error("Parameter " + paramName + " must be specified"); + } + if (param && typeof param != paramType) { + throw new Error("Parameter " + paramName + " must be of type " + paramType); + } + } + + function isFunction(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + } + + function makeUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + .replace(/[xy]/g,function(a,b){return b=Math.random()*16,(a=="y"?b&3|8:b|0).toString(16)})} + + } + + vertx.EventBus.CONNECTING = 0; + vertx.EventBus.OPEN = 1; + vertx.EventBus.CLOSING = 2; + vertx.EventBus.CLOSED = 3; + + return vertx.EventBus; + +}); diff --git a/labs/vertx-akka/project/Build.scala b/labs/vertx-akka/project/Build.scala new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/labs/vertx-akka/project/build.properties b/labs/vertx-akka/project/build.properties new file mode 100755 index 0000000000000000000000000000000000000000..a6e117b61042ee81c62ba3a0fc5210d9502944df --- /dev/null +++ b/labs/vertx-akka/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.8 diff --git a/labs/vertx-akka/project/packager.sbt b/labs/vertx-akka/project/packager.sbt new file mode 100755 index 0000000000000000000000000000000000000000..d3f5a12faa99758192ecc4ed3fc22c9249232e86 --- /dev/null +++ b/labs/vertx-akka/project/packager.sbt @@ -0,0 +1 @@ + diff --git a/labs/vertx-akka/project/plugins.sbt b/labs/vertx-akka/project/plugins.sbt new file mode 100755 index 0000000000000000000000000000000000000000..87ef6448644c23eed22abe7453eefd0054ca02a7 --- /dev/null +++ b/labs/vertx-akka/project/plugins.sbt @@ -0,0 +1,7 @@ +addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") + +addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.3.0") + +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") diff --git a/labs/vertx-akka/server-keystore.jks b/labs/vertx-akka/server-keystore.jks new file mode 100755 index 0000000000000000000000000000000000000000..3322b4548c06c7e7890460d70ebb97fe9fd22799 Binary files /dev/null and b/labs/vertx-akka/server-keystore.jks differ diff --git a/labs/vertx-akka/src/main/java/.gitkeep b/labs/vertx-akka/src/main/java/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/AkkaToVertxGateway.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/AkkaToVertxGateway.java new file mode 100755 index 0000000000000000000000000000000000000000..d651e3a496a553f260cb2f6671b01359292f8fef --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/AkkaToVertxGateway.java @@ -0,0 +1,19 @@ +package org.bigbluebutton.vertx; + +import io.vertx.core.Vertx; + +public class AkkaToVertxGateway implements IAkkaToVertxGateway{ + + private final Vertx vertx; + + public AkkaToVertxGateway(Vertx vertx) { + this.vertx = vertx; + } + + @Override + public void send(String json) { + vertx.eventBus().publish("to-vertx", json); + + } + +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/AuthenticateVerticle.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/AuthenticateVerticle.java new file mode 100755 index 0000000000000000000000000000000000000000..5c43a9275aed86badca804a01d67a44530ec05df --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/AuthenticateVerticle.java @@ -0,0 +1,54 @@ +package org.bigbluebutton.vertx; + +import io.vertx.core.AbstractVerticle; +import io.vertx.ext.auth.AuthProvider; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.*; +import io.vertx.ext.web.sstore.LocalSessionStore; +import org.bigbluebutton.VertxToAkkaGateway; + +public class AuthenticateVerticle extends AbstractVerticle { + private final VertxToAkkaGateway gw; + + public AuthenticateVerticle(VertxToAkkaGateway gw) { + this.gw = gw; + } + + @Override + public void start() throws Exception { + + Router router = Router.router(vertx); + + // We need cookies, sessions and request bodies + router.route().handler(CookieHandler.create()); + router.route().handler(BodyHandler.create()); + router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); + + // Simple auth service which uses a properties file for user/role info + AuthProvider authProvider = new MyAuthProvider(vertx); + + // We need a user session handler too to make sure the user is stored in the session between requests + router.route().handler(UserSessionHandler.create(authProvider)); + + // Any requests to URI starting '/private/' require login + router.route("/private/*").handler(RedirectAuthHandler.create(authProvider, "/loginpage.html")); + + // Serve the static private pages from directory 'private' + router.route("/private/*").handler(StaticHandler.create().setCachingEnabled(false).setWebRoot("private")); + + // Handles the actual login + router.route("/loginhandler").handler(FormLoginHandler.create(authProvider)); + + // Implement logout + router.route("/logout").handler(context -> { + context.clearUser(); + // Redirect back to the index page + context.response().putHeader("location", "/").setStatusCode(302).end(); + }); + + // Serve the non private static pages + router.route().handler(StaticHandler.create()); + + vertx.createHttpServer().requestHandler(router::accept).listen(4000); + } +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/BbbApi.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/BbbApi.java new file mode 100755 index 0000000000000000000000000000000000000000..2b549fbc89e1da5cdf67164f2c169197c6b86802 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/BbbApi.java @@ -0,0 +1,91 @@ +package org.bigbluebutton.vertx; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.BodyHandler; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author <a href="http://tfox.org">Tim Fox</a> + */ +public class BbbApi extends AbstractVerticle { + + + private Map<String, JsonObject> products = new HashMap<>(); + + @Override + public void start() { + + setUpInitialData(); + + Router router = Router.router(vertx); + + router.route().handler(BodyHandler.create()); + router.get("/products/:productID").handler(this::handleGetProduct); + router.put("/products/:productID").handler(this::handleAddProduct); + router.get("/products").handler(this::handleListProducts); + router.get("/bigbluebutton/api/create").handler(this::handleListProducts); + + vertx.createHttpServer().requestHandler(router::accept).listen(4000); + } + + private void handleGetProduct(RoutingContext routingContext) { + String productID = routingContext.request().getParam("productID"); + HttpServerResponse response = routingContext.response(); + if (productID == null) { + sendError(400, response); + } else { + JsonObject product = products.get(productID); + if (product == null) { + sendError(404, response); + } else { + response.putHeader("content-type", "application/json").end(product.encodePrettily()); + } + } + } + + private void handleAddProduct(RoutingContext routingContext) { + String productID = routingContext.request().getParam("productID"); + HttpServerResponse response = routingContext.response(); + if (productID == null) { + sendError(400, response); + } else { + JsonObject product = routingContext.getBodyAsJson(); + if (product == null) { + sendError(400, response); + } else { + products.put(productID, product); + response.end(); + } + } + } + + private void handleListProducts(RoutingContext routingContext) { + MultiMap params = routingContext.request().params(); + System.out.println("Name: " + params.get("name")); + JsonArray arr = new JsonArray(); + products.forEach((k, v) -> arr.add(v)); + routingContext.response().putHeader("content-type", "application/json").end(arr.encodePrettily()); + } + + private void sendError(int statusCode, HttpServerResponse response) { + response.setStatusCode(statusCode).end(); + } + + private void setUpInitialData() { + addProduct(new JsonObject().put("id", "prod3568").put("name", "Egg Whisk").put("price", 3.99).put("weight", 150)); + addProduct(new JsonObject().put("id", "prod7340").put("name", "Tea Cosy").put("price", 5.99).put("weight", 100)); + addProduct(new JsonObject().put("id", "prod8643").put("name", "Spatula").put("price", 1.00).put("weight", 80)); + } + + private void addProduct(JsonObject product) { + products.put(product.getString("id"), product); + } +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/BbbUser.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/BbbUser.java new file mode 100755 index 0000000000000000000000000000000000000000..ef476db9730c0b9f8b817c6e642a8be85001f737 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/BbbUser.java @@ -0,0 +1,50 @@ +package org.bigbluebutton.vertx; + +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.ext.auth.AbstractUser; +import io.vertx.ext.auth.AuthProvider; +import io.vertx.core.logging.Logger; +import io.vertx.core.logging.LoggerFactory; +import io.vertx.core.Future; + +public class BbbUser extends AbstractUser { + private static final Logger log = LoggerFactory.getLogger(BbbUser.class); + + private JsonObject jwtToken; + private JsonArray permissions; + + public BbbUser(JsonObject jwtToken, JsonArray permissions) { + this.jwtToken = jwtToken; + this.permissions = permissions; + } + + + @Override + public JsonObject principal() { + return jwtToken; + } + + @Override + public void setAuthProvider(AuthProvider arg0) { + // NOOP - JWT tokens are self contained :) + } + + @Override + protected void doIsPermitted(String permission, Handler<AsyncResult<Boolean>> handler) { + if (permissions != null) { + for (Object jwtPermission : permissions) { + if (permission.equals(jwtPermission)) { + handler.handle(Future.succeededFuture(true)); + return; + } + } + } + + log.debug("User has no permission [" + permission + "]"); + handler.handle(Future.succeededFuture(false)); + } + +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/ChatVerticle.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/ChatVerticle.java new file mode 100755 index 0000000000000000000000000000000000000000..0cf12e631034fce5b40f08d33ee5fba9ea1e2a98 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/ChatVerticle.java @@ -0,0 +1,75 @@ +package org.bigbluebutton.vertx; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.eventbus.EventBus; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.StaticHandler; +import io.vertx.ext.web.handler.sockjs.BridgeEventType; +import io.vertx.ext.web.handler.sockjs.BridgeOptions; +import io.vertx.ext.web.handler.sockjs.PermittedOptions; +import io.vertx.ext.web.handler.sockjs.SockJSHandler; +import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions; + +import java.text.DateFormat; +import java.time.Instant; +import java.util.Date; + +import org.bigbluebutton.VertxToAkkaGateway; + + +public class ChatVerticle extends AbstractVerticle { + + private final VertxToAkkaGateway gw; + + public ChatVerticle(VertxToAkkaGateway gw) { + this.gw = gw; + } + + @Override + public void start() throws Exception { + + Router router = Router.router(vertx); + + // Allow events for the designated addresses in/out of the event bus bridge + BridgeOptions opts = new BridgeOptions() + .addInboundPermitted(new PermittedOptions().setAddress("chat.to.server")) + .addOutboundPermitted(new PermittedOptions().setAddress("chat.to.client")); + + SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000); + + // Create the event bus bridge and add it to the router. + SockJSHandler ebHandler = SockJSHandler.create(vertx, options); + router.route("/eventbus/*").handler(ebHandler); + + // Create a router endpoint for the static content. + router.route().handler(StaticHandler.create()); + + ebHandler.bridge(opts, be -> { + if (be.type() == BridgeEventType.PUBLISH || be.type() == BridgeEventType.RECEIVE) { + if (be.rawMessage().getString("body").equals("armadillos")) { + // Reject it + be.complete(false); + return; + } + } + be.complete(true); + }); + + // Start the web server and tell it to use the router to handle requests. + vertx.createHttpServer().requestHandler(router::accept).listen(3001); + + EventBus eb = vertx.eventBus(); + + // Register to listen for messages coming IN to the server + eb.consumer("chat.to.server").handler(message -> { + // Create a timestamp string + String timestamp = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(Date.from(Instant.now())); + // Send the message back out to all clients with the timestamp prepended. + gw.send(timestamp + ": " + message.body()); + }); + + eb.consumer("to-vertx").handler(message -> { + eb.publish("chat.to.client", message.body()); + }); + } +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/HelloWorld.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/HelloWorld.java new file mode 100755 index 0000000000000000000000000000000000000000..560bf81a9fea68c7f7e548a7bfc190c6fad386e7 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/HelloWorld.java @@ -0,0 +1,31 @@ +package org.bigbluebutton.vertx; + +import org.bigbluebutton.VertxToAkkaGateway; + +import io.vertx.core.Vertx; + +public class HelloWorld { + + private final Vertx vertx; + private final VertxToAkkaGateway gw; + + public HelloWorld(Vertx vertx, VertxToAkkaGateway gw) { + this.vertx = vertx; + this.gw = gw; + } + + public void startup() { + // Create an HTTP server which simply returns "Hello World!" to each request. + //Vertx.vertx().createHttpServer().requestHandler(req -> req.response().end("Hello World! from gradle.")).listen(3000); + + //vertx.deployVerticle(new ChatVerticle(gw)); + //vertx.deployVerticle(new RealtimeVerticle()); + //vertx.deployVerticle(new AuthenticateVerticle()); + + vertx.deployVerticle(new PrivateVerticle(gw)); + //vertx.deployVerticle(new SimpleREST()); + //vertx.deployVerticle(new BbbApi()); + } + + +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/IAkkaToVertxGateway.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/IAkkaToVertxGateway.java new file mode 100755 index 0000000000000000000000000000000000000000..f53438117f62402808eced3215a000365174d628 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/IAkkaToVertxGateway.java @@ -0,0 +1,6 @@ +package org.bigbluebutton.vertx; + +public interface IAkkaToVertxGateway { + + void send(String json); +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/IVertxToAkkaGateway.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/IVertxToAkkaGateway.java new file mode 100755 index 0000000000000000000000000000000000000000..3f89a1af9ce21c83864d82af80f968a8088e3b60 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/IVertxToAkkaGateway.java @@ -0,0 +1,6 @@ +package org.bigbluebutton.vertx; + +public interface IVertxToAkkaGateway { + + void send(String json); +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/MyAuthProvider.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/MyAuthProvider.java new file mode 100755 index 0000000000000000000000000000000000000000..5473a9cd7a5e1e9cd2761cd64aa9fbd54cb127e1 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/MyAuthProvider.java @@ -0,0 +1,45 @@ +package org.bigbluebutton.vertx; + +import org.bigbluebutton.VertxToAkkaGateway; + +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.eventbus.DeliveryOptions; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.AuthProvider; +import io.vertx.ext.auth.User; +import io.vertx.core.Future; + +public class MyAuthProvider implements AuthProvider { + + private final Vertx vertx; + + public MyAuthProvider(Vertx vertx) { + this.vertx = vertx; + } + + @Override + public void authenticate(JsonObject user, Handler<AsyncResult<User>> resultHandler) { + JsonObject object = new JsonObject(); + object.put("foo", "bar").put("num", 123).put("mybool", true); + + JsonArray array = new JsonArray(); + array.add("foo").add(123).add(false); + + DeliveryOptions options = new DeliveryOptions(); + options.setSendTimeout(5000); + + vertx.eventBus().send("to-akka-gw", + "Yay! Someone kicked a ball across a patch of grass", + options, ar -> { + if (ar.succeeded()) { + System.out.println("Received reply: " + ar.result().body()); + System.out.println("Got Authenticated"); + resultHandler.handle(Future.succeededFuture(new BbbUser(object, array))); + } + }); + } + +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/PrivateVerticle.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/PrivateVerticle.java new file mode 100755 index 0000000000000000000000000000000000000000..0370bc4b993759efcb35c2daf953b487b2990ab6 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/PrivateVerticle.java @@ -0,0 +1,152 @@ +package org.bigbluebutton.vertx; + +import java.text.DateFormat; +import java.time.Instant; +import java.util.Date; + +import org.bigbluebutton.VertxToAkkaGateway; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.eventbus.EventBus; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.net.JksOptions; +import io.vertx.ext.auth.AuthProvider; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.Session; +import io.vertx.ext.web.handler.*; +import io.vertx.ext.web.handler.sockjs.BridgeEventType; +import io.vertx.ext.web.handler.sockjs.BridgeOptions; +import io.vertx.ext.web.handler.sockjs.PermittedOptions; +import io.vertx.ext.web.handler.sockjs.SockJSHandler; +import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions; +import io.vertx.ext.web.sstore.LocalSessionStore; + +public class PrivateVerticle extends AbstractVerticle { + private final VertxToAkkaGateway gw; + + public PrivateVerticle(VertxToAkkaGateway gw) { + this.gw = gw; + } + + @Override + public void start() throws Exception { + + Router router = Router.router(vertx); + + // We need cookies, sessions and request bodies + router.route().handler(CookieHandler.create()); + router.route().handler(BodyHandler.create()); + router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); + + // Simple auth service which uses a properties file for user/role info + //AuthProvider authProvider = new MyAuthProvider(vertx); + + // We need a user session handler too to make sure the user is stored in the session between requests + //router.route().handler(UserSessionHandler.create(authProvider)); + + // Handles the actual login + //router.route("/loginhandler").handler(FormLoginHandler.create(authProvider)); + + router.route("/private/*").handler(routingContext -> { + + // This will require a login + + // This will have the value true + boolean isAuthenticated = routingContext.user() != null; + + if (isAuthenticated) { + System.out.println("**** User is authenticated."); + } else { + System.out.println("**** User is NOT authenticated."); + } + Session session = routingContext.session(); + + Integer cnt = session.get("hitcount"); + cnt = (cnt == null ? 0 : cnt) + 1; + + session.put("hitcount", cnt); + +// routingContext.response().putHeader("content-type", "text/html") +// .end("<html><body><h1>Hitcount: " + cnt + "</h1></body></html>"); + routingContext.next(); + + }); + + // Any requests to URI starting '/private/' require login + //router.route("/private/*").handler(RedirectAuthHandler.create(authProvider, "/loginpage.html")); + + // Serve the static private pages from directory 'private' + //router.route("/private/*").handler(StaticHandler.create().setCachingEnabled(false).setWebRoot("private")); + + // Implement logout + router.route("/logout").handler(context -> { + context.clearUser(); + // Redirect back to the index page + context.response().putHeader("location", "/").setStatusCode(302).end(); + }); + + // Allow events for the designated addresses in/out of the event bus bridge + BridgeOptions opts = new BridgeOptions() + .addInboundPermitted(new PermittedOptions().setAddress("chat.to.server")) + .addOutboundPermitted(new PermittedOptions().setAddress("chat.to.client")); + + SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000); + + // Create the event bus bridge and add it to the router. + SockJSHandler ebHandler = SockJSHandler.create(vertx, options); + + router.route("/eventbus/*").handler(ebHandler); + +// SockJSHandlerFactory sockJsMessageHandler = new SockJSHandlerFactory(); +// sockJsMessageHandler.setupHandler(ebHandler, opts); + + EventBus eb = vertx.eventBus(); + + ebHandler.bridge(opts, be -> { + if (be.type() == BridgeEventType.SOCKET_CREATED) { + System.out.println("Socket create for session: " + be.socket().webSession().id() + " socketWriteId:" + be.socket().writeHandlerID()); + } else if (be.type() == BridgeEventType.SOCKET_CLOSED) { + System.out.println("Socket closed for: " + be.socket().webSession().id()); + } else if (be.type() == BridgeEventType.REGISTER) { + System.out.println("Register for: " + be.socket().webSession().id() + " \n " + be.rawMessage()); + eb.consumer("to-vertx").handler(message -> { + System.out.println("**** response to " + be.socket().webSession().id() + " msg = " + message.body()); + }); + gw.send(be.rawMessage().toString()); + } else { + System.out.println("Message from: " + be.socket().webSession().id() + " \n " + be.rawMessage()); + } + + // System.out.println("USER=" + be.socket().webUser().principal()); + + be.complete(true); + }); + + // Create a router endpoint for the static content. + router.route().handler(StaticHandler.create()); + + // Start the web server and tell it to use the router to handle requests. + //vertx.createHttpServer(new HttpServerOptions().setSsl(true).setKeyStoreOptions( + // new JksOptions().setPath("server-keystore.jks").setPassword("wibble") + // )).requestHandler(router::accept).listen(3001); + vertx.createHttpServer().requestHandler(router::accept).listen(3001); + + // Register to listen for messages coming IN to the server + eb.consumer("chat.to.server").handler(message -> { + // Create a timestamp string + String timestamp = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(Date.from(Instant.now())); + // Send the message back out to all clients with the timestamp prepended. + gw.send(timestamp + ": " + message.body()); + eb.publish("foofoofoo", message.body()); + }); + + eb.consumer("to-vertx").handler(message -> { + eb.publish("chat.to.client", message.body()); + }); + + + // Serve the non private static pages + router.route().handler(StaticHandler.create()); + } +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/RealtimeVerticle.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/RealtimeVerticle.java new file mode 100755 index 0000000000000000000000000000000000000000..55cb70152bd63d4aca9e1f1398d507b656df5cbc --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/RealtimeVerticle.java @@ -0,0 +1,44 @@ +package org.bigbluebutton.vertx; + +import io.vertx.core.AbstractVerticle; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.StaticHandler; +import io.vertx.ext.web.handler.sockjs.BridgeEventType; +import io.vertx.ext.web.handler.sockjs.BridgeOptions; +import io.vertx.ext.web.handler.sockjs.PermittedOptions; +import io.vertx.ext.web.handler.sockjs.SockJSHandler; + +public class RealtimeVerticle extends AbstractVerticle { + @Override + public void start() throws Exception { + + Router router = Router.router(vertx); + + // Allow outbound traffic to the news-feed address + + BridgeOptions options = new BridgeOptions().addOutboundPermitted(new PermittedOptions().setAddress("news-feed")); + + router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(options, event -> { + + // You can also optionally provide a handler like this which will be passed any events that occur on the bridge + // You can use this for monitoring or logging, or to change the raw messages in-flight. + // It can also be used for fine grained access control. + + if (event.type() == BridgeEventType.SOCKET_CREATED) { + System.out.println("A socket was created"); + } + + // This signals that it's ok to process the event + event.complete(true); + + })); + + // Serve the static resources + router.route().handler(StaticHandler.create()); + + vertx.createHttpServer().requestHandler(router::accept).listen(3000); + + // Publish a message to the address "news-feed" every second + vertx.setPeriodic(1000, t -> vertx.eventBus().publish("news-feed", "news from the server!")); + } +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/SimpleREST.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/SimpleREST.java new file mode 100755 index 0000000000000000000000000000000000000000..e71dd5f448b393e097ff765be1559767a816f2ef --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/SimpleREST.java @@ -0,0 +1,87 @@ +package org.bigbluebutton.vertx; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.BodyHandler; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author <a href="http://tfox.org">Tim Fox</a> + */ +public class SimpleREST extends AbstractVerticle { + + + private Map<String, JsonObject> products = new HashMap<>(); + + @Override + public void start() { + + setUpInitialData(); + + Router router = Router.router(vertx); + + router.route().handler(BodyHandler.create()); + router.get("/products/:productID").handler(this::handleGetProduct); + router.put("/products/:productID").handler(this::handleAddProduct); + router.get("/products").handler(this::handleListProducts); + + vertx.createHttpServer().requestHandler(router::accept).listen(4000); + } + + private void handleGetProduct(RoutingContext routingContext) { + String productID = routingContext.request().getParam("productID"); + HttpServerResponse response = routingContext.response(); + if (productID == null) { + sendError(400, response); + } else { + JsonObject product = products.get(productID); + if (product == null) { + sendError(404, response); + } else { + response.putHeader("content-type", "application/json").end(product.encodePrettily()); + } + } + } + + private void handleAddProduct(RoutingContext routingContext) { + String productID = routingContext.request().getParam("productID"); + HttpServerResponse response = routingContext.response(); + if (productID == null) { + sendError(400, response); + } else { + JsonObject product = routingContext.getBodyAsJson(); + if (product == null) { + sendError(400, response); + } else { + products.put(productID, product); + response.end(); + } + } + } + + private void handleListProducts(RoutingContext routingContext) { + JsonArray arr = new JsonArray(); + products.forEach((k, v) -> arr.add(v)); + routingContext.response().putHeader("content-type", "application/json").end(arr.encodePrettily()); + } + + private void sendError(int statusCode, HttpServerResponse response) { + response.setStatusCode(statusCode).end(); + } + + private void setUpInitialData() { + addProduct(new JsonObject().put("id", "prod3568").put("name", "Egg Whisk").put("price", 3.99).put("weight", 150)); + addProduct(new JsonObject().put("id", "prod7340").put("name", "Tea Cosy").put("price", 5.99).put("weight", 100)); + addProduct(new JsonObject().put("id", "prod8643").put("name", "Spatula").put("price", 1.00).put("weight", 80)); + } + + private void addProduct(JsonObject product) { + products.put(product.getString("id"), product); + } +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/SockJSHandlerFactory.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/SockJSHandlerFactory.java new file mode 100755 index 0000000000000000000000000000000000000000..560dad665f35fc12fa28c4d61cf9989f66786f74 --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/SockJSHandlerFactory.java @@ -0,0 +1,24 @@ +package org.bigbluebutton.vertx; + +import io.vertx.ext.web.handler.sockjs.BridgeEventType; +import io.vertx.ext.web.handler.sockjs.BridgeOptions; +import io.vertx.ext.web.handler.sockjs.SockJSHandler; + +public class SockJSHandlerFactory { + + public SockJSHandler setupHandler(SockJSHandler ebHandler, BridgeOptions opts) { + + ebHandler.bridge(opts, be -> { + if (be.type() == BridgeEventType.SOCKET_CREATED) { + System.out.println("Socket create for: " + be.socket().webSession().id()); + } else if (be.type() == BridgeEventType.SOCKET_CLOSED) { + System.out.println("Socket closed for: " + be.socket().webSession().id()); + } else { + System.out.println("Message from: " + be.socket().webSession().id() + " \n " + be.rawMessage()); + } + be.complete(true); + }); + + return ebHandler; + } +} diff --git a/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/VertxToAkkaBus.java b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/VertxToAkkaBus.java new file mode 100755 index 0000000000000000000000000000000000000000..d64539f3814034a88321f072322465b2f143a70f --- /dev/null +++ b/labs/vertx-akka/src/main/java/org/bigbluebutton/vertx/VertxToAkkaBus.java @@ -0,0 +1,39 @@ +package org.bigbluebutton.vertx; + + +import io.vertx.core.Vertx; +import io.vertx.core.eventbus.Message; +import io.vertx.core.eventbus.MessageConsumer; +import io.vertx.core.json.JsonObject; + +import org.bigbluebutton.VertxToAkkaGateway; + +public class VertxToAkkaBus { + + + public VertxToAkkaBus(Vertx vertx, VertxToAkkaGateway gw) { + + MessageConsumer<String> consumer = + vertx.eventBus().consumer("to-akka-gw"); + + consumer.handler(message -> { + System.out.println("I have received a message: " + message.body()); + if (message.replyAddress() != null) { + String replyChannel = "reply-channel"; + MessageConsumer<String> replyConsumer = + vertx.eventBus().consumer(replyChannel); + replyConsumer.handler(replyMessage -> { + System.out.println("Got Authenticated"); + + message.reply(replyMessage.body().toString()); + replyConsumer.unregister(); + }); + gw.sendWithReply(message.body().toString(), replyChannel); + } else { + gw.send(message.body().toString()); + } + + }); + + } +} diff --git a/labs/vertx-akka/src/main/resources/README b/labs/vertx-akka/src/main/resources/README new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/labs/vertx-akka/src/main/resources/application.conf b/labs/vertx-akka/src/main/resources/application.conf new file mode 100755 index 0000000000000000000000000000000000000000..1d1430f5e1300f67217777ddab4fad2a76936b04 --- /dev/null +++ b/labs/vertx-akka/src/main/resources/application.conf @@ -0,0 +1,34 @@ +akka { + actor { + debug { + # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill et.c.) + autoreceive = on + # enable DEBUG logging of actor lifecycle changes + lifecycle = on + } + } + loggers = ["akka.event.slf4j.Slf4jLogger"] + loglevel = "INFO" + + rediscala-publish-worker-dispatcher { + mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" + # Throughput defines the maximum number of messages to be + # processed per actor before the thread jumps to the next actor. + # Set to 1 for as fair as possible. + throughput = 512 + } + + rediscala-subscriber-worker-dispatcher { + mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" + # Throughput defines the maximum number of messages to be + # processed per actor before the thread jumps to the next actor. + # Set to 1 for as fair as possible. + throughput = 512 + } +} + +redis { + host="127.0.0.1" + port=6379 + password="" +} diff --git a/labs/vertx-akka/src/main/resources/logback.xml b/labs/vertx-akka/src/main/resources/logback.xml new file mode 100755 index 0000000000000000000000000000000000000000..267781bba2dd60859c0902fe61e8312243c0acbb --- /dev/null +++ b/labs/vertx-akka/src/main/resources/logback.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> +<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%date{ISO8601} %-5level %logger{36} - %msg%n</pattern> + </encoder> +</appender> + + <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <File>logs/bbb-apps-akka.log</File> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <FileNamePattern>logs/bbb-apps-akka.%d{yyyy-MM-dd}.log</FileNamePattern> + <!-- keep 30 days worth of history --> + <MaxHistory>5</MaxHistory> + </rollingPolicy> + <layout class="ch.qos.logback.classic.PatternLayout"> + <Pattern>%d{"yyyy-MM-dd HH:mm:ss,SSSXXX"} [%thread] %-5level %logger{35} - %msg%n</Pattern> + </layout> + </appender> + + <logger name="akka" level="INFO" /> + <logger name="org.bigbluebutton" level="DEBUG" /> + + <root level="DEBUG"> + <appender-ref ref="STDOUT"/> + <appender-ref ref="FILE" /> + </root> +</configuration> \ No newline at end of file diff --git a/labs/vertx-akka/src/main/scala/org/bigbluebutton/AuthService.scala b/labs/vertx-akka/src/main/scala/org/bigbluebutton/AuthService.scala new file mode 100755 index 0000000000000000000000000000000000000000..aa7d7b11ca13ac83e8f38080bbe246acbefa9dc7 --- /dev/null +++ b/labs/vertx-akka/src/main/scala/org/bigbluebutton/AuthService.scala @@ -0,0 +1,24 @@ +package org.bigbluebutton + +import io.vertx.core.Vertx +import akka.actor._ +import akka.actor.ActorLogging +import org.bigbluebutton.vertx.IAkkaToVertxGateway +import org.bigbluebutton.vertx.AkkaToVertxGateway + +object AuthService { + def props(gw: AkkaToVertxGateway): Props = + Props(classOf[AuthService], gw) +} + +class AuthService(gw: AkkaToVertxGateway) + extends Actor with ActorLogging { + + def receive = { + case msg: String => { + println("****** Authenticating " + msg) + sender ! "Let `em in!" + } + case _ => log.error("Cannot handle message ") + } +} \ No newline at end of file diff --git a/labs/vertx-akka/src/main/scala/org/bigbluebutton/Boot.scala b/labs/vertx-akka/src/main/scala/org/bigbluebutton/Boot.scala new file mode 100755 index 0000000000000000000000000000000000000000..b21cbaa918d1669c35e6d3c2ae3ee968d1413acf --- /dev/null +++ b/labs/vertx-akka/src/main/scala/org/bigbluebutton/Boot.scala @@ -0,0 +1,30 @@ +package org.bigbluebutton + +import akka.actor.{ ActorSystem, Props } +import scala.concurrent.duration._ +import scala.concurrent.{ Future, Await } +import scala.concurrent.ExecutionContext.Implicits.global +import org.bigbluebutton.vertx.HelloWorld +import io.vertx.core.Vertx +import org.bigbluebutton.vertx.AkkaToVertxGateway +import org.bigbluebutton.vertx.IVertxToAkkaGateway +import org.bigbluebutton.vertx.VertxToAkkaBus + +object Boot extends App with SystemConfiguration { + + implicit val system = ActorSystem("vertx-akka-system") + + val vertx = Vertx.vertx() + + val vertxGW = new AkkaToVertxGateway(vertx) + + val echoActor = system.actorOf(EchoService.props(vertxGW), "echo-actor") + val authActor = system.actorOf(AuthService.props(vertxGW), "auth-actor") + + val akkaGW = new VertxToAkkaGateway(system, vertx, authActor, echoActor) + val vertxToAkkaBus = new VertxToAkkaBus(vertx, akkaGW) + + val hello = new HelloWorld(vertx, akkaGW); + hello.startup() + +} \ No newline at end of file diff --git a/labs/vertx-akka/src/main/scala/org/bigbluebutton/EchoService.scala b/labs/vertx-akka/src/main/scala/org/bigbluebutton/EchoService.scala new file mode 100755 index 0000000000000000000000000000000000000000..1df6f134c41ffb5d0bac076b18aa418ff7ed72a2 --- /dev/null +++ b/labs/vertx-akka/src/main/scala/org/bigbluebutton/EchoService.scala @@ -0,0 +1,23 @@ +package org.bigbluebutton + +import io.vertx.core.Vertx +import akka.actor._ +import akka.actor.ActorLogging +import org.bigbluebutton.vertx.IAkkaToVertxGateway +import org.bigbluebutton.vertx.AkkaToVertxGateway + +object EchoService { + def props(gw: AkkaToVertxGateway): Props = + Props(classOf[EchoService], gw) +} + +class EchoService(gw: AkkaToVertxGateway) extends Actor with ActorLogging { + + def receive = { + case msg: String => { + println("****** Echoing " + msg) + gw.send(msg) + } + case _ => log.error("Cannot handle message ") + } +} \ No newline at end of file diff --git a/labs/vertx-akka/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/labs/vertx-akka/src/main/scala/org/bigbluebutton/SystemConfiguration.scala new file mode 100755 index 0000000000000000000000000000000000000000..663c885576f2a232677c7ae3aff22646eff5d84b --- /dev/null +++ b/labs/vertx-akka/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -0,0 +1,13 @@ +package org.bigbluebutton + +import com.typesafe.config.ConfigFactory +import scala.util.Try + +trait SystemConfiguration { + + val config = ConfigFactory.load() + + lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") + lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) + lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("") +} \ No newline at end of file diff --git a/labs/vertx-akka/src/main/scala/org/bigbluebutton/VertxToAkkaGateway.scala b/labs/vertx-akka/src/main/scala/org/bigbluebutton/VertxToAkkaGateway.scala new file mode 100755 index 0000000000000000000000000000000000000000..01976ea0338bfe0218421996dc3826a36eb94318 --- /dev/null +++ b/labs/vertx-akka/src/main/scala/org/bigbluebutton/VertxToAkkaGateway.scala @@ -0,0 +1,49 @@ +package org.bigbluebutton + +import org.bigbluebutton.vertx.IAkkaToVertxGateway +import akka.pattern.{ ask, pipe } +import akka.util.Timeout +import scala.concurrent.duration._ +import scala.util.Success +import scala.util.Failure +import akka.actor.ActorRef +import io.vertx.core.Vertx +import io.vertx.core.eventbus.MessageConsumer +import io.vertx.core.Handler +import io.vertx.core.eventbus.Message +import akka.actor.ActorSystem + +class VertxToAkkaGateway(system: ActorSystem, vertx: Vertx, + authService: ActorRef, + echoService: ActorRef) extends IAkkaToVertxGateway { + implicit def executionContext = system.dispatcher + + val consumer: MessageConsumer[String] = vertx.eventBus().consumer("foofoofoo") + + def handle(m: Message[String]) = println(m.body()) + + consumer.handler(new MyHandler()) + + def sendWithReply(json: String, replyChannel: String) { + val future = authService.ask(json)(5 seconds) + + future onComplete { + case Success(result) => { + vertx.eventBus().send("reply-channel", "You can come in.") + } + case Failure(failure) => { + vertx.eventBus().send("reply-channel", "You can NOT come in.") + } + } + } + + def send(json: String) { + echoService ! json + } +} + +class MyHandler extends Handler[Message[String]] { + def handle(message: Message[String]) = { + println("My Handler " + message.body()) + } +} \ No newline at end of file diff --git a/labs/vertx-akka/src/main/webapp/private/chat.html b/labs/vertx-akka/src/main/webapp/private/chat.html new file mode 100755 index 0000000000000000000000000000000000000000..3caf52f2e56267a805e8d7e03d3571e14be62ff8 --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/private/chat.html @@ -0,0 +1,76 @@ +<!-- + #%L + distributed-chat-service + %% + Copyright (C) 2015 Zanclus Consulting + %% + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + #L% + --> +<html> +<head> + <title>Distributed Chat Service</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <script src="https://code.jquery.com/jquery-1.11.2.min.js"></script> + <script src="//cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script> + <script src="vertxbus.js"></script> + <style> + .inset { + box-shadow: inset 0 0 4px #000000; + -moz-box-shadow: inset 0 0 4px #000000; + -webkit-box-shadow: inset 0 0 4px #000000; + width: 400px; + border-width: 4px; + padding: 5px; + } + + input.inset { + height: 40px; + } + + div.inset { + height: 500px; + white-space: pre-wrap + } + </style> +</head> +<body> +<script> + //var eb = new vertx.EventBus("https://192.168.23.33:3001/eventbus"); + var eb = new vertx.EventBus("http://192.168.246.131:3001/eventbus"); + eb.onopen = function () { + eb.registerHandler("chat.to.client", function (msg) { + $('#chat').append(msg + "\n"); + }); + + eb.send("foo-bar", "ValidateAuthToken", function(msg) { + $('#chat').append("reply: " + msg + "\n"); + }); + }; + + function send(event) { + if (event.keyCode == 13 || event.which == 13) { + var message = $('#input').val(); + if (message.length > 0) { + console.log($('#input')); + eb.publish("chat.to.server", message); + $('#input').val(""); + } + } + } +</script> +<div id="chat" class="inset"></div> +<input id="input" type="text" onkeydown="send(event)" class="inset"> +</body> +</html> diff --git a/labs/vertx-akka/src/main/webapp/private/private_page.html b/labs/vertx-akka/src/main/webapp/private/private_page.html new file mode 100755 index 0000000000000000000000000000000000000000..9f57655c64b97b75f278deb51cc737255eae39ff --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/private/private_page.html @@ -0,0 +1,19 @@ +<html> + +<head> + <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/> + <meta http-equiv="Pragma" content="no-cache"/> + <meta http-equiv="Expires" content="0"/> +</head> + +<body> + +<h2>You can only see this page if you are logged in!</h2> + +<br> +<br> +<a href="/logout">Logout</a> + +</body> + +</html> \ No newline at end of file diff --git a/labs/vertx-akka/src/main/webapp/private/vertxbus.js b/labs/vertx-akka/src/main/webapp/private/vertxbus.js new file mode 100755 index 0000000000000000000000000000000000000000..59df3af1ad0d1abe10af5989d2f1179f0e7d25d8 --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/private/vertxbus.js @@ -0,0 +1,228 @@ +/* + * Copyright 2014 Red Hat, Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ + +/* + * Copyright (c) 2011-2013 The original author or authors + * ------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ + +var vertx = vertx || {}; + +!function(factory) { + if (typeof define === "function" && define.amd) { + // Expose as an AMD module with SockJS dependency. + // "vertxbus" and "sockjs" names are used because + // AMD module names are derived from file names. + define("vertxbus", ["sockjs"], factory); + } else { + // No AMD-compliant loader + factory(SockJS); + } +}(function(SockJS) { + + vertx.EventBus = function(url, options) { + + var that = this; + var sockJSConn = new SockJS(url, undefined, options); + var handlerMap = {}; + var replyHandlers = {}; + var state = vertx.EventBus.CONNECTING; + var pingTimerID = null; + var pingInterval = null; + if (options) { + pingInterval = options['vertxbus_ping_interval']; + } + if (!pingInterval) { + pingInterval = 5000; + } + + that.onopen = null; + that.onclose = null; + + that.send = function(address, message, replyHandler) { + sendOrPub("send", address, message, replyHandler) + } + + that.publish = function(address, message) { + sendOrPub("publish", address, message, null) + } + + that.registerHandler = function(address, handler) { + checkSpecified("address", 'string', address); + checkSpecified("handler", 'function', handler); + checkOpen(); + var handlers = handlerMap[address]; + if (!handlers) { + handlers = [handler]; + handlerMap[address] = handlers; + // First handler for this address so we should register the connection + var msg = { type : "register", + address: address }; + sockJSConn.send(JSON.stringify(msg)); + } else { + handlers[handlers.length] = handler; + } + } + + that.unregisterHandler = function(address, handler) { + checkSpecified("address", 'string', address); + checkSpecified("handler", 'function', handler); + checkOpen(); + var handlers = handlerMap[address]; + if (handlers) { + var idx = handlers.indexOf(handler); + if (idx != -1) handlers.splice(idx, 1); + if (handlers.length == 0) { + // No more local handlers so we should unregister the connection + + var msg = { type : "unregister", + address: address}; + sockJSConn.send(JSON.stringify(msg)); + delete handlerMap[address]; + } + } + } + + that.close = function() { + checkOpen(); + state = vertx.EventBus.CLOSING; + sockJSConn.close(); + } + + that.readyState = function() { + return state; + } + + sockJSConn.onopen = function() { + // Send the first ping then send a ping every pingInterval milliseconds + sendPing(); + pingTimerID = setInterval(sendPing, pingInterval); + state = vertx.EventBus.OPEN; + if (that.onopen) { + that.onopen(); + } + }; + + sockJSConn.onclose = function() { + state = vertx.EventBus.CLOSED; + if (pingTimerID) clearInterval(pingTimerID); + if (that.onclose) { + that.onclose(); + } + }; + + sockJSConn.onmessage = function(e) { + var msg = e.data; + var json = JSON.parse(msg); + var type = json.type; + if (type === 'err') { + console.error("Error received on connection: " + json.body); + return; + } + var body = json.body; + var replyAddress = json.replyAddress; + var address = json.address; + var replyHandler; + if (replyAddress) { + replyHandler = function(reply, replyHandler) { + // Send back reply + that.send(replyAddress, reply, replyHandler); + }; + } + var handlers = handlerMap[address]; + if (handlers) { + // We make a copy since the handler might get unregistered from within the + // handler itself, which would screw up our iteration + var copy = handlers.slice(0); + for (var i = 0; i < copy.length; i++) { + copy[i](body, replyHandler); + } + } else { + // Might be a reply message + var handler = replyHandlers[address]; + if (handler) { + delete replyHandlers[address]; + handler(body, replyHandler); + } + } + } + + function sendPing() { + var msg = { + type: "ping" + } + sockJSConn.send(JSON.stringify(msg)); + } + + function sendOrPub(sendOrPub, address, message, replyHandler) { + checkSpecified("address", 'string', address); + checkSpecified("replyHandler", 'function', replyHandler, true); + checkOpen(); + var envelope = { type : sendOrPub, + address: address, + body: message }; + if (replyHandler) { + var replyAddress = makeUUID(); + envelope.replyAddress = replyAddress; + replyHandlers[replyAddress] = replyHandler; + } + var str = JSON.stringify(envelope); + sockJSConn.send(str); + } + + function checkOpen() { + if (state != vertx.EventBus.OPEN) { + throw new Error('INVALID_STATE_ERR'); + } + } + + function checkSpecified(paramName, paramType, param, optional) { + if (!optional && !param) { + throw new Error("Parameter " + paramName + " must be specified"); + } + if (param && typeof param != paramType) { + throw new Error("Parameter " + paramName + " must be of type " + paramType); + } + } + + function isFunction(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + } + + function makeUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + .replace(/[xy]/g,function(a,b){return b=Math.random()*16,(a=="y"?b&3|8:b|0).toString(16)})} + + } + + vertx.EventBus.CONNECTING = 0; + vertx.EventBus.OPEN = 1; + vertx.EventBus.CLOSING = 2; + vertx.EventBus.CLOSED = 3; + + return vertx.EventBus; + +}); diff --git a/labs/vertx-akka/src/main/webapp/webroot/index.html b/labs/vertx-akka/src/main/webapp/webroot/index.html new file mode 100755 index 0000000000000000000000000000000000000000..f4106abb7605f76a432054f0d37f2f409ca43c88 --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/webroot/index.html @@ -0,0 +1,15 @@ +<html> + +<body> + +<h1>Web site with public and private pages</h1> + +<br> +<br> +<a href="private/chat.html">Private page - login is required</a> + +<br><br> + +<a href="page1.html">Public page - no login required</a> +</body> +</html> diff --git a/labs/vertx-akka/src/main/webapp/webroot/loginpage.html b/labs/vertx-akka/src/main/webapp/webroot/loginpage.html new file mode 100755 index 0000000000000000000000000000000000000000..8549635298ea2d1381e9e8a03313b90619f97b52 --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/webroot/loginpage.html @@ -0,0 +1,29 @@ +<html> +<head> + <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/> + <meta http-equiv="Pragma" content="no-cache"/> + <meta http-equiv="Expires" content="0"/> +</head> +<body> +<h2>Please login</h2><br> + +<br> +(Username is 'tim', password is 'sausages') +<br> +<br> + +<form action="/loginhandler" method="post"> + <div> + <label>Username:</label> + <input type="text" name="username"/> + </div> + <div> + <label>Password:</label> + <input type="password" name="password"/> + </div> + <div> + <input type="submit" value="Log In"/> + </div> +</form> +</body> +</html> \ No newline at end of file diff --git a/labs/vertx-akka/src/main/webapp/webroot/page1.html b/labs/vertx-akka/src/main/webapp/webroot/page1.html new file mode 100755 index 0000000000000000000000000000000000000000..3ad26717225f5a41702e378b5e5aa7a3e4280a1d --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/webroot/page1.html @@ -0,0 +1,11 @@ +<html> +<head> + <title></title> +</head> +<body> +<h1>Welcome to page1!</h1> + +<br> +This page does not require login - anyone can see it +</body> +</html> \ No newline at end of file diff --git a/labs/vertx-akka/src/main/webapp/webroot/realtime.html b/labs/vertx-akka/src/main/webapp/webroot/realtime.html new file mode 100755 index 0000000000000000000000000000000000000000..83bf5f519b8b9965aaa53fc0c543feab927fd52c --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/webroot/realtime.html @@ -0,0 +1,35 @@ +<html> +<head> + <title></title> + <script src="https://code.jquery.com/jquery-1.11.2.min.js"></script> + <script src="https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script> + <script src="vertxbus.js"></script> +</head> + +<style> + .news { + font-size: 20pt; + } +</style> + +<body> + +<div class="news">Latest news: </div><br> +<div id="status" class="news"></div> + +<script> + + var eb = new vertx.EventBus("http://192.168.23.33:3000/eventbus"); + + eb.onopen = function() { + eb.registerHandler("news-feed", function(msg) { + var str = "<code>" + msg + "</code><br>"; + $('#status').prepend(str); + }) + } + + +</script> + +</body> +</html> diff --git a/labs/vertx-akka/src/main/webapp/webroot/vertxbus.js b/labs/vertx-akka/src/main/webapp/webroot/vertxbus.js new file mode 100755 index 0000000000000000000000000000000000000000..59df3af1ad0d1abe10af5989d2f1179f0e7d25d8 --- /dev/null +++ b/labs/vertx-akka/src/main/webapp/webroot/vertxbus.js @@ -0,0 +1,228 @@ +/* + * Copyright 2014 Red Hat, Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ + +/* + * Copyright (c) 2011-2013 The original author or authors + * ------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ + +var vertx = vertx || {}; + +!function(factory) { + if (typeof define === "function" && define.amd) { + // Expose as an AMD module with SockJS dependency. + // "vertxbus" and "sockjs" names are used because + // AMD module names are derived from file names. + define("vertxbus", ["sockjs"], factory); + } else { + // No AMD-compliant loader + factory(SockJS); + } +}(function(SockJS) { + + vertx.EventBus = function(url, options) { + + var that = this; + var sockJSConn = new SockJS(url, undefined, options); + var handlerMap = {}; + var replyHandlers = {}; + var state = vertx.EventBus.CONNECTING; + var pingTimerID = null; + var pingInterval = null; + if (options) { + pingInterval = options['vertxbus_ping_interval']; + } + if (!pingInterval) { + pingInterval = 5000; + } + + that.onopen = null; + that.onclose = null; + + that.send = function(address, message, replyHandler) { + sendOrPub("send", address, message, replyHandler) + } + + that.publish = function(address, message) { + sendOrPub("publish", address, message, null) + } + + that.registerHandler = function(address, handler) { + checkSpecified("address", 'string', address); + checkSpecified("handler", 'function', handler); + checkOpen(); + var handlers = handlerMap[address]; + if (!handlers) { + handlers = [handler]; + handlerMap[address] = handlers; + // First handler for this address so we should register the connection + var msg = { type : "register", + address: address }; + sockJSConn.send(JSON.stringify(msg)); + } else { + handlers[handlers.length] = handler; + } + } + + that.unregisterHandler = function(address, handler) { + checkSpecified("address", 'string', address); + checkSpecified("handler", 'function', handler); + checkOpen(); + var handlers = handlerMap[address]; + if (handlers) { + var idx = handlers.indexOf(handler); + if (idx != -1) handlers.splice(idx, 1); + if (handlers.length == 0) { + // No more local handlers so we should unregister the connection + + var msg = { type : "unregister", + address: address}; + sockJSConn.send(JSON.stringify(msg)); + delete handlerMap[address]; + } + } + } + + that.close = function() { + checkOpen(); + state = vertx.EventBus.CLOSING; + sockJSConn.close(); + } + + that.readyState = function() { + return state; + } + + sockJSConn.onopen = function() { + // Send the first ping then send a ping every pingInterval milliseconds + sendPing(); + pingTimerID = setInterval(sendPing, pingInterval); + state = vertx.EventBus.OPEN; + if (that.onopen) { + that.onopen(); + } + }; + + sockJSConn.onclose = function() { + state = vertx.EventBus.CLOSED; + if (pingTimerID) clearInterval(pingTimerID); + if (that.onclose) { + that.onclose(); + } + }; + + sockJSConn.onmessage = function(e) { + var msg = e.data; + var json = JSON.parse(msg); + var type = json.type; + if (type === 'err') { + console.error("Error received on connection: " + json.body); + return; + } + var body = json.body; + var replyAddress = json.replyAddress; + var address = json.address; + var replyHandler; + if (replyAddress) { + replyHandler = function(reply, replyHandler) { + // Send back reply + that.send(replyAddress, reply, replyHandler); + }; + } + var handlers = handlerMap[address]; + if (handlers) { + // We make a copy since the handler might get unregistered from within the + // handler itself, which would screw up our iteration + var copy = handlers.slice(0); + for (var i = 0; i < copy.length; i++) { + copy[i](body, replyHandler); + } + } else { + // Might be a reply message + var handler = replyHandlers[address]; + if (handler) { + delete replyHandlers[address]; + handler(body, replyHandler); + } + } + } + + function sendPing() { + var msg = { + type: "ping" + } + sockJSConn.send(JSON.stringify(msg)); + } + + function sendOrPub(sendOrPub, address, message, replyHandler) { + checkSpecified("address", 'string', address); + checkSpecified("replyHandler", 'function', replyHandler, true); + checkOpen(); + var envelope = { type : sendOrPub, + address: address, + body: message }; + if (replyHandler) { + var replyAddress = makeUUID(); + envelope.replyAddress = replyAddress; + replyHandlers[replyAddress] = replyHandler; + } + var str = JSON.stringify(envelope); + sockJSConn.send(str); + } + + function checkOpen() { + if (state != vertx.EventBus.OPEN) { + throw new Error('INVALID_STATE_ERR'); + } + } + + function checkSpecified(paramName, paramType, param, optional) { + if (!optional && !param) { + throw new Error("Parameter " + paramName + " must be specified"); + } + if (param && typeof param != paramType) { + throw new Error("Parameter " + paramName + " must be of type " + paramType); + } + } + + function isFunction(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + } + + function makeUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + .replace(/[xy]/g,function(a,b){return b=Math.random()*16,(a=="y"?b&3|8:b|0).toString(16)})} + + } + + vertx.EventBus.CONNECTING = 0; + vertx.EventBus.OPEN = 1; + vertx.EventBus.CLOSING = 2; + vertx.EventBus.CLOSED = 3; + + return vertx.EventBus; + +}); diff --git a/labs/vertx-akka/src/templates/etc-default b/labs/vertx-akka/src/templates/etc-default new file mode 100755 index 0000000000000000000000000000000000000000..ce68a7ccfc47b3928f42050eae4639163e39286e --- /dev/null +++ b/labs/vertx-akka/src/templates/etc-default @@ -0,0 +1 @@ +JAVA_OPTS="-Dconfig.file=${{chdir}}/conf/application.conf $JAVA_OPTS" diff --git a/labs/vertx-akka/src/test/java/README b/labs/vertx-akka/src/test/java/README new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/labs/vertx-akka/src/test/resources/README b/labs/vertx-akka/src/test/resources/README new file mode 100644 index 0000000000000000000000000000000000000000..2c6bd342ed02205663cd55e21bb3889809f2a23f --- /dev/null +++ b/labs/vertx-akka/src/test/resources/README @@ -0,0 +1,8 @@ + +1. Install httpie (https://github.com/jkbr/httpie) + +sudo apt-get install python-setuptools +sudo easy_install httpie + +2. Send messages stored in messages/ directory + e.g. http POST 192.168.22.146:8989/meeting < create-meeting.json diff --git a/labs/vertx-akka/src/test/resources/messages/create-meeting.json b/labs/vertx-akka/src/test/resources/messages/create-meeting.json new file mode 100644 index 0000000000000000000000000000000000000000..cc7b3745f0fb7b58a7a21f84473a89b68c8a053d --- /dev/null +++ b/labs/vertx-akka/src/test/resources/messages/create-meeting.json @@ -0,0 +1,49 @@ + { + "header": { + "destination": { + "to": "apps_channel" + }, + "reply": { + "to": "apps_channel", + "correlation_id": "abc" + }, + "name": "create_meeting_request", + "timestamp": "2013-12-23T08:50Z", + "source": "web-api" + }, + "payload": { + "meeting_descriptor": { + "name": "English 101", + "external_id": "english_101", + "record": true, + "welcome_message": "Welcome to English 101", + "logout_url": "http://www.bigbluebutton.org", + "avatar_url": "http://www.gravatar.com/bigbluebutton", + "max_users": 20, + "duration": { + "length": 120, + "allow_extend": false, + "max": 240 + }, + "voice_conference": { + "pin": 123456, + "number": 85115 + }, + "phone_numbers": [ + { + "number": "613-520-7600", + "description": "Ottawa" + }, + { + "number": "1-888-555-7890", + "description": "NA Toll-Free" + } + ], + "metadata": { + "customer_id": "acme-customer", + "customer_name": "ACME" + } + } + } + } + diff --git a/labs/vertx-akka/src/test/resources/messages/register-user-request.json b/labs/vertx-akka/src/test/resources/messages/register-user-request.json new file mode 100644 index 0000000000000000000000000000000000000000..e4c4d0bd6cc26c7f136ca4d275f7a5ea2e100e9c --- /dev/null +++ b/labs/vertx-akka/src/test/resources/messages/register-user-request.json @@ -0,0 +1,30 @@ +{ + "header": { + "destination": { + "to": "apps_channel" + }, + "reply": { + "to": "apps_channel", + "correlation_id": "abc" + }, + "name": "register_user_request", + "timestamp": "2013-12-23T08:50Z", + "source": "bbb-web" + }, + "payload": { + "meeting": { + "name": "English 101", + "id": "english_101" + }, + "session": "english_101-12345", + "user_descriptor": { + "external_id": "user1", + "name": "Guga", + "role": "MODERATOR", + "pin": 12345, + "welcome_message": "Welcome to English 101", + "logout_url": "http://www.example.com", + "avatar_url": "http://www.example.com/avatar.png" + } + } +} diff --git a/labs/vertx-akka/src/universal/conf/application.ini b/labs/vertx-akka/src/universal/conf/application.ini new file mode 100644 index 0000000000000000000000000000000000000000..85777a6ed706bbae98b987fb9fc190b5eeef7e17 --- /dev/null +++ b/labs/vertx-akka/src/universal/conf/application.ini @@ -0,0 +1,42 @@ +# ################################# +# ##### Default configuration ##### +# ################################# + +# Available replacements +# ------------------------------------------------ +# ${{author}} debian author +# ${{descr}} debian package description +# ${{exec}} startup script name +# ${{chdir}} app directory +# ${{retries}} retries for startup +# ${{retryTimeout}} retry timeout +# ${{app_name}} normalized app name +# ${{daemon_user}} daemon user +# ------------------------------------------------- +# DEPRECATED, use -J-Xmx1024m instead +# -mem 1024 + +# Setting -X directly (-J is stripped) +# -J-X +# -J-Xmx1024 + +# Add additional jvm parameters +# -Dkey=val + +# For play applications you may set +# -Dpidfile.path=/var/run/${{app_name}}/play.pid + +# Turn on JVM debugging, open at the given port +# -jvm-debug <port> + +# Don't run the java version check +# -no-version-check + +-J-Xms130m +-J-Xmx256m + +# With universal:packageBin: +# - setup with a configuration tool after unzip +# - use the path to the application.ini file +# -Dconfig.file=${{path_to}}/conf/application.conf +