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.

12 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++;
    }

  4. Hi,
    I got this error when I call the “PUSH” function:

    ORA-29273: HTTP request failed
    ORA-06512: at “SYS.UTL_HTTP”, line 1130
    ORA-12541: TNS:no listener

    Can you help me please ?

  5. Dear Edwin,
    when i run server.js i encounter the following error
    C:\emoracle>node servera.js
    C:\emoracle\servera.js:29
    req.on(‘dataunction(data) {
    ^^^^^^^^^^^^^^^^^^^^

    SyntaxError: Invalid or unexpected token
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:607:28)
    at Object.Module._extensions..js (module.js:654:10)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)
    at Function.Module.runMain (module.js:684:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

    kindly guide..

      • Hi Edwin, Thanks for quick reply. Just saved my day..I had copied the above script via notepad plus editior Seems the code got changed and then i changed to sublime text 3
        And it directly shows where the syntax error is..
        i just got the server up..Thanks and cheers

  6. How does client communicate with the database? For example, if I wanted to store a list of connected clients in the database so that messages can then be sent to specific clients?

Leave a reply to Brian Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.