{"id":398,"date":"2020-05-25T10:18:44","date_gmt":"2020-05-25T10:18:44","guid":{"rendered":"http:\/\/spiderwiz.org\/project\/?page_id=398"},"modified":"2025-04-09T06:15:57","modified_gmt":"2025-04-09T06:15:57","slug":"lesson-3","status":"publish","type":"page","link":"https:\/\/spiderwiz.org\/project\/tutorial\/lesson-3\/","title":{"rendered":"Lesson 3: Going World Wide with a WebSocket Hub"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In the previous lessons we saw how Spiderwiz worked with a command line application. We connected a producer and a consumer directly over TCP\/IP. In this lesson we will create a web application and use it as a hub to connect client producers and consumers over WebSockets. We will use the implementation to demonstrate a simple chat service.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We start by using the IDE to create a Java Web Application project. We call it <code>MyHub<\/code>. Since it will act as a WebSocket server we need to add the following dependency, as mentioned in <a href=\"http:\/\/spiderwiz.org\/project\/download\/\">Getting Spiderwiz<\/a>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>&lt;dependency&gt;\n  \t&lt;groupId&gt;org.spiderwiz&lt;\/groupId&gt;\n  \t&lt;artifactId&gt;spiderwiz-websocket-server&lt;\/artifactId&gt;\n  \t&lt;version&gt;1.2&lt;\/version&gt;\n&lt;\/dependency&gt;<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Our first class, <code>HubMain<\/code>, is an extension of Spiderwiz <code>Main<\/code> class. Here it goes:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson3;\n\nimport org.spiderwiz.core.Main;\n\n\/**\n * Main class for MyHub. As a hub, we neither produce nor consume any object.\n *\/\npublic class HubMain extends Main{\n    private static final String ROOT_DIRECTORY = &quot;\/tests\/MyHub&quot;;\n    private static final String CONFIG_FILE_NAME = &quot;hub.conf&quot;;\n    private static final String APP_NAME = &quot;My Hub&quot;;\n    private static final String APP_VERSION = &quot;Z2.01&quot;;  \/\/ Version Z2.01: First working version\n\n    public HubMain() {\n        super(ROOT_DIRECTORY, CONFIG_FILE_NAME, APP_NAME, APP_VERSION);\n    }\n\n    \/**\n     * @return an empty list as we produce nothing\n     *\/\n    @Override\n    protected String[] getProducedObjects() {\n        return new String[]{};\n    }\n\n    \/**\n     * @@return an empty list as we consume nothing\n     *\/\n    @Override\n    protected String[] getConsumedObjects() {\n        return new String[]{};\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">As you see, not much. We define a root directory, a configuration file name, application name and version, and return empty lists in <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getProducedObjects()\">getProducedObjects()<\/a> and <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getConsumedObjects()\">getConsumedObjects()<\/a>. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To run the application on our website we need to initialize it from a servlet. Here is <code>RootServlet<\/code> class: <\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson3;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\n\n\/**\n * A root servlet for spawning a Spiderwiz application\n *\/\n@WebServlet(name = &quot;RootServlet&quot;, urlPatterns = {&quot;\/RootServlet&quot;}, loadOnStartup = 1)\npublic class RootServlet extends HttpServlet {\n\n    \/**\n     * Instantiate and initialize the Spiderwiz runtime\n     * @throws ServletException\n     *\/\n    @Override\n    public void init() throws ServletException {\n        super.init();\n        new HubMain().init();\n    }\n\n    \/**\n     * Do Spiderwiz cleanup on application termination\n     *\/\n    @Override\n    public void destroy() {\n        HubMain.getInstance().cleanup();\n        super.destroy();\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><code>RootServlet<\/code> implements a servlet that loads on startup, initializes <code>HubMain<\/code> and calls its <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#cleanup()\">cleanup()<\/a> method when the servlet is destroyed. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Actually we are done! If you wonder where all the WebSocket server stuff is, recall that we still have to provide a <code>hub.conf<\/code> file. There, in one line (two actually), is where we define our hub as a WebSocket server: <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[log folder]Logs\n[consumer server-1]websocket;ping-rate=30\n[producer server-1]websocket;ping-rate=30\n[hub mode]yes<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Everything is in the definitions of the server as <code>websocket<\/code>. There are two property lines that do that, because our hub can be connected by both <em>Producer<\/em> and <em>Consumer<\/em> applications, so we define it as a <code>consumer server<\/code> for the first and <code>producer server<\/code> for the latter. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note that besides defining each server as \u201cwebsocket\u201d, we add the parameter \u201c<code>ping-rate=30<\/code>\u201d. This is done because many web servers drop WebSocket connections that are idle for too long. The parameter tells the Spiderwiz engine to ping these connections every 30 seconds, therefore keeping them busy so they would not be dropped even if there is no activity for a while. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last configuration line sets <code>hub mode<\/code> to <code>yes<\/code>. This is needed because, since the application neither produces nor consumes anything, without it it will neither get nor send any data. The definition as <code>hub mode<\/code> tells the system to pass objects of any type through the hub, which routes them between producers and consumers similarly to an IP router. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Go ahead and deploy the project. You will see the following in your web server\u2019s log file: <\/p>\n\n\n\n<pre>My Hub ver. Z2.01 (core version Z2.42) has been initiated\nsuccessfully\nProducer is listening to consumers on WebSockets\nConsumer is listening to producers on WebSockets\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">We are done with the hub server, now to the clients. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The main ingredient of our chat application is the <code>Chat<\/code> data object: <\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.objectLib;\n\nimport org.spiderwiz.annotation.WizField;\nimport org.spiderwiz.core.DataObject;\n\n\/**\n * Chat class for Spiderwiz tutorial's lesson 2.\n *\/\npublic class Chat extends DataObject {\n    \/**\n     * Mandatory public static field for all data objects.\n     *\/\n    public final static String ObjectCode = &quot;CHAT&quot;;\n    \n    @WizField private String name;              \/\/ Contains the name of the chatter\n    @WizField private String message;           \/\/ Contains a chat message\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n\n    \/**\n     * @return null as this is a root object.\n     *\/\n    @Override\n    protected String getParentCode() {\n        return null;\n    }\n\n    \/**\n     * @return true as this object is disposable.\n     *\/\n    @Override\n    protected boolean isDisposable() {\n        return true;\n    }\n\n    @Override\n    protected boolean isUrgent() {\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We define the mandatory <code>ObjectCode<\/code>, make objects of this class disposable top-level objects (because we can dispose a message after it has been sent), and define two properties annotated as <code>@WizField<\/code>: <code>name<\/code> and <code>message<\/code> \u2013 the first is the name of the chatter and the latter, well, is the chat message. We also mark that this is an<a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isUrgent()\"> urgent object<\/a> (line 51) because we want chat messages to reach their destination promptly. We place the class in the <code>objectLib<\/code> package. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s build now the chat client application. We will implement it as a command line application. We need to start by extending class <code>Main<\/code>. But before doing that there is a twist. We want our client to read lines from the console, exit if an input line is \u201cexit\u201d, and do something else if not. Since this mechanism will repeat in many of the next lessons we should make it reusable. To do that, we create a <code>Main<\/code> class extension called <code>ConsoleMain<\/code> that does the work and place it in the <code>objectLib<\/code> package: <\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.objectLib;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport org.spiderwiz.core.Main;\n\n\/**\n * Extend Spiderwiz Main class to handle reading input lines from the console by a command line application.\n *\/\npublic abstract class ConsoleMain extends Main{\n    BufferedReader br;\n\n    \/**\n     * Pass parameters to super constructor\n     * @param rootDirectory     the application root directory.\n     * @param configFileName    the application configuration file.\n     * @param appName           the default application name\n     * @param appVersion        the default application version number\n     *\/\n    public ConsoleMain(String rootDirectory, String configFileName, String appName, String appVersion) {\n        super(rootDirectory, configFileName, appName, appVersion);\n    }\n\n    \/**\n     * Called after framework initialization. Install a shutdown hook, then loop on reading messages from the console\n     * until &quot;exit&quot; is typed.\n     *\/\n    @Override\n    protected void postStart() {\n        \/\/ Install a shutdown hook that cleans resources up on termination.\n        Runtime.getRuntime().addShutdownHook(new Thread() {\n            @Override\n            public void run() {\n                cleanup();\n            }\n        });\n        \n        \/\/ Do post init processing\n        if (!postInit())\n            return;\n\n        \/\/ Read console lines and process them until &quot;exit&quot; is typed\n        br = new BufferedReader(new InputStreamReader(System.in));\n        String line;\n        do {\n            line = readConsoleLine(getConsolePrompt());\n            if (&quot;exit&quot;.equalsIgnoreCase(line))\n                break;\n        } while (processConsoleLine(line));\n        System.exit(0);\n    }\n\n    \/**\n     * Override this method to return the prompt text to display on the console before reading a line.\n     * @return an empty string by default.\n     *\/\n    protected String getConsolePrompt() {return &quot;&quot;;}\n    \n    \/**\n     * Implement this method to provide console input line processing\n     * @param line  a console input line\n     * @return true if program shall continue to process next input line, false if it shall terminate\n     *\/\n    protected abstract boolean processConsoleLine(String line);\n    \n    \/**\n     * Provides a formatted prompt, then reads a single line of text from the console.\n     * @param fmt   A format string as described in Format string syntax.\n     * @param args  Arguments referenced by the format specifiers in the format string\n     * @return      A string containing the line read from the console\n     *\/\n    protected String readConsoleLine(String fmt, Object ... args) {\n        try {\n            System.out.printf(fmt, args);\n            return br.readLine();\n        } catch (IOException ex) {\n            return null;\n        }\n    }\n    \n    \/**\n     * Override this to do something after calling Main.init() and before starting to read console lines\n     * @return true if processing shat continue, false if program shall be aborted.\n     *\/\n    protected boolean postInit() {\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ConsoleMain<\/code> is an abstract extension of <code>Main<\/code>. It defines four protected methods that can be overridden \u2013 <code>getConsolePrompt()<\/code> (line 58) returns the&nbsp; prompt text that shall be printed to the&nbsp; console before reading each line. If not overridden, it returns an empty string. The abstract method <code>processConsoleLine()<\/code> (line 65) must be implemented to tell what to do with each input line. Additionally <code>readConsoleLine()<\/code> (line 73) prints a formatted prompt, then reads a single line of text from the console. The method is called internally from <code>ConsoleMain<\/code> and can also be called from derived classes if they need to do their own command line processing. Another method that can be optionally overridden is <code>postInit()<\/code> (line 86). It is called after calling <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#init()\">Main.init()<\/a> and before starting to read console lines, therefore extension classes can call it to do something before processing them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ConsoleMain<\/code> overrides <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#postStart()\">Main.postStart()<\/a> (line 30) to install a shutdown hook, call <code>postInit()<\/code> and loop on reading messages from the console and calling <code>processConsoleLine<\/code>() to process them until &#8220;exit&#8221; is typed (or <code>processConsoleLine()<\/code> returns false).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now everything is ready for <code>ChatMain<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson3;\n\nimport java.util.List;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.tutorial.objectLib.Chat;\nimport org.spiderwiz.tutorial.objectLib.ConsoleMain;\n\n\/**\n * Provides the entry point of the application. Initializes and executes the Spiderwiz framework.\n *\/\npublic class ChatMain extends ConsoleMain{\n    private static final String ROOT_DIRECTORY = &quot;&quot;;\n    private static final String APP_NAME = &quot;Chat Client&quot;;\n    private static final String APP_VERSION = &quot;Z1.01&quot;;  \/\/ Version Z1.01: Initial version\n    \n    private String myName;      \/\/ Get this from the configuration file\n    private Chat chat = null;   \/\/ A Chat object for committing chat messages\n\n    \/**\n     * Class constructor with constant parameters.\n     * @param confFileName  configuration file name, provided as a command argument\n     *\/\n    public ChatMain(String confFileName) {\n        super(ROOT_DIRECTORY, confFileName, APP_NAME, APP_VERSION);\n    }\n\n    \/**\n     * Application entry point. Instantiate the class and initialize the instance.\n     * \n     * @param args the command line arguments. The first argument is used as the configuration file name.\n     *\/\n    \/**\n     * @param args the command line arguments\n     *\/\n    public static void main(String[] args) {\n        \/\/ Don't do anything if there is no configuration file name\n        if (args.length == 0) {\n            System.out.println(&quot;Configuration file has not been defined&quot;);\n            return;\n        }\n        new ChatMain(args[0]).init();\n    }\n\n    \/**\n     * Before starting Spiderwiz engine (but after reading program configuration) get the chatter name.\n     * @return true if a chatter name is provided, false if not.\n     *\/\n    @Override\n    protected boolean preStart() {\n        myName = getConfig().getProperty(&quot;my name&quot;);\n        if (myName == null || myName.isBlank()) {\n            System.out.println(&quot;Chatter name has not been defined&quot;);\n            return false;\n        }\n        System.out.println(&quot;You are chatting as &quot; + myName + &quot;. Go ahead and type your messages&quot;);\n        return true;\n    }\n    \n    \/**\n     * @return the list of produced objects, in this case Chat is the only one.\n     *\/\n    @Override\n    protected String[] getProducedObjects() {\n        return new String[]{Chat.ObjectCode};\n    }\n\n    \/**\n     * @return the list of consumed objects, in this case Chat is the only one.\n     *\/\n    @Override\n    protected String[] getConsumedObjects() {\n        return new String[]{Chat.ObjectCode};\n    }\n\n    \/**\n     * Add ChatConsume implementation class to the object factory list of this application.\n     * @param factoryList\n     *\/\n    @Override\n    protected void populateObjectFactory(List&lt;Class&lt;? extends DataObject&gt;&gt; factoryList) {\n        super.populateObjectFactory(factoryList);\n        factoryList.add(ChatConsume.class);\n    }\n\n    \/**\n     * Process an input line\n     * @param line      the line to be broadcast as a chat message.\n     * @return true if processed successfully\n     *\/\n    @Override\n    protected boolean processConsoleLine(String line) {\n        try {\n            \/\/ If not yet done, instantiate a Chat object and set its 'name' field.\n            if (chat == null) {\n                chat = ChatMain.createTopLevelObject(Chat.class, null);\n                chat.setName(myName);\n            }\n            \/\/ Set chat message and commit\n            chat.setMessage(line);\n            chat.commit();\n            return true;\n        } catch (NoSuchFieldException | IllegalAccessException ex) {\n            \/\/ Theoretically arriving here if the Chat object does not contain ObjectCode static field or the field is not public.\n            \/\/ In this case, send a command exception message.\n            sendExceptionMail(ex, &quot;Cannot instantiate Chat class&quot;, null, false);\n            return false;\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ChatMain<\/code> extends <code>ConsoleMain<\/code>. This time the configuration file name that is passed to <code>ChatMain<\/code> (and <code>Main<\/code>) constructor is entered as a command line argument. This is because we may want to have few instances of the chat client on the same machine, and we don\u2019t want to mix log folders and other properties.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We introduce here (line 49) the usage of <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#preStart()\"><code>Main.preStart()<\/code><\/a> \u2013 a method that is called after the configuration file is loaded and log folders are set, but before Spiderwiz engine is started. Here we get [<code>my name<\/code>] property from the configuration file (line 50) and use it as the chatter (owner of the instance) name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our application is both a Producer and a Consumer of the <code>Chat<\/code> object described above, so both <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getProducedObjects()\">getProducedObjects()<\/a> and <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getConsumedObjects()\">getConsumedObjects()<\/a> return a list that contains one element \u2013 <code>Chat.ObjectCode<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To handle incoming messages we implement an extension of <code>Chat<\/code> called <code>ChatConsume<\/code>. We register it in <code>populateObjectFactory()<\/code> (line 81).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lastly, we implement <code>processConsoleLine()<\/code> that we inherited from <code>ConsoleMain<\/code> (line 91). The argument to this method is the message that shall be sent to the chat group. It works as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>If this is the first message that the application needs to send, use <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#createTopLevelObject(java.lang.Class,java.lang.String)\">createTopLevelObject()<\/a> to create a Chat object. Set the <code>name<\/code> field of the object to \u201cmy name\u201d taken from the configuration file in <code>preStart()<\/code>. If it is not the first time, use the object created at the first time. <\/li><li>Set the object\u2019s <code>message<\/code> field to the line read from the console.<\/li><li><a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#commit()\">Commit<\/a> the object. <\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">You may have noticed that the entire code block is wrapped by <code>try<\/code> \u2026 <code>catch<\/code> commands. This is because <code>createTopLevelObject()<\/code> can potentially refer to a <em>data object<\/em> that does not have a static <code>ObjectCode<\/code> field or the field is not declared <code>public<\/code>. Although we know that this is not the case here, we need to write code that catches the exceptions, and we use the occasion to demonstrate the use of <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#sendExceptionMail(java.lang.Throwable,java.lang.String,java.lang.String,boolean)\">sendExceptionMail()<\/a> \u2013 a nice Spiderwiz feature that reports exceptions and their stack trace to the console, log files and even in emails sent to configured addresses. See the Javadoc for details.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We saw how the application sent chat messages. It remains to see how it receives and prints them out. This is done in <code>ChatConsume<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson3;\n\nimport org.spiderwiz.tutorial.objectLib.Chat;\n\n\/**\n * Consumer implementation of the Chat class\n *\/\npublic class ChatConsume extends Chat{\n\n    \/**\n     * Called when a chat message is received. Print the sender name followed by a colon, then the message.\n     * @return true to indicate that the event has been handled.\n     *\/\n    @Override\n    protected boolean onEvent() {\n        \/\/ Do not print out my own messages\n        if (!ChatMain.getInstance().getAppUUID().equals(getOriginUUID())) {\n            System.out.printf(&quot;%1$s: %2$s&quot;, getName(), getMessage());\n            System.out.println();\n        }\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Quite straightforward. We extend <code>Chat<\/code> and override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\"><code>onEvent()<\/code><\/a> to print sender name and message on the console. The only tricky thing here is that we do not want our own messages to be printed, therefore we use <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getAppUUID()\">getAppUUID()<\/a> and <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getOriginUUID()\">getOriginUUID()<\/a> to compare the receiver application UUID to the sender\u2019s one and skip printing if they are equal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This concludes our coding work. Again you may wonder where the WebSocket stuff is, and again the answer is that it is where all the interesting things in Spiderwiz happen \u2013 in the configuration files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will demonstrate the chatting service with three chatters, each running an instance of the chat client. Recall that the client application expects a configuration file name as a command line argument, so each chatter can supply its own configuration file. Let\u2019s see them:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[application name]Chat 1\n[log folder]\/tests\/Chat1\/Logs\n[producer-1]websocket=localhost:90\/MyHub\n[my name]FERRANDO<\/pre><\/div>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[application name]Chat 2\n[log folder]\/tests\/Chat2\/Logs\n[producer-1]websocket=localhost:90\/MyHub\n[my name]GUGLIELMO<\/pre><\/div>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[application name]Chat 3\n[log folder]\/tests\/Chat3\/Logs\n[producer-1]websocket=localhost:90\/MyHub\n[my name]DON ALFONSO<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">These are explained in the following table: <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[table id=3 \/]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One more thing is missing before running the applications. The chat clients connect as WebSocket clients, therefore they need a standalone client dependency. This is mentioned in <a href=\"http:\/\/spiderwiz.org\/project\/download\/\">Getting Spiderwiz<\/a> and we will repeat it here:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>&lt;dependency&gt;\n\t&lt;groupId&gt;org.glassfish.tyrus.bundles&lt;\/groupId&gt;\n    &lt;artifactId&gt;tyrus-standalone-client&lt;\/artifactId&gt;\n    &lt;version&gt;1.15&lt;\/version&gt;\n&lt;\/dependency&gt;<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Everything is ready, let\u2019s shoot it. The following table reflects the chat activity: <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[table id=4 \/]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this example we saw one central hub that was connected to by multiple WebSocket clients. Spiderwiz is not constrained to this topology. You could, for instance, have few hubs connected to each other in TCP\/IP and distribute the clients between them. You can also clone several hubs and connect each client to all of them for redundancy. However complex is the topology, applications get the objects they consume, and they get only one copy of each. For details about this and other network and data transfer issues see <a href=\"http:\/\/spiderwiz.org\/project\/under-the-hood\/\">Lean and Mean \u2013 Under the Spiderwiz hood<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The magic of SpiderAdmin<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To complete the whole picture we should introduce <a href=\"http:\/\/spideradmin.com\">SpiderAdmin<\/a> &#8211; the administration service that comes free with every Spiderwiz-based application. It is explained in detail in <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-14\/\">Lesson 14<\/a> but we will see a trailer here.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remember the console message \u201c<code>Include spiderwiz-admin.jar in your project in order to use www.spiderwiz.org\/SpiderAdmin<\/code>\u201d from the <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-2\/\">previous lesson<\/a>? It reminds you that if you want your application to participate in the game then the first thing you have to do is to include the <em>SpiderAdmin dependency<\/em> in your project.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">SpiderAdmin is a complimentary service that is not available on the <a href=\"https:\/\/repo1.maven.org\/\">Maven Central Repository<\/a>, therefore you need to define the Spiderwiz internal repository in your project:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>&lt;project&gt;\n  ...\n  &lt;repositories&gt;\n    &lt;repository&gt;\n      &lt;id&gt;spiderwiz-internal&lt;\/id&gt;\n      &lt;url&gt;http:\/\/spiderwiz.org\/repo&lt;\/url&gt;\n    &lt;\/repository&gt;\n  &lt;\/repositories&gt;\n  ...\n&lt;\/project&gt;<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">With this in place you can  specify the <code>spider-admin<\/code> dependency:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>&lt;dependency&gt;\n    &lt;groupId&gt;org.spiderwiz&lt;\/groupId&gt;\n    &lt;artifactId&gt;spiderwiz-admin&lt;\/artifactId&gt;\n    &lt;version&gt;2.2&lt;\/version&gt;\n&lt;\/dependency&gt;<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Non-Maven users can download <code>spiderwiz-admin.jar<\/code> directly by <a href=\"http:\/\/spiderwiz.org\/repo\/org\/spiderwiz\/spiderwiz-admin\/2.2\/spiderwiz-admin-2.2.jar\">this link<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As mentioned above, this needs to be included in <strong>every<\/strong> application that you want to be governed by SpiderAdmin. However, it is enough to connect <strong>only one<\/strong> application to the service, and it will be used as a \u201chole\u201d through which the \u201ccolonoscope\u201d penetrates the entire service-mesh and administers every SpiderAdmin-enabled application. In this example the \u201chole\u201d is <em>My Hub<\/em>.  It works as follows:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, you must be a registered SpiderAdmin user. Go <a href=\"http:\/\/spiderwiz.org\/project\/spideradmin\/\">here<\/a> and use the panel on the right of the screen to register, or log in if you have done it already. If you prefer not to register at this moment but still want to taste the experience, you can use the predefined \u201ctest\u201d user with \u201ctest\u201d also as the password. Once logged in, you will be redirected to the SpiderAdmin service page. If you have not connected yet any application to the service (and if you use the \u201ctest\u201d account then no other user of that account has), you will see a page like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"477\" src=\"http:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-1-1-1024x477.png\" alt=\"\" class=\"wp-image-1078\" srcset=\"https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-1-1-1024x477.png 1024w, https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-1-1-300x140.png 300w, https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-1-1-768x358.png 768w, https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-1-1.png 1278w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Follow the instructions on the page and copy the displayed configuration property to <code>hub.conf<\/code>, which will now look like this:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[log folder]Logs\n[consumer server-1]websocket;ping-rate=30\n[producer server-1]websocket;ping-rate=30\n[spideradmin]W6WEMWZEbCRImAMvgf2M2Xtt3tvH\n[hub mode]yes<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Restart <em>My Hub<\/em> and all the chat applications. Soon the SpiderAdmin service page will refresh to show something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"449\" src=\"http:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-2-1-1024x449.png\" alt=\"\" class=\"wp-image-1080\" srcset=\"https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-2-1-1024x449.png 1024w, https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-2-1-300x131.png 300w, https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-2-1-768x336.png 768w, https:\/\/spiderwiz.org\/project\/wp-content\/uploads\/2020\/08\/lesson-3-2-1.png 1276w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The \u201cApplications\u201d table shows all the active applications, while the \u201cProducers\u201d table shows <em>My Hub<\/em> that connects directly. You can now surf through the entire service mesh by clicking from application to application. For more information see <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-14\/\">Lesson 14.<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lesson 3 is concluded. That was quite a long explanation but pretty short code. In the next chapter we will learn about fine grained object routing, which we will use to implement chat rooms.<br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to create a Spiderwiz web application. We will use it to implement a chat service over WebSockets. We will also discuss how Spiderwiz handles a complex network topology (hint \u2013 with Spiderwiz nothing is complex).<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":145,"menu_order":3,"comment_status":"closed","ping_status":"closed","template":"tutorial-child.php","meta":{"footnotes":""},"class_list":["post-398","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/398","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/comments?post=398"}],"version-history":[{"count":129,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/398\/revisions"}],"predecessor-version":[{"id":1399,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/398\/revisions\/1399"}],"up":[{"embeddable":true,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/145"}],"wp:attachment":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/media?parent=398"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}