Publishing to CometD Bayeux Channel from inside the Oracle Database – PL/SQL based push to CometD Web Client

In recent articles, I have introduced CometD as framework for Server to Client and Client to Client Push: Running CometD 2 examples – locally on Tomcat using Maven and NetBeans https://technology.amis.nl/blog/14709/running-cometd-2-examples-locally-on-tomcat-using-maven-and-netbeans, CometD 2 Java Client Sample – open project in NetBeans based on Maven pom file, modify sources and run Java Based Comet Client (https://technology.amis.nl/blog/14720/cometd-2-java-client-sample-open-project-in-netbeans-based-on-maven-pom-file-modify-sources-and-run-java-based-comet-client) and Push based synchronized Slideshow demo application implemented using CometD and jQuery running on Tomcat (https://technology.amis.nl/blog/14870/push-based-synchronized-slideshow-demo-application-implemented-using-cometd-and-jquery-running-on-tomcat).

CometD allows web clients (or stand alone Java clients) to subscribe to Bayeux channels maintained by the CometD Server (Servlet). These clients can publish messages to the channels and CometD will deliver the messages to all or to selected clients – in a push-fashion. This makes it possible to push messages from the Java server side of a web application to web clients and indirectly to do the same between web clients. One example I have described of applying this technology is the synchronized slideshow: a web page is opened in two or more browsers. When a slide is selected in one of the browsers, the slide is selected in all browsers (because they all subscribed to the channel that a slide selection event was published to).

Image

This article adds another push channel to the overall picture: it describes how the Oracle Database can publish directly to the Bayeux Channels (by posting to the CometD servlet over HTTP) and thereby perform push to the subscribed web clients. Database to browser push – how does that sound?

What is happening can be described like this:

Image

A PL/SQL procedure in the Oracle Database uses the UTL_HTTP package to post HTTP requests to the CometD servlet. These requests contain the same JSON messages that the browser sends to connect to CometD (perform handshake) and publish a slideSelection event to the Bayeux channel. When the post request is received by the CometD Servlet, it does not look any different than the requests received from the browser or the Java client.

Analyzing HTTP traffic for CometD handshake and message publication

I have used Firebug to analyze the HTTP interaction from the browser when the connection to the CometD ‘server’ is initiated and when the slide selection message is sent. Even though the client does a lot of JavaScript processing, in the end it all ends up in plain HTTP requests – and those can be made from PL/SQL as well.

The initial interaction for a client to open communications with CometD happens with a POST request:

Image

The call is made to http://localhost:8085/slideshow/cometd – that is the URL for the CometD Servlet in the web application running on my local Tomcat server, as described in the other articles. The importance of the handshake lies in the response:

Image

This response contains the clientId that is required to be recognized by CometD as a known client when we next try to publish messages to the Bayeux Channel.

The message published when a slide is selected in the web client looks like this:

Image

Note how the clientId allocated by the CometD Servlet is now used to identify the message publisher. The channel this message gets published to is /slide/show and the actual payload of the message is in the data property of the JSON object. Only the slideNumber is really relevant.

Create the PL/SQL procedure(s) to publish to CometD

With this knowledge under our belt, we need to create a PL/SQL procedure -post(p_url, p_message) – that can POST a message to a given URL. This procedure can then be invoked by our main procedure set_slide that takes a slidenumber as input and uses the post procedure to send the JSON requests to the CometD Servlet to first connect (or handshake) and then publish the slide selection message.

First the fairly generic post procedure:

PROCEDURE post(
    p_url     IN VARCHAR2,
    p_message IN VARCHAR2,
    p_response OUT VARCHAR2)
IS
  l_end_loop BOOLEAN := false;
  l_http_req utl_http.req;
  l_http_resp utl_http.resp;
  l_buffer CLOB;
  l_data VARCHAR2(20000);
  value  VARCHAR2(1024);
