Dienstag, 10. November 2015

Node.js und die Oracle-Datenbank: Erste Schritte ... und ein etwas anderes Beispiel

Heute geht es mal nicht um REST und Oracle REST Data Services, dieses Posting stellt vielmehr die Entwicklerplattform Node.js in den Vordergrund. Ich werde euch den Oracle-Datenbanktreiber für Node.js vorstellen: node-oracledb. Der Treiber ist kostenlos verfügbar und wird auf Github und auf npmjs.org gehostet. Im Oracle Technet findet man im Node.js Developer Center nähere Informationen. In diesem Blog-Posting muss der Treiber aber nicht manuell heruntergeladen werden; das geht alles vollautomatisch.

Was Node.js ist und wie es funktioniert, möchte ich hier nicht mehr ausführlich beschreiben; dazu gibt es eine ganze Menge fertiger Artikel im Internet, die ich hier nicht nochmals wiederholen möchte. Ich nenne beispielhaft mal zwei - eine kurze Suche bringt noch viel mehr zum Vorschein.

In meinem anderen Blog SQL und PL/SQL in der Oracle-Datenbank hatte ich vor einiger Zeit schon Postings zu Node.js: Im ersten Posting gab es den "offiziellen" Treiber node-oracledb noch gar nicht, so dass ich auf einen der Open Source Community angewiesen war. Der ist heute nicht mehr nötig - der Treiber von Oracle ist ebenfalls Open Source und bietet wesentlich mehr Funktionen an. Das zweite Blog Posting habe ich kurz nach dem Beta-Release von node-oracledb geschrieben; es zeigt erstmals die Installation. Diese hat sich inzwischen etwas geändert - so dass dieses Blog-Posting hier den aktuellen Stand wiedergibt.

Node.js ist also Javascript – auf dem Server. Node.js ist in der Entwicklergemeinde mittlerweile sehr populär, was man vor allem an den zahlreichen Community-Erweiterungen erkennen kann. Für den Package Manager (NPM = Node Packaged Modules), mit dem die Erweiterungen der Node-Bibliothek verwaltet werden können, stehen mittlerweile fast 120.000 Pakete bereit.

Das asychrone Programmiermodell von Node.js werden wir bei den ersten Versuchen mit dem Datenbanktreiber node-oracledb noch kennenlernen. Alle blockierenden Aufrufe (Netzwerk, lokales Dateisystem etc.) werden mit Node asynchron ausgeführt – das Programm kann also weiter arbeiten. Man bemerkt deutlich das Ziel von Node.js: Die CPU soll möglichst ausgelastet werden – der Programmthread soll nur dann warten, wenn gerade wirklich nichts zu tun ist. Besonders beliebt ist Node für das Aufsetzen webbasierter Dienste, kommt diese Architektur deren Anforderungen doch besonders entgegen.

Im folgenden beschreibe ich, wie Ihr (ausgehend von einem Linux-System) eine Node.js-Umgebung so einrichtet, dass Ihr Node.js Programme für die Oracle-Datenbank schreiben könnt.

Installation von node.js selbst

Ladet euch Node.js von nodejs.org herunter. Zur Zeit unterstützt der Oracle-Treiber die Node.js Versionen 0.10 und 0.12. Sucht die richtige Datei für eure Plattform und installiert diese. Nach der Installation prüft Ihr, ob die Executables für Node und den Package Manager NPM funktionieren.

# ${NODE_HOME}/bin/node -v
v0.12.7

# ${NODE_HOME}/bin/npm -v
2.11.3

Der Package Manager NPM ist für das Nachladen benötigter Node.js-Pakete aus dem Internet zuständig. Wenn sich eure Umgebung hinter einer Firewall befindet, so müsst Ihr einen Proxy-Server setzen; das ist nur einmal erforderlich und geht wie folgt:

# npm config set proxy=http://{proxy-server}:{port}
# npm config set https-proxy=http://{proxy-server}:{port}

Legt euch dann ein "Arbeitsverzeichnis" an (mywork) - darin könnt Ihr die Internetverbindung direkt testen - ladet euch das Node-Paket express herunter, das ist ein sehr beliebter Webserver für Node.js.

