Using websockets in APEX for automatic refresh with nodejs

In my former post Using websockets in APEX for automatic refresh I explained how to implement a mechanism with an external websocketserver to push a refresh to an APEX page. It used Beaconpush.

But I didn’t like the dependency on an external websocketserver (I’m just that way), so I investigated what it should take to implement my own websocketserver.
Introducing node.js

Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

JavaScript is a growing skill amongst Oracle/APEX developers, so it was a logical choice to look into using server-side JavaScript to implement a http/websocketserver combination. The idea was to create a very simple server which the clients could connect to using the websocket-mechanism. The database should be able post requests to the HTTP-part of the server that would be used to the connected clients.

  • Install node.js

On the website of node.js you can download an installer for node.js. I downloaded the msi (I’m on a Windows 7 machine).

Install websocket.

Node.js works with modules that can be installed. For this example we need the websocket module. To install, use the NPM executable

npm install websocket

As a sidenote, this installation failed on my machine. I think the node.js version was not compatible with the websocket version (1.0.4).  I had to install one version lower:

npm install websocket@1.0.3
  • Create the server.js

As I mentioned before, we are talking here about server-side JavaScript. So you have to create a JavaScript file, e.g. server.js

"use strict";

process.title = 'database-chat';

var config = {
  port:1337
};

/* 
  We need a HTTP-server and a websocketserver
*/
var webSocketServer = require('websocket').server;
var http = require('http');

/* 
  We need to keep record of connected clients
*/
var clients = [ ];

/*
   The HTTP server
*/
var server = http.createServer( function(req, resp){
  var body = '';
  var json;
  req.setEncoding('utf8');
  resp.writeHead(200,{'Content-Type':'application/json' });

  req.on('data', function(data) {
    body += data.toString();
    /*
      When we get a stringified JSON-object, that's oke.
      If not, we just create one with one attribute  
    */
    try {
      json = body;
      var test = JSON.parse(json);
    }
    catch (e) {
      json = JSON.stringify({ 'message':body} );
    }  
    /*
      Push the JSON-string to all the connected clients 
    */
    for (var i=0; i < clients.length; i++) {
      clients[i].sendUTF(json);
    }  
    resp.end(JSON.stringify({ message: 'Send to '+ clients.length + ' clients' }));
  });
});

/* Start listening on the configured port an write to standard output*/
server.listen(config.port, function() {
    console.log((new Date()) + " Server is listening on port " + config.port);1
});

/*
   WebSocket server
 */
var wsServer = new webSocketServer({
    // De WebSocket server is tied to a HTTP server.
    httpServer: server
});

/* If a client connects to the websocketserver,
   this callback accepts the request and adds it to the list
*/
wsServer.on('request', function(request) {
    console.log((new Date()) + ' Websocketconnection from origin ' + request.origin + '.');
    var connection = request.accept(null, request.origin); 
    // we need to know client index to remove them on 'close' event
    var index = clients.push(connection) - 1;
    console.log((new Date()) + ' Connection accepted.');

    connection.on('close', function(connection) {
            console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected.");
            clients.splice(index, 1);
    });

});
  • Start the server

Starting this server.js script is easy. In a command window type

node server.js

You will get something like Newly started server.js

But what is a server without the client?

First of all you will have to have some sort of procedure on the database that will sent a message to your server.js

procedure push 
  ( p_text in varchar2
  )
is
  l_clob clob;
begin
  l_clob := APEX_WEB_SERVICE.MAKE_REST_REQUEST
              ( p_url    => 'http://127.0.0.1:1337'
              , p_method => 'POST'
              , p_body   => '{"message":"'||p_text||'"}'
              );
end;

With such a procedure you can e.g. push the message REFRESH to you page and refresh the region.
Create a page with an interactive report that you want to be refreshed.
In the page footer add the following script:

    
$(function(){
  window.WebSocket = window.WebSocket || window.MozWebSocket;

  if (!window.WebSocket) {
    console.log('Sorry, no websockets');
  }
  else {
    var connection = new WebSocket('ws://127.0.0.1:1337');

    connection.onerror = function (error) {
      console.log( 'Sorry, but there is some problem with your connection or the server is down.');
    };

    connection.onmessage = function (message) { 
      console.log(message.data);   
      try {
        var json = JSON.parse(message.data);

        if (json.message=='REFRESH') {
          gReport.pull();
        }
        else { 
          console.log(json.message);
        } 

      } 
      catch (e) {
        console.log('This is not valid JSON: ' + message.data);
      }
    };
  }
});

and that’s it.

Mind you, this is just one implementation, but you can push every database event to the clients page.

About these ads

7 thoughts on “Using websockets in APEX for automatic refresh with nodejs

  1. One small item that I noted…not sure of your APEX version but, under 4.2.1.00.08, the name of the method argumnet to APEX_WEB_SERVICE.MAKE_REST_REQUEST is p_http_method rather than p_method.

  2. Great article! This was exactly what we needed and, given the nature of our business, we wouldn’t have been able to employ connections to a third party. Thanks for sharing.

    • One thing, in a multiuser environment, I am unsure if the clients array is slicing off the correct connection on disconnect…I might be wrong…not sure…. .

      Just to make sure, I added an global var clientId at the top of script, and then inside of the on request:

      clientId = clientId +1;
      var id = clientId;
      clients.push({id:id, connection:connection});

      and then in the onclose:
      for (var i=0; i<clients.length;i++){
      if (clients[i].id == id){
      clients.splice( i, 1);
      break;
      }

  3. Just wanted to update… it does look like there is a problem in the onclose function.

    Also doing what is in the example code does not work: var index = clients.indexOf(connection); so test this carefully you go this route. This appears to never find the index.

    This is what is working for me:

    var clients = {}; // at top of script — create an object not an array!!!!

    in the on request do:
    clientId = clientId +1;
    var id = clientId;
    clients[id] = connection;

    In onClose do:
    delete sockets[id];

    Then instead of using for loops use:

    var keys = Object.keys(clients);
    var len = keys.length;
    var i=0;
    while (i < len) {
    console.log( i + ' — ' + keys[i] );
    clients[ keys[i] ].send('xyz');
    i++;
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s