BEGIN
  -- source: http://awads.net/wp/2005/11/30/http-post-from-inside-oracle/
  -- Ask UTL_HTTP not to raise an exception for 4xx and 5xx status codes,
  -- rather than just returning the text of the error page.
  utl_http.set_response_error_check(false);
  -- Begin the post request
  l_http_req := utl_http.begin_request (p_url, 'POST');
  -- Set the HTTP request headers
  utl_http.set_header(l_http_req, 'User-Agent', 'Mozilla/4.0');
  utl_http.set_header(l_http_req, 'content-type', 'application/json');
  utl_http.set_header(l_http_req, 'content-length', LENGTH(p_message));
  -- Write the data to the body of the HTTP request
  utl_http.write_text(l_http_req, p_message);
  -- Process the request and get the response.
  l_http_resp := utl_http.get_response (l_http_req);
  dbms_output.put_line ('status code: ' || l_http_resp.status_code);
  dbms_output.put_line ('reason phrase: ' || l_http_resp.reason_phrase);
  LOOP
    EXIT
  WHEN l_end_loop;
    BEGIN
      utl_http.read_line(l_http_resp, l_buffer, true);
      IF(l_buffer IS NOT NULL AND (LENGTH(l_buffer)>0)) THEN
        l_data    := l_data||l_buffer;
      END IF;
    EXCEPTION
    WHEN utl_http.end_of_body THEN
      l_end_loop := true;
    END;
  END LOOP;
  dbms_output.put_line(l_data);
  p_response:= l_data;
  -- Look for client-side error and report it.
  IF (l_http_resp.status_code >= 400) AND (l_http_resp.status_code <= 499) THEN
    dbms_output.put_line('Check the URL.');
    utl_http.end_response(l_http_resp);
    -- Look for server-side error and report it.
  elsif (l_http_resp.status_code >= 500) AND (l_http_resp.status_code <= 599) THEN
    dbms_output.put_line('Check if the Web site is up.');
    utl_http.end_response(l_http_resp);
    RETURN;
  END IF;
  utl_http.end_response (l_http_resp);
EXCEPTION
WHEN OTHERS THEN
  dbms_output.put_line (sqlerrm);
  raise;
END;

This procedure can be used to send any POST request to the URL specified as input parameter.

The set_slide procedure is specific for the slideshow channel and the local CometD configuration. However, it can easily be adopted for other environments, channels and message types.

PROCEDURE set_slide(
    p_slidenumber IN NUMBER )
IS
  l_url      VARCHAR2(200)   :='http://localhost:8085/slideshow/cometd';
  l_json     VARCHAR2(20000) := '[{"version":"1.0","minimumVersion":"0.9","channel":"/meta/handshake","supportedConnectionTypes":["long-polling","callback-polling"],"advice":{"timeout":60000,"interval":0},"id":"1"}]';
  l_json1    VARCHAR2(20000) ;
  l_response VARCHAR2(30000);
  l_clientId VARCHAR2(30000);
BEGIN
  post( p_url => l_url, p_message =>l_json, p_response => l_response);
  l_clientId := SUBSTR(l_response, 156 ,instr( l_response, '"', 156)-156);
  l_json1 := '[{"channel":"/slide/show","data":{"uniqueId":1326523938212,"slideNumber":'||p_slidenumber||'},"id":"40","clientId":"'|| l_clientId||'"}]';
  post( p_url => l_url, p_message =>l_json1, p_response => l_response);
END;

Finally, it is simple now to drive a slideshow from within the database. Executing the following piece of PL/SQL will have all subscribed Web Clients dancing to the tune of the database, as they get the slidenumbers to present pushed from the CometD Servlet as instructed by the POST requests from the PL/SQL procedure:

BEGIN
  set_slide( p_slidenumber => 2);
  dbms_lock.sleep(3);
  set_slide( p_slidenumber => 4);
  dbms_lock.sleep(3);
  set_slide( p_slidenumber => 3);
  dbms_lock.sleep(3);
  set_slide( p_slidenumber => 5);
  dbms_lock.sleep(3);
  set_slide( p_slidenumber => 1);
  dbms_lock.sleep(3);
END;

The result is:

Image

Image

Image

Of course you would have to see it animated to really get excited.

Some of the output produced when executing the PL/SQL is shown here – representing to slide selection events being published:

status code: 200
reason phrase: OK
[{"id":"1","minimumVersion":"1.0","supportedConnectionTypes":["callback-polling","long-polling"],"successful":true,"channel":"/meta/handshake","clientId":"5711zev5nev6fg23jth4gpg2no3","version":"1.0"}]
response:5711zev5nev6fg23jth4gpg2no3
status code: 200
reason phrase: OK
[{"id":"40","successful":true,"channel":"/slide/show"}]
status code: 200
reason phrase: OK
[{"id":"1","minimumVersion":"1.0","supportedConnectionTypes":["callback-polling","long-polling"],"successful":true,"channel":"/meta/handshake","clientId":"5azb1iyw0mj5no19d0xjtezs5hu","version":"1.0"}]
response:5azb1iyw0mj5no19d0xjtezs5hu
status code: 200
reason phrase: OK
[{"id":"40","successful":true,"channel":"/slide/show"}]

Coordinating a slideshow is not necessarily the most useful thing to do with the ability to Push from Database to Web Clients. Offering live updates based on data manipulation in the database seems a more useful application of this facility. I am sure I will demonstrate something to that effect in the near future.

Resources

Download the PL/SQL code demonstrated in this article: postToCometFromPLSQL.zip.