# mkdir mywork
# cd mywork
# npm install express
express@4.13.3 node_modules/express
+-- escape-html@1.0.2
+-- merge-descriptors@1.0.0
:
+-- accepts@1.2.13 (negotiator@0.5.3, mime-types@2.1.7)
+-- send@0.13.0 (destroy@1.0.3, statuses@1.2.1, ms@0.7.1, http-errors@1.3.1, mime@1.3.4)

Euer Arbeitsverzeichnis enthält nun ein Unterverzeichnis node_modules; darin eins namens express, welches die Dateien des Pakets enthält.

Installation des Oracle Instant Clients

Der Datenbanktreiber node-oracledb benötigt zum Funktionieren Zugriff auf die Client-Bibliotheken der Oracle-Datenbank. Wenn eure Datenbankinstallation bereits auf dem Node.js Server vorhanden ist, müsst Ihr nichts weiter tun. Wenn nicht, muss ein Client heruntergeladen werden. Am besten ist der Oracle Instant Client geeignet; dessen Installation ist am einfachsten. Achtet darauf, dass Ihr zwei Pakete aus dem OTN herunterladet:

  • Instant Client Package - Basic: Das ist das Basispaket mit den Client-Bibliotheken.
  • Instant Client Package - SDK: Hierin sind die Header-Files für den C-Compiler enthalten; dieses Paket wird initial zum Einrichten des Datenbanktreibers gebraucht.

Packt beide ZIP-Dateien in ein Verzeichnis (${INSTANTCLIENT_HOME}) aus. Es sollte dann in etwa so aussehen (die Dateien des Pakets Basic sind blau, die des SDK rot markiert)

# cd ${INSTANTCLIENT_HOME}
$ find
./uidrvci
./libocci.so.12.1
./libociei.so
:
./xstreams.jar
./sdk
./sdk/include
./sdk/include/occiCommon.h
./sdk/include/occi.h
./sdk/include/occiData.h
:

Jetzt legt Ihr noch einen symbolischen Link namens libclntsh.so für die Datei libclntsh.so.12.1 an; natürlich kann man die Datei auch einfach kopieren. Dann sind die Vorbereitungen soweit fertig.

# cd ${INSTANTCLIENT_HOME}
# ln -s libclntsh.so.12.1 libclntsh.so

Installation des Datenbanktreibers "node-oracledb"

Wechselt nun nochmals in euer Arbeitsverzeichnis mywork. Setzt die Umgebungsvariablen LD_LIBRARY_PATH, OCI_LIB_DIR und OCI_INC_DIR wie folgt. LD_LIBRARY_PATH muss künftig auch zur Laufzeit immer gesetzt sein, die anderen beiden werden nur einmalig zum Build des Treibers benötigt.

# cd mywork
# export OCI_LIB_DIR = ${INSTANTCLIENT_HOME}
# export OCI_INC_DIR = ${INSTANTCLIENT_HOME}/sdk/include

# export LD_LIBRARY_PATH = ${INSTANTCLIENT_HOME}:$LD_LIBRARY_PATH

Wenn Ihr nicht mit dem Instant Client, sondern mit einer lokal installieren Oracle-Datenbank arbeitet, dann habt Ihr ein ORACLE_HOME und die Umgebungsvariablen werden etwas anders gesetzt.

# export OCI_LIB_DIR=${ORACLE_HOME}/lib
# export OCI_INC_DIR=${ORACLE_HOME}/rdbms/public

# export LD_LIBRARY_PATH = ${ORACLE_HOME}/lib:$LD_LIBRARY_PATH

Danach "installiert" Ihr den Treiber einfach mit npm install. Das Herunterladen, Auspacken und den "Build" des Treibers übernimmt NPM - Ihr solltet etwa folgende Ausgabe sehen.

# npm install oracledb
|
> oracledb@1.3.0 install /home/oracle/mywork/node_modules/oracledb
> node-gyp rebuild
:
  CXX(target) Release/obj.target/oracledb/src/dpi/src/dpiCommon.o
  SOLINK_MODULE(target) Release/obj.target/oracledb.node
  COPY Release/oracledb.node
