Looking for Senior AWS Serverless Architects & Engineers?
Let's TalkSWIM.AI provides next-generation enterprises and developers software solutions for edge and streaming data. This is a unique product for edge/streaming data — software, which is lightweight, able to run anywhere — at the edge or in the cloud — enabling local data collection, reduction, analytics and delivers both data and insights to the cloud.
In this article, we will learn how to set up a simple SWIM server and client and how to connect them.
Requirements
- Java 1.8 or higher
- Gradle
- Nodejs
The Application
In this session, we will be setting up a very simple application with SWIM which will show real-time tweets from a specific hashtag.
Setup Server-Side
- On your project directory create the following files subdirectories
touch build.gradle
mkdir -p src/main/java/app
mkdir -p src/main/resources
touch src/main/java/app/server.java
touch src/main/java/app/twitterAgent.java
touch src/main/resources/server.recon
The directory structure will look like below,
server.java
package app;
import swim.api.SwimRoute;
import swim.api.agent.AgentRoute;
import swim.api.plane.AbstractPlane;
import swim.kernel.Kernel;
import swim.server.ServerLoader;
public class server extends AbstractPlane {
@SwimRoute("/twitter/feed")
private AgentRoute<twitterAgent> userManager;
public static void main(String[] args) throws InterruptedException {
final Kernel kernel = ServerLoader.loadServer();
kernel.start();
System.out.println("Running Server plane...");
kernel.run();
}
}
Every Swim server runs a plane that manages the runtime and provides a shared context for a group of Web Agents. Planes are like an API Gateway that receives and resolves requests to Web Agent, each Agent type in a plane should be annotated with @SwimRoute. The argument inside the annotation defines a URI pattern (colons (:) indicate dynamic components). Requests that match this pattern are routed to an Agent of the provided type, with Instantiation happening as necessary.
twitterAgent.java
package app;
import swim.api.SwimLane;
import swim.api.agent.AbstractAgent;
import swim.api.lane.CommandLane;
import swim.api.lane.ValueLane;
public class twitterAgent extends AbstractAgent {
@SwimLane("tweets")
ValueLane<String> info = this.<String>valueLane()
.didSet((newValue, oldValue) -> {
logMessage("`info` set to " + newValue + " from " + oldValue);
});
private void logMessage(Object o) {
System.out.println("[" + nodeUri() + "] " + o);
}
@Override
public void didStart() {
logMessage("Web Agent Started");
}
}
Swim servers utilize a general-purpose distributed object model in which the objects are called **Web Agents.** Every Web Agent has a universal, logical address, in the form of a URI. In this project, the URI is /twitter/feed . The fields in a Web Agent are called lanes. In our web agent, we will be using a Value Lane.
A value lane stores a scalar value and still meets these requirements:
- Every value lane can be set with a value
- Doing so will trigger its didSet() callback
- The parameter on a value lane indicates the type of value that it stores
- Value lane state and lifecycle can be subscribed to via both value downlinks and general-purpose event downlinks
server.recon
api: @fabric {
@plane(class: "app.server")
}
@web(port: 9001) {
space: "api"
@websocket {
serverCompressionLevel: 0# -1 = default; 0 = off; 1-9 = deflate level
clientCompressionLevel: 0# -1 = default; 0 = off; 1-9 = deflate level
}
}
This file contains the swim server configurations.
build.gradle
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'application'
mainClassName = 'app.server'
// tag::repositories[]
repositories {
mavenCentral()
}
// end::repositories[]
// tag::jar[]
jar {
baseName = 'gs-gradle'
version = '0.1.0'
}
// end::jar[]
// tag::dependencies[]
sourceCompatibility = 1.9
targetCompatibility = 1.9
dependencies {
compile group: 'org.swimos', name: 'swim-server', version: '3.10.2'
compile group: 'org.swimos', name: 'swim-api', version: '3.10.2'
compile group: 'org.swimos', name: 'swim-client', version: '3.10.2'
compile "joda-time:joda-time:2.2"
testCompile "junit:junit:4.12"
}
task runClient(type: JavaExec) {
classpath sourceSets.main.runtimeClasspath
main = "app.customClient"
}
Gradle file which includes the SWIM dependencies.
With these configurations the server-side setup is complete.
run the below steps to build and start the server.
gradle build
gradle run
The output will look like this,
The next step is to set up the client-side part to send some data to the server and subscribe to the server and see it in real-time.
Setup Client-Side
The client side will have two files.
- A nodejs script reads the latest tweets from a hashtag and sends it to the SWIM server
- Simple HTML app, which gets realtime data by subscribing to the SWIM server.
Create a file with the name getTweets.js. And add the following contents in it.
var Twitter = require('twitter');
var swim = require("@swim/client");
const swimClient = new swim.WarpClient();
let valueLane = swimClient.downlinkValue()
.hostUri("warp://localhost:9001").nodeUri("/twitter/feed").laneUri("tweets")
.didSet((newValue, oldValue) => {
// console.log("link watched info change to " + newValue + " from " + oldValue);
})
.open();
// You can get the API keys from here, <https://developer.twitter.com/en/apps>
var client = new Twitter({
consumer_key: "your key",
consumer_secret: "your secret",
access_token_key: "your token",
access_token_secret: "your access_token_secret"
});
/**
* Stream statuses filtered by keyword
* number of tweets per second depends on topic popularity
**/
client.stream('statuses/filter', {track: '#tech'}, function(stream) {
stream.on('data', function(tweet) {
let twtObj = {}
twtObj.msg = tweet.text
twtObj.url = tweet.user.profile_image_url_https || '<https://i1.pngguru.com/preview/137/834/449/cartoon-cartoon-character-avatar-drawing-film-ecommerce-facial-expression-png-clipart.jpg>'
twtObj.timeStamp = tweet.created_at
valueLane.set(JSON.stringify(twtObj));
});
stream.on('error', function(error) {
console.log(error);
});
});
The above script will get live tweets from twitter API’s and write it to the SWIM server using @swim/client SDK. To test it, start the SWIM server with gradle run and on another terminal run node getTweets.js you will be able to see the data received on the server
side as below,
client.html
<!DOCTYPE >
<html>
<head>
<title>Twitter Feed</title>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, shrink-to-fit=no, viewport-fit=cover" />
<style>
body {
margin: 0 auto;
max-width: 800px;
padding: 0 20px;
}
.container {
border: 2px solid #a1eafb;
background-color: #a1eafb;
border-radius: 5px;
padding: 10px;
margin: 10px 0;
}
.darker {
border-color: #cabbe9;
background-color: #cabbe9;
}
.container::after {
content: "";
clear: both;
display: table;
}
.container img {
float: left;
max-width: 60px;
width: 100%;
margin-right: 20px;
border-radius: 50%;
}
.container img.right {
float: right;
margin-left: 20px;
margin-right:0;
}
.time-right {
float: right;
color: #aaa;
}
.time-left {
float: left;
color: #999;
}
</style>
</head>
<body>
<h2 style="font-family:geneva">Latest Tweet</h2>
<div class="container">
<img id="newDpUrl" src="<https://i1.pngguru.com/preview/137/834/449/cartoon-cartoon-character-avatar-drawing-film-ecommerce-facial-expression-png-clipart.jpg>" alt="Avatar" style="width:100%;">
<p style="font-family:verdana" id="newChat"></p>
<span style="font-family:arial" id="newTime" class="time-right"></span>
</div>
<h2 style="font-family:geneva">Previous Tweet</h2>
<div class="container darker">
<img id="oldDpUrl" src="<https://i1.pngguru.com/preview/137/834/449/cartoon-cartoon-character-avatar-drawing-film-ecommerce-facial-expression-png-clipart.jpg>" alt="Avatar" class="right" style="width:100%;">
<p style="font-family:verdana" id="oldChat"></p>
<span style="font-family:arial" id="oldTime" class="time-left"></span>
</div>
</body>
<div id="app" style="display: flex; width: 67%; height: 67%; flex-direction: column;">
</div>
<script src="<https://cdn.swimos.org/js/3.10.2/swim-system.js>"></script>
<script src="<https://cdn.swimos.org/js/latest/swim-core.js>"></script>
<script src="<https://cdn.swimos.org/js/latest/swim-ui.js>"></script>
<script src="<https://cdn.swimos.org/js/latest/swim-web.js>"></script>
<script>
const app = new swim.HtmlAppView(document.getElementById("app"));
const gaugeCanvas = app.append('div').position('relative').append("canvas");
/* Data Subscriptions */
const valueLink = swim.downlinkValue()
.hostUri("warp://localhost:9001")
.nodeUri("/twitter/feed")
.laneUri("tweets")
.didSet(function (newValue, oldValue) {
console.log(JSON.parse(newValue['_value']).msg)
document.getElementById("newChat").innerHTML = JSON.parse(newValue['_value']).msg
document.getElementById("newDpUrl").src = JSON.parse(newValue['_value']).url
document.getElementById("newTime").innerHTML = JSON.parse(newValue['_value']).timeStamp
document.getElementById("oldChat").innerHTML = JSON.parse(oldValue['_value']).msg
document.getElementById("oldDpUrl").src = JSON.parse(oldValue['_value']).url
document.getElementById("oldTime").innerHTML = JSON.parse(newValue['_value']).timeStamp
})
.open();
</script>
</body>
</html>
The above code will subscribe to the SWIM server and will read the latest data from there and show it in a simple HTML view in real-time as below.
You can find the full project here. https://github.com/serverless-guru/templates/tree/master/swim-ai-sample-app