Real-Time WebSocket App Using Oracle Application Container Cloud
How to create and deploy a Java web application using WebSocket technology.
by Fernando Babadopulos and Fabiane Nardon

The following technologies will be covered in this article:

  • Java
  • Maven
  • Spring Boot
  • Oracle Application Container Cloud
Setting up the Environment

We will use Maven to set up our project structure. If you don't have Maven installed, please, read the instructions and install Maven from https://maven.apache.org/download.cgi. The default installation is enough for this article.

To set up the base project structure, run this command:

mvn -B archetype:generate \
    -DarchetypeGroupId=org.apache.maven.archetypes \
    -DgroupId=com.example.realtimewebsocket \
    -DartifactId=real-time-websocket

This should create the following directory tree:

real-time-websocket/
./pom.xml
./src
./src/test/java/com/example/realtimewebsocket/AppTest.java
./src/main
./src/main/java/com/example/realtimewebsocket/App.java

The pom.xml file is used by Maven to represent the current project; it describes the structure, dependencies, and all things related to the project itself.

Under the src folder, Maven will create two other folders: test and main. The application code will be in the main folder, and the code used to test the application will be in the test folder.

More information about how Maven works can be found here: https://maven.apache.org/guides/getting-started/.

The first step to set up our web application is to edit the pom.xml file and include all the dependencies that we need.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.10.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>1.5.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>webjars-locator</artifactId>
        <version>0.32-1</version>
    </dependency>
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>sockjs-client</artifactId>
        <version>1.0.3</version>
    </dependency>
    <dependency>
        <groupId>org.webjars.bower</groupId>
        <artifactId>stomp-websocket</artifactId>
        <version>2.3.4</version>
    </dependency>
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.3.1</version>
    </dependency>
</dependencies>

The <parent> group instructs Maven to load a pom.xml file from Spring Framework, while the <dependencies> group has information for every library that we will need.

spring-boot-starter-web enables our application to have web capabilities, bind an HTTP port, have routes to a specific URL, and so on.

spring-boot-starter-websocket is responsible for WebSocket functionalities.

All four webjars dependencies provide an easy-to-use way to have all the JavaScript files that we need available in the project.

If you run the following command, Maven will download all the dependencies described in the pom.xml file and compile the existing code. But we won't have any web application yet.

mvn clean install

To enable web capabilities, we need to annotate the main class of our application and instruct Spring to run, as follows:

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}

The @SpringBootApplication is the same as @Configuration, @EnableAutoConfiguration, and @ComponentScan with default values used together.

Inside the main method, the line SpringApplication.run is responsible for starting the server and running our web application.

The default Spring HTTP port is 8080, but we can override all default configurations using an application.properties file.

At this point, we already have a working Spring application and, because we imported a parent pom.xml from Spring Boot (spring-boot-starter-parent), we have a convenient way to start the application using Maven:

mvn spring-boot:run

If you run this command and point your browser to http://localhost:8080/, you should now see an error page. That error is because our application has web capabilities, but it does nothing yet. Let's fill in the gaps.

By the way, you can press Ctrl+c to stop the application.

Because our application will use WebSockets to send events in real time to a browser, we have to configure and enable it. First we create a config class like this:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/channel");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket")
                .setAllowedOrigins("*")
                .withSockJS();
    }

}

Because we extended the AbstractWebSocketMessageBrokerConfigurer we can override the configureMessageBroker method and create a new message broker channel under the path /channel. We also create an endpoint for WebSocket STOMP messages under the /websocket path, enable access to it from all origins and, as a fallback, we instruct it to use SockJS if the browser has no WebSocket capabilities.

STOMP is a text-oriented message protocol, and we will use it to transport our messages from our application to the browser.

Our TransportMessage.java class will have the following structure:

public class TransportMessage {
    private Date date;
    private DataType type;
    private String data;
    private Long nextUpdate;

}

We will send two types of data back to the browser, described by this DataType.java enum:

public enum DataType {
    RANDOM,
    BITCOIN;

}

To produce the data, we created the DataFactory.java class with two methods:

  • randomDataGenerator produces random data.
  • bitcoinPriceFromAPI consumes a Bitcoin price API.

We annotate DataFactory with the @Component annotation, so Spring can locate it.

Those two methods need to be called from time to time, for example, every 1 second, generating the data and sending it to a specific channel. All browsers subscribed to a specific channel should receive the message.

To execute our methods every 1 second, we use the @Scheduled Spring annotation:

@Component
public class DataFactory {