make: Leaving directory `/home/oracle/mywork/node_modules/oracledb/build'
oracledb@1.3.0 node_modules/oracledb
+-- nan@1.9.0

Das Unterverzeichnis node_modules enthält nun einen weiteren Unterordner: oracledb. Jetzt kann's losgehen.

Das erste Node.js Programm mit der Oracle-Datenbank

Nun ist der Treiber installiert und das erste Node.js Programm zum Zugriff auf die Oracle-Datenbank kann geschrieben werden. Hier ist es auch schon, zunächst selektieren wir eine Zeile der EMP-Tabelle. Passt die Koordinaten eures Datenbankservers in Zeile 7 vorher an eure Umgebung an.

var oracledb = require('oracledb');

oracledb.getConnection(
  {
    user          : "scott",
    password      : "tiger",
    connectString : "dbserver:1521/orcl"
  },
  function(err, connection) {
    if (err) {console.error(err.message); return;}
    connection.execute(
      "SELECT * from EMP where ENAME = 'KING'",
      [],
      function(err, result) {
        if (err) {console.log('%s', err.message); return;}
        console.log(result.rows);
      }
    );
  });

console.log("Finished – really?");

Wenn Ihr das Programm laufen lasst, greift Ihr direkt mit Javascript auf eine Oracle-Datenbank zu - wer hätte das vor 10 Jahren gedacht ...?

# node emp.js
Finished – really?
[ [ 7839,
    'KING',
    'PRESIDENT',
    null,
    Tue Nov 17 1981 00:00:00 GMT+0100 (CET),
    5000,
    null,
    10 ] ]

Die Ausgabe zeigt deutlich die asynchrone Natur von Node.js. Zunächst baut das Programm eine Datenbankverbindung auf - da dies ein I/O gebundener Vorgang ist, wird ein Callback (als "anonymer" Block) mitgegeben, der dann aufgerufen wird, wenn die Verbindung steht. Der Programmthread arbeitet schon mal weiter mit der Anweisung console.log("Finished - really?"). Das Callback enthält dann das eigentliche Ausführen der SQL-Abfrage und die Ausgabe des Ergebnisses. Das Ausführen der SQL-Abfrage ist wiederum eine asynchrone Operation, die wiederum einen Callback übergeben bekommt. Man merkt schon, dass man mit Node.js etwas anders programmiert als mit "klassischen" Programmiersprachen.

"Screenshots" von Webseiten direkt in die Datenbank: Mit Node.js!

Zum Abschluß möchte ich ein etwas "exotischeres" Node.js Paket vorstellen: webshot. Dieses Node.js Paket ruft eine Webseite auf (ganz wie ein Browser), rendert die HTML-Seite und speichert diese als Bild ab. Damit kann man sich leicht "Vorschaubilder" für Webseiten erstellen - und genau das machen wir jetzt (es scheint, dass webshot keine Proxy-Server unterstützt; ihr müsst also Webseiten im Intranet nehmen). Natürlich speichern wir die Bilder in der Datenbank ab. Zuerst braucht es also eine Tabelle ... und in diese schreiben wir ein paar Zeilen für Webseiten, die wir "aufnehmen" wollen.

create table webshots (
  name varchar2(200)  not null, 
  url varchar2(200)   not null, 
  time_taken date, 
  image blob,
  constraint pk_webshots primary key (name)
);

insert into webshots values (
  'BLOG JSON, REST und Oracle12c','json-rest-oracledb.blogspot.com', null, null
);
insert into webshots values (
  'APEX Community', 'blogs.oracle.com/apexcommunity_deutsch', null, null
); 
insert into webshots values (
  'nodejs.org', 'nodejs.org', null, null
);
insert into webshots values (
  'BLOG SQL und PLSQL', 'sql-plsql-de.blogspot.com', null, null
);

commit;

Als nächstes braucht es das Node-Paket webshot. Ihr könnt es in gewohnter Manier mit npm installieren. Navigiert dazu in euer Arbeitsverzeichnis und führt dort npm install webshot aus.

# npm install webshot
> phantomjs@1.9.18 install /home/oracle/node/work/node_modules/webshot/node_modules/phantomjs

:

+-- tmp@0.0.28 (os-tmpdir@1.0.1)
+-- phantomjs@1.9.18 (which@1.0.9, progress@1.1.8, kew@0.4.0, adm-zip@0.4.4, request-progress@0.3.1, npmconf@2.1.1, request@2.42.0, fs-extra@0.23.1)
#

Danach kommt das Node-Programm selbst. Es soll sich mit der Datenbank verbinden, dann ein SELECT auf die Tabelle WEBSHOTS durchführen. Für jede Zeile soll ein Webshot anhand der Adresse in der Spalte URL gemacht und das Bild in IMAGE abgelegt werden. Der Code ist wie folgt - natürlich müsst Ihr die Koordinaten der Datenbank im Aufruf von oracledb.getConnection anpassen.

var webshot = require('webshot');
var oracledb = require('oracledb');

var options = {
  screenSize: { width: 640 , height: 480 }
 ,shotSize:   { width: 640 , height: 600 }
 ,userAgent:  "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0"
}

function doWebshot(connection, gresult, ix) {
  var pName = gresult.rows[ix][0];
  var pUrl  = gresult.rows[ix][1];
  var pRows = gresult.rows.length;

  connection.execute(
    "UPDATE WEBSHOTS set TIME_TAKEN = SYSDATE, IMAGE = EMPTY_BLOB() where NAME = :name returning IMAGE into :lob",
    { name: pName, lob: {type: oracledb.BLOB, dir: oracledb.BIND_OUT} },
    { autoCommit: false },
    function(err, result) {
      if (err) { console.error(err.message); return; }
      if (result.rowsAffected != 1 || result.outBinds.lob.length != 1) { console.error('Error getting a LOB locator'); return; }

      var slob = result.outBinds.lob[0];
      slob.on('error', function(err) { console.error(err); });

      // Hier passiert es: Node.js macht ein Foto der Webseite
      console.log('Grabbing Webshot for ' + pName);
      var inStream = webshot(pUrl,options);
      inStream.on('error', function(err) { console.error(err); });

      // Die Funktion pipe() überträgt die Bytes in den BLOB-Locator der Datenbank.
      inStream.pipe(slob);

      // Wenn alle Bytes in den LOB geschrieben sind: COMMIT und nächste Zeile.
      slob.on('finish', function() {
        connection.commit(function(err) {
          if (err) {
            console.error(err.message);
          } else {
            console.log("Webshot " + (ix+1) + " of " + pRows + " for " + pName + " stored.");
            if (ix < pRows-1 ) {
              doWebshot(connection, gresult, ix + 1);
            }
          }
        });
      });
    }
  );
}

oracledb.getConnection(
  {
    user          : "scott",
    password      : "tiger",
    connectString : "dbserver:1521/orcl"
  },
  function(err, connection) {
    if (err) {console.error(err.message); return;}
    connection.execute(
      "SELECT NAME, URL from WEBSHOTS",
      [],
      function(err, result) {
        if (err) {console.log('%s', err.message); return;}
        console.log("SELECT successful: Found " + result.rows.length + " rows.");
        doWebshot(connection,result,0);
      }
    );
  });

Speichert diesen Code als Datei webshot.js ab. Nun könnt Ihr die Screenshots generieren lassen.

# node webshot.js
SELECT successful: Found 4 rows.
Grabbing Webshot for BLOG JSON, REST und Oracle12c
Webshot 1 of 4 for BLOG JSON, REST und Oracle12c stored.
Grabbing Webshot for APEX Community
Webshot 2 of 4 for APEX Community stored.
Grabbing Webshot for Nodejs.org
Webshot 3 of 4 for Nodejs.org stored.
Grabbing Webshot for BLOG SQL, PLSQL
Webshot 4 of 4 for BLOG SQL, PLSQL stored.
#

Zum Visualisieren habe ich mir eine kleine APEX-Anwendung gebaut - aber man kann natürlich alles hernehmen, was Bilder in einer Tabelle anzeigt.

Node.js eröffnet wirklich sehr interessante Möglichkeiten - das hier ist allenfalls der Anfang. Viel Spaß beim Ausprobieren und beim Entdecken der Möglichkeiten.

Keine Kommentare:

Kommentar veröffentlichen