    private static final long BITCOIN_REFRESH = 60_000L;

    private long lastBitcoinUpdate = 0l;
    private String lastBitcoinPrice = null;

    @Autowired
    private SimpMessagingTemplate template;

    @Scheduled(fixedRate = 1_000)
    public void randomDataGenerator() {

        TransportMessage transportMessage = new TransportMessage(DataType.RANDOM);
        transportMessage.setDate(new Date());
        String value = String.valueOf(Math.random());
        transportMessage.setData(value);
        transportMessage.setNextUpdate(1l);

        this.template.convertAndSend("/channel/random", transportMessage);

    }


    @Scheduled(fixedRate = 1_000)
    public void bitcoinPriceFromAPI() {
        TransportMessage transportMessage = new TransportMessage(DataType.BITCOIN);
        transportMessage.setDate(new Date());


        /* omitting the API request to improve readability
        .
        .
        .
        */
 
        this.template.convertAndSend("/channel/bitcoin", transportMessage);
    }


}

The @Autowired annotation is responsible for the dependency injection; it automatically initializes our template variable with an instance of SimpMessagingTemplate.

Also, to create a background task executor and start all tasks, the main class needs the @EnableScheduling annotation. Without this annotation, no task will be executed.

@SpringBootApplication
@EnableScheduling
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}

At this point, the application back end is ready. We can move forward and create the front-end web page.

Creating the Front-End Web Page

Under src/main/, create a resource/static directory, which will hold all our resources and static pages:

mkdir src/main/resources/static

Under src/main/resources/static, create the following files: index.html, application.js, and application.css.

The index.html file should look like this:

<!DOCTYPE html>
<html>
<head>
    <title>Real Time WebSocket</title>
    <link href="/application.css" rel="stylesheet">

    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/application.js"></script>
</head>
<body>


<div id="content">
    <div id="controls">
        <button id="connect_random" class="connect" type="button">Connect to Random Channel</button>
        <button id="connect_bitcoin" class="connect" type="button">Connect to Bitcoin Channel</button>
        <button id="disconnect" type="button" disabled="disabled">Disconnect</button>
    </div>

    <div id="data">
        <span class="label">Next update: </span>
        <div class="value" id="nextUpdate">-</div>
        <span class="label"> sec.</span><br>

        <span class="label">Timestamp: </span>
        <div class="value" id="date">-</div><br>

        <span class="label">Value: </span>
        <div class="value" id="value">-</div>
    </div>

</div>
</body>
</html>

In this file, we load a few JavaScript files:

  • sockjs.min.js enables WebSocket capabilities in JavaScript.
  • stomp.min.js process STOMP messages.
  • jquery.min.js is used to easily manipulate the DOM.

The application.js file contains all our application-specific JavaScript and should have the following content:

var stompClient = null;

function setConnected(connected) {
    $("#disconnect").prop("disabled", !connected);
}

function connect(channel) {
    disconnect();

    $(".connect").removeClass("connected");
    $("#connect_" + channel).addClass("connected");

    var socket = new SockJS('/websocket');

    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        stompClient.subscribe('/channel/' + channel, function (data) {
            showGreeting(JSON.parse(data.body));
        });
    });
}

function disconnect() {
    $(".connect").removeClass("connected");

    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}


function showGreeting(data) {

    var date;
    var value;
    var nextUpdate = data.nextUpdate;


    $("#nextUpdate").html(nextUpdate);
    switch (data.type) {
        case "RANDOM":
            date = data.date;
            value = data.data;
            break;
        case "BITCOIN":
            var bitcoin = JSON.parse(data.data);
            date = bitcoin.time.updated;
            value = bitcoin.bpi.USD.rate_float;

            break
    }

    $("#date").html(date);
    $("#value").html(value);

}

$(function () {
    $("#connect_random").click(function () {
        connect('random');
    });
    $("#connect_bitcoin").click(function () {
        connect('bitcoin');
    });
    $("#disconnect").click(function () {
        disconnect();
    });
});

Finally, we create a simple application.css file to lay out our application:

.disconnected {
    background-color: white;
}
.connected {
    background-color: #97de97;

}
#data {
    margin: 10px;
}
.label {
    display: inline;
}
.value {
    display: inline;
}
Starting the Server

Now we have a fully functional WebSocket application. Let's start the server:

mvn spring-boot:run

Point your web browser to http://localhost:8080 and you should see our application. Click any Connect button to subscribe to a specific WebSocket channel and start receiving data.

Now that our software is ready, we can package and deploy it on an application container cloud.

Packaging and Deploying the Software on an Application Container Cloud

The first step is to create an application.properties file under src/main/resources/ and change the server.port value. It should read the value from an environment variable, ${PORT}, that is automatically created by the application container.

server.port=${PORT}

Also, create a distribution.xml file in the project root folder to hold our assembly configuration.

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
    <id>dist</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <fileSet>
            <directory>${project.basedir}</directory>
            <includes>
                <include>manifest.json</include>
            </includes>
        </fileSet>
        <fileSet>
            <directory>${project.build.directory}</directory>
            <outputDirectory>\</outputDirectory>
            <includes>
                <include>real-time-websocket-1.0-SNAPSHOT.jar</include>
            </includes>
            <excludes>
                <exclude>*.zip</exclude>
            </excludes>
        </fileSet>
    </fileSets>
</assembly>

And add a build configuration to the pom.xml file:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>1.5.10.RELEASE</version>
        </plugin>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <appendAssemblyId>false</appendAssemblyId>
                <descriptors>
                    <descriptor>distribution.xml</descriptor>
                </descriptors>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

We are now ready to package our application. But before doing that, create a manifest.json file in the project root folder; it will have all the configuration Oracle Application Container Cloud needs to run our application. Pay attention to the command value; it contains the command that runs the application.

{
  "runtime": {
    "majorVersion": "8"
  },
  "command": "java -jar real-time-websocket-1.0-SNAPSHOT.jar",
  "notes": "Real Time WebSocket enabled application DEMO"
}

Run the following Maven command to build and package our application:

mvn clean install

Maven will package our application and create a real-time-websocket-1.0-SNAPSHOT.zip file in the "target" directory. The .zip file contains our entire application ready to be delivered.

It also creates a real-time-websocket-1.0-SNAPSHOT.jar file that can be used to start the server. Just remember to set the PORT environment variable first:

export PORT=8080
java -jar real-time-websocket-1.0-SNAPSHOT.jar
Deploying the Application

It is time to deploy our application:

Log in to your Oracle cloud account at https://cloud.oracle.com/sign-in and go to Services -> Application Container.

Click the Create Application button, which is shown in Figure 1.

Figure 1. Screen where you can choose to create an application
Figure 1. Screen where you can choose to create an application

Choose Java SE as the application platform (Figure 2), and then in the screen that appears (Figure 3) fill in the Name field, click Choose File and select the real-time-websocket-1.0-SNAPSHOT.zip file that we previously created.

igure 2. Screen where you can select the application platform
Figure 2. Screen where you can select the application platform
Figure 3. Screen where you can specify the application details
Figure 3. Screen where you can specify the application details

Finally, click the Create button and wait a while to have your application deployed.

Once your container is ready you can point your browser to the URL shown in the application interface and test your newly deployed application.

The code for this article is available on GitHub. You can clone our repository and start testing right now:

git clone https://github.com/babadopulos/real-time-websocket.git
About the Authors
 

Fernando Babadopulos (@babadopulos), the CTO at Tail, is a software architect, entrepreneur, and enthusiast of new technologies; he was responsible for developing some of the most popular web applications in Brazil and abroad. With more than 15 years of experience with the internet, Babadopulos specializes in the creation and design of high-performance systems and is a frequent speaker and participant in developer conferences worldwide. He is also a JavaOne program committee member. He holds a master's degree in information engineering from the Federal University of ABC and a BS in computer science from the University Center of FEI. He is a Java Champion and an Oracle Champion recognized for his contributions to the Java ecosystem and he is a Duke's Choice Award winner.

Fabiane Nardon (@fabianenardon) is a computer scientist who is passionate about creating software that will positively change the world. She was chief architect of the Sao Paulo Healthcare Information System—considered the largest Java EE application in the world—and a winner of the 2005 Duke's Choice Award. She led several communities, including the Java Tools Community at java.net, where 800+ open source projects were born. She is a frequent speaker at conferences in Brazil and abroad, including JavaOne, OSCON, Jfokus, JustJava, QCon, and more. She's also the author of several technical articles and a member of the program committee of several conferences such as JavaOne, OSCON, TDC, and QCon. She was chosen as a Java Champion by Sun Microsystems in recognition of her contribution to the Java ecosystem. Currently, she works as a Chief Scientist at Tail, where she is helping to shape new disruptive data science–based services.

Joins the java community conversation
DEVO_ATTACH_BOTTOM
Experience Oracle Cloud —Get up to 3,500 hours free.