Mittwoch, 20. Januar 2016

ORDS und "3-Legged-OAuth": So geht's

Das erste Blog Posting des Jahres 2016 knüpft unmittelbar ans Jahr 2015 an. Zuletzt hatte ich vorgestellt, wie eine Authentifizierung für REST-Dienste mit ORDS eingerichtet werden kann. Zwei Verfahren wurden vorgestellt. Zunächst die einfache Variante, indem Username und Passwort als Basic-Authentication mit dem REST-Request mitgegeben werden und die erste OAuth-Variante, bei welcher sich die Applikation selbst am ORDS-Server anmeldet und ein Token abruft. Solange dieses Token gültig ist, müssen keine sensiblen Authentifizierungsdaten übertragen werden.

Heute geht es um das zweite OAuth-Verfahren, das sog. 3-Legged OAuth. Hier ist es so, dass sowohl die Applikation als auch der Endanwender authentifiziert werden. Für den Endanwender kommt hinzu, dass dieser seine (sensiblen) Login-Daten nicht etwa bei der Anwendung eingibt (wo diese komprimittiert werden könnten). Vielmehr loggt sich der Endanwender bei ORDS selbst ein und autorisiert den REST-Service. Dazu muss der Browser von der Anwendung auf die Login-Seite des ORDS und von dieser wieder zurück auf die Anwendung geleitet werden.

Wichtig für dieses Blog-Posting ist, dass Ihr mit der aktuellsen ORDS-Version 3.0.3 arbeitet. Die Version 3.0.2 enthält einen Fehler, so dass "3-Legged-OAuth" nicht läuft. Also am besten gleich auf 3.0.3 upgraden - das ist sehr einfach und schnell gemacht.

Im folgenden setzen wir also 3-Legged-OAuth für unseren REST-Service ein. Wir verwenden im wesentlichen die Definitionen vom letzten Blog Posting, der Vollständigkeit halber führe ich diese hier aber nochmals auf. Zuerst wird der REST-Service eingerichtet.

/*
 * Zuerst am Datenbankschema (hier: OAUTH3LEG) anmelden
 * Datenbank-Schema für REST-Services freigeben, falls noch nicht geschehen.
 */

begin
  ords.enable_schema;
end;
/
sho err

commit
/

/*
 * ORDS Service erstellen - noch ohne Authentifizierung
 */

begin
  ords.define_service(
    p_module_name =>    'oauth3leg.emp' ,
    p_base_path  =>     'emp/',
    p_pattern =>        'list/',
    p_method =>         'GET',
    p_source_type =>    ords.source_type_query,
    p_source =>         'select * from emp'
  );
end;
/
sho err

commit
/

Nun schützen wir den REST-Service mit dem OAuth-Verfahren. Wie schon gesagt, melden sich Endanwender beim 3-Legged-OAuth-Verfahren direkt am ORDS an. Die User müssen dem Application Server, in dem der ORDS läuft, also bekannt sein. In produktiven Systemen wird es häufig so sein, dass LDAP-Server eingerichtet sind - Application Server wie Weblogic oder andere enthalten hierfür fertige Module. Endanwender können sich dann mit ihren Standard-Passwörtern anmelden. Für dieses Blog-Posting verwenden wir den Standalone-Modus von ORDS - Nutzerkonten werden darin wie folgt angelegt:

C:\> java -jar ords.war user e1 "EmpViewer"
Enter a password for user e1: ********
Confirm password for user e1: ********
Dez 07, 2015 10:22:32 AM oracle.dbtools.standalone.ModifyUser execute
INFO: Created user: e1 in file: D:\oracle\ordsconf\ords\credentials
C:\>

Legt nun einige User in eurem ORDS an - ich verwende e1, e2 und e3. Wichtig ist, dass Ihr die Rolle EmpViewer wie oben blau markiert zuweist. Nur User, welche diese Rolle haben, sollen des REST-Service nutzen können.

Danach legt Ihr die Rolle EmpViewer an und verknüpft sie mit dem REST-Service. Diese Zuordnung, dass die Rolle EmpViewer den REST Service /emp/list/* des Moduls oauth3leg.emp ausführen darf, merkt sich ORDS als Privileg. Das Privileg trägt den Namen my.listEmployees. Wichtig ist hier der Parameter P_DESCRIPTION, dieser muss gesetzt werden, nur dann erkennt ORDS, dass hier das 3-Legged-OAuth verwendet werden soll. Das ist auch nachvollziehbar, denn dieser Text wird dem Endanwender angezeigt, wenn er den Zugriff auf den REST-Service freigeben soll.

begin
  ords.create_role(
    p_role_name => 'EmpViewer'
  );
end;
/
sho err

declare
  l_roles    owa.vc_arr;
  l_patterns owa.vc_arr;
  l_modules  owa.vc_arr;
begin
  -- Liste der Rollen, denen das Privileg zugeordnet sein soll 
  -- Weitere werden mit l_roles(2), l_roles(3) usw. hinzugefügt.
  l_roles(1) := 'EmpViewer';

  -- Liste der URL-Patterns, die geschützt werden sollen.
  l_patterns(1) := '/emp/list/*';
  l_modules(1) := 'oauth3leg.emp';
  ords.define_privilege(
    p_privilege_name => 'my.listEmployees'
   ,p_roles =>          l_roles
   ,p_patterns =>       l_patterns     
   ,p_modules =>        l_modules      
   ,p_description =>    'Die Beschreibung ist wirklich wichtig!'
   ,p_label =>          'List of Employees'
  );
end;
/

commit
/

Num kommt die Einrichtung der Client-Anwendung - das sieht so ähnlich aus, wie beim letzten Blog-Posting, wir brauchen nur etwas andere Parameter: Nach erfolgreicher Anmeldung und Freigabe des REST-Requests muss der Browser ja zur Anwendung zurückgeleitet werden - diese Callback-URL muss angegeben werden. ORDS versieht diese dann mit Parametern und leitet den Browser zurück. Im Beispiel verwende ich eine statische Datei (http://localhost:8081/rest-oauth.html); es wäre aber auch ein komplett anderer Webserver oder gar eine spezielle URL zum Öffnen einer App auf einem mobilen Gerät denkbar. Außerdem wichtig ist der Parameter P_PRIVILEGE_NAMES, das legt fest, welche Privilegien (also Zuordungen von Rollen zu REST-Services) die Anwendung nutzen kann. Spielt also diesen PL/SQL Aufruf in euer Datenbankschema ein.

begin
 oauth.create_client(
    p_name => 'SimpleHtml'
   ,p_grant_type => 'authorization_code'
   ,p_owner => 'Carsten'
   ,p_description => 'Einfaches HTML / JS Beispiel'
   ,p_redirect_uri => 'http://localhost:8081/rest-oauth'
   ,p_support_email => 'carsten.czarski@oracle.com'
   ,p_support_uri => 'http://localhost:8081'
   ,p_privilege_names => 'my.listEmployees'
    );
end;
/
sho err

commit
/

Nun ist die Applikation registriert und ORDS hat eine Client-ID und ein Client-Secret erzeugt. Diese kann man sich als Usernamen und Passwort für eine Client-Anwendung vorstellen. Die folgende SQL-Abfrage zeigt sie an - für die nachfolgenden Schritte werden wir sie brauchen.

SQL> select name, client_id, client_secret from user_ords_clients;

NAME                           CLIENT_ID                        CLIENT_SECRET
------------------------------ -------------------------------- --------------------------------
SimpleHtml                     7NPUOs1pmKbgN6bL8jzQlw..         x7DC0U1vRG2X_mQzQCZExA..

1 Zeile wurde ausgewählt.

Nun wollen wir die OAuth-Authentifizierung testen. Der Setup ist nun etwas komplexer als beim letzten Mal und läuft in mehreren Schritten ab.

Zuerst braucht die Anwendung einen sog. Authentication Code - um diesen zu erhalten, muss der Endanwender aus der Anwendung heraus zu ORDS umgeleitet werden. Dort kann er sich anmelden und die gewünschte Operation freigeben. Diese Aktionen erfolgen mit dem Browser - der erste Schritt muss also mit einem Browser (oder einer HTML-Render-Bibliothek) erfolgen. Der Prozess zum Erlangen des Authentication Code sieht wie folgt aus:

  1. Die spezielle ORDS-URL /ords/{db-schema}/oauth/auth mit den URL-Paremetern response_type, client_id und state wird aufgerufen. response_type erhält den festen Wert "code", als client_id wird die CLIENT_ID aus der View USER_ORDS_CLIENTS mitgegeben und state sollte ein zufällig generierter Wert sein. Anhand des state kann die Client-Anwendung später erkennen, dass der Browser von ORDS zurückgeleitet wurde und sie kann es auf den richtigen Authentication-Code-Request matchen.
    /ords/oauth3leg/oauth/auth?response_type=code&client_id=7NPUOs1pmKbgN6bL8jzQlw..&state=4711
    
  2. Der Browser wird auf die URL aus dem ersten Schritt umgeleitet und fordert nun den Endanwender zum Anmelden auf.
  3. Der Endanwender meldet sich mit Usernamen und Passwort an - wohlgemerkt: Die Anwendung selbst bekommt die Login-Daten nicht zu sehen.
  4. Nach erfolgreicher Anmeldung zeigt ORDS, welche Rechte die Anwendung anfragt. Nun wird auch deutlich, warum die Beschreibung im Aufruf von ORDS.CREATE_PRIVILEGE so wichtig ist, denn diese dient der Erklärung des Vorgangs.
  5. Nach erfolgreicher Freigabe leitet ORDS den Browser auf die im Client konfigurierte Redirect-URL zurück. Der geforderte Authentication Code wird als URL-Parameter code angehängt. Außerdem wird der zufällig gewählte URL-Parameter state unverändert zurückgegeben - die Client-Anwendung kann also den Zusammenhang zum ersten Schritt herstellen.
    http://localhost:8081/rest-oauth?code=R8WkUCIQmxfq7ZqH3A3IGA..&state=4711
    
    Siehe dazu nochmals den Aufruf von ORDS.CREATE_CLIENT.
     oauth.create_client(
       :
      ,p_redirect_uri => 'http://localhost:8081/rest-oauth'
      ,p_support_email => ...
       :
     );
    

Als nächstes muss sich die Anwendung das Access Token von ORDS holen - dies wird dann wieder eine bestimmte Weile gültig sein und so das erneute Authentifizieren ersparen. Mit dem Authorization Code kann nur einmal ein Access Token angefordert werden; um ein neues Access Token zu erzeugen, braucht es einen neuen Authorization Code.

  1. Die Anwendung führt einen POST-Request auf die URL /ords/{db-schema}/oauth/token durch. Die URL erwartet eine HTTP Basic Authentication mit der CLIENT_ID aus der View USER_ORDS_CLIENTS als "Usernamen" und dem CLIENT_SECRET als "Passwort". Als Request-Body wird muss der vorher erlangte Authentication Code wie folgt übergeben werden.
    grant_type=authorization_code&code={Authentication Code aus Schritt 1}
    
    Verwendet man Javascript, jQuery und die Funktion ajax(), dann könnte der Call wie folgt aussehen.
      $.ajax({
        type: "POST",
        url: "/ords/oauth3leg/oauth/token",
        data: "grant_type=authorization_code&code=" + R8WkUCIQmxfq7ZqH3A3IGA..,
        beforeSend: function (xhr) {
          // HTTP Basic Auth mit CLIENT_ID und CLIENT_SECRET (siehe oben)
          xhr.setRequestHeader("Authorization", "Basic " + btoa("7NPUOs1pmKbgN6bL8jzQlw.." + ":" + "x7DC0U1vRG2X_mQzQCZExA.."));
        },
        success: function (data) {
          // process JSON here
        },
        dataType: "json"
      });
    
    Mit dem Kommandozeilentool curl sieht das so aus:
    C:\> curl -i 
                 --user 7NPUOs1pmKbgN6bL8jzQlw..:x7DC0U1vRG2X_mQzQCZExA..
                 --data "grant_type=authorization_code&code=R8WkUCIQmxfq7ZqH3A3IGA.." 
                 http://localhost:8081/ords/oauth3leg/oauth/token
    
  2. ORDS wird den Request im JSON-Format wie folgt beantworten.
    {
      "access_token":"EYrbSdTgG0IZ-QKc2g2OKQ..",
      "token_type":"bearer",
      "expires_in":3600,
      "refresh_token":"J9XgTEihshpobsUu3Ilncw.."
    }
    
Die Client-Anwendung kann also nicht nur das Access-Token selbst entnehmen, sondern auch die Gültigkeit in Sekunden und das Refresh-Token, mit dem sie sich ein neues Access Token ohne den gesamten Prozess holen kann. Mit dem Access Token kann nun auf den REST Service zugreifen; und das geht nicht nur mit dem Browser, sondern auch von anderen Prozessen aus, wie der folgende curl-Aufruf zeigt.

C:\> curl -H"Authorization: Bearer EYrbSdTgG0IZ-QKc2g2OKQ.." -i http://localhost:8081/ords/oauth3leg/emp/list/
 
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "AwR8DXNHtIkyk4PbmSr0PJnZbaCYETdkmg17Yi28Q3dKq4a6+De3o/GMEbQiv+TjY5z/Owyr3hnHfk2RPmd7Pg=="
Transfer-Encoding: chunked

{
    "items" : [ {
        "empno" : 7369,
        "ename" : "SMITH",
        "job" : "CLERK",
        "mgr" : 7902,
        "hiredate" : "1980-12-16T23:00:00Z",
        "sal" : 800,
        "comm" : null,
        "deptno" : 20

So ist sehr gut nachvollziehbar, wie, zum Beispiel, eine mobile Anwendung mit ORDS kommuniziert. Der Endanwender öffnet die App (welche durchaus eine Native Anwendung sein kann); diese holt sich dann vom ORDS den Authentication Code. Auf dem mobilen Gerät öffnet sich dann der Browser, der Endbenutzer loggt sich ein und gibt den Request frei. Als Redirect-URL wird eine URL verwendet, welche wiederum die mobile Anwendung öffnet und dieser den Authentication Code übergibt. Nun kann die mobile Anwendung sich ein Access Token holen und die REST-Dienste aufrufen.

Viel Spaß beim Ausprobieren.

Montag, 7. Dezember 2015

Bitte Anmelden: Authentifizierung für REST Dienste mit ORDS

In diesem Blog-Posting geht es wieder um Oracle REST Data Services (ORDS) - und hier um die Authentifizierung von Nutzern. Während dieses Thema bei allen anderen Postings in der Vergangenheit außen vor blieb, wollen wir es heute in den Mittelpunkt stellen. Dieses Blog-Posting wurde mit ORDS 3.0.2 erstellt.

ORDS bietet unterschiedliche Varianten an, REST Services mit einer Authentifizierung zu schützen. Der einfachste Weg sind Username und Passwort: etwas fortgeschrittener ist der Weg über das OAuth-Protokoll. Zunächst sei der Weg mit Username und Passwort kurz vorgestellt.

Um die Beispiele auszuprobieren, erzeugt Ihr am besten einen einfachen REST-Service wie folgt. Es wird angenommen, dass ORDS bereits installiert ist und läuft und dass Ihr Zugang zu einem Datenbankschema mit der EMP-Tabelle habt. Das Datenbankschema heißt in diesem Posting RESTAUTH; wenn euer Datenbankschema anders heißt, passt dies an den entsprechenden Stellen (vor allem ORDS-URLs) an. Wenn Ihr noch keine ORDS-Installation habt, findet Ihr Hinweise zur Installation in diesem Blog-Posting.

/*
 * Zuerst im Datenbankschema (hier: RESTAUTH) anmelden.
 * Datenbank-Schema für REST-Services freigeben, falls noch nicht geschehen.
 */

begin
  ords.enable_schema;
end;
/
sho err

commit
/

/*
 * ORDS Service erstellen - noch ohne Authentifizierung
 */

begin
  ords.define_service(
    p_module_name =>    'restauth.emp' ,
    p_base_path  =>     'emp/',
    p_pattern =>        'list/',
    p_method =>         'GET',
    p_source_type =>    ords.source_type_query,
    p_source =>         'select * from emp'
  );
end;
/
sho err

commit
/

Mit einem REST Client könnt Ihr den Service testen - und er wird funktionieren (im Moment noch ohne Authentifizierung). Hier arbeite ich mit dem Kommandozeilentool curl.

D:\> curl http://localhost:8081/ords/restauth/emp/list/ 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1845    0  1845    0     0   9088      0 --:--:-- --:--:-- --:--:--  9088
{
    "items" : [ {
        "empno" : 7369,
        "ename" : "SMITH",
        "job" : "CLERK",
        "mgr" : 7902,
        "hiredate" : "1980-12-16T23:00:00Z",
        "sal" : 800,
        "comm" : null,
        "deptno" : 20
    }, {
        "empno" : 7499,
        "ename" : "ALLEN",
        "job" : "SALESMAN",
        "mgr" : 7698,

Sicherheit - Schritt 1: Usernamen und Passwort

Nun geht es daran, diesen REST-Service zu schützen. ORDS arbeitet zunächst mit Privilegien und Rollen. Diese werden im zweiten Schritt dann an Usernamen (bei Username/Passwort-Authentifizierung) oder an registierte OAuth-Clients vergeben. Die folgenden PL/SQL Aufrufe erzeugen ein Privileg listEmployees und eine Rolle EmpAdmin.

begin
  ords.create_role(
    p_role_name => 'EmpAdmin'
  );
end;
/
sho err

declare
  l_roles    owa.vc_arr;
  l_patterns owa.vc_arr;
begin
  -- Liste der Rollen, denen das Privileg zugeordnet sein soll 
  -- Weitere werden mit l_roles(2), l_roles(3) usw. hinzugefügt.
  l_roles(1) := 'EmpAdmin';

  -- Liste der URL-Patterns, die geschützt werden sollen.
  l_patterns(1) := '/emp/list/*';

  ords.define_privilege(
    p_privilege_name => 'listEmployees'
   ,p_roles =>          l_roles
   ,p_patterns =>       l_patterns     
   ,p_label =>          'List of Employees'
  );
end;
/

commit
/

Nun sind Rolle und Privileg angelegt; und alle URL, die mit /emp/list beginnen, sind geschützt. Versucht man den curl-Aufruf von vorhin nochmal, so bekommt man nun einen Fehler.

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Content-Length: 11549

:

Im Browser würde man das folgende Bild sehen.

Doch wie meldet man sich an? Im ORDS wurden nur Rollen und Privilegien definiert, aber noch keine Usernamen und Passwörter. Grund ist, dass ORDS selbst keine Benutzernamen und Passwörter verwaltet - diese Aufgabe übernimmt der Application Server, in dem ORDS läuft. Verwendet man einen Tomcat, so müssen nun im Tomcat die Usernamen und Passwörter angelegt werden, gleiches gilt für Weblogic oder jeden anderen Java-Server.

Auch der Standalone-Webserver, mit dem ORDS ausgeliefert ist, bringt eine minimalistische Nutzerverwaltung mit. Darin legen wir nun einen neuen User (rot) an und weisen diesem gleichzeitig die Rolle EmpAdmin (blau) zu.

C:\> java -jar ords.war user cczarski "EmpAdmin"
Enter a password for user cczarski: ********
Confirm password for user cczarski: ********
Dez 07, 2015 10:22:32 AM oracle.dbtools.standalone.ModifyUser execute
INFO: Created user: cczarski in file: D:\oracle\ordsconf\ords\credentials
C:\>

Um den Service nun (wiederum mit curl) aufrufen zu können, müssen Authentifizierungsdaten mitgegeben werden (HTTP Basic Authentication).

D:\> curl -u cczarski:******** -i http://localhost:8081/ords/restauth/emp/list/
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "AwR8DXNHtIkyk4PbmSr0PJnZbaCYETdkmg17Yi28Q3dKq4a6+De3o/GMEbQiv+TjY5z/Owyr3hnHfk2RPmd7Pg=="
Transfer-Encoding: chunked

{
    "items" : [ {
        "empno" : 7369,
        "ename" : "SMITH",
        "job" : "CLERK",
        "mgr" : 7902,
        "hiredate" : "1980-12-16T23:00:00Z",
        "sal" : 800,
        "comm" : null,
        "deptno" : 20

Wichtig bei diesem Setup ist, dass alles genau zusammenpasst: Die Rolle, die dem Nutzerkonto im Application Server zugewiesen ist, muss genau zur Rolle, die mit ORDS.CREATE_ROLE erzeugt wurde, passen. Auch Groß- und Kleinschreibung sind hier wichtig. Zu empfehlen ist auch, sich auf reine ASCII-Zeichen zu beschränken und das Leerzeichen nicht zu nutzen. Es mag zwar sein, dass ORDS damit keine Probleme hat - es kann aber sein, dass es Beschränkungen in den Java-Containern gibt, die dann dazu führen, dass die Rollendefinitionen nicht mehr zusammenpassen.

Mehr Sicherheit: OAuth-Verfahren

Der größte Nachteil am bis jetzt beschriebenen Verfahren ist, dass die Login-Credentials (Usernamen und Passwort) bei jedem Request mitgeschickt werden müssen. Technisch wird das Format {username}:{password} bei dieser Basic-Authentication ins Base64-Format konvertiert und als HTTP-Header Authorization mitgesendet. Das Verwenden von HTTPS sollte bei dieser Authentifizierungsvariante selbstverständlich sein - ohne SSL-Verschlüsselung kann jedermann die Login-Daten mitlesen. Aber auch mit Verschlüsselung gibt es bessere Verfahren: So wäre es doch sinnvoller, Usernamen und Passwort nur einmal (bei der intialen Anmeldung) zu senden und danach mit einem Token zu arbeiten. Das hat den entscheidenden Vorteil, dass auch die Client-Anwendung sich das Passwort nicht meht mehr merken muss. Im folgenden setzen wir also eine einfache OAuth-Authentifizierung für unseren REST-Service auf.

Ein OAuth-Client muss zuerst als solcher registriert werden. Das geht wie folgt:

/*
 * Client-Applikation erstellen
 */

begin 
  oauth.create_client(
    p_name =>            'OAuth EMP List Application',
    p_grant_type =>      'client_credentials',
    p_privilege_names => '',
    p_support_email =>   'carsten.czarski@oracle.com'
  );
end;
/
sho err

commit
/

/*
 * Rolle zuweisen
 */

begin
  oauth.grant_client_role(
    p_client_name => 'OAuth EMP List Application',
    p_role_name =>   'EmpAdmin'
  );
end;
/

commit 
/

Nun ist die Applikation registriert und ORDS hat eine Client-ID und ein Client-Secret erzeugt. Diese kann man sich als Usernamen und Passwort für eine Client-Anwendung vorstellen. Die folgende SQL-Abfrage zeigt sie an - für den nachfolgenden curl Aufruf werden wir sie brauchen.

SQL> select name, client_id, client_secret from user_ords_clients;

NAME                           CLIENT_ID                        CLIENT_SECRET
------------------------------ -------------------------------- --------------------------------
OAuth EMP List Application     oMQvSnxOUOTXWd3oKgZN9Q..         PyFS4qVFSe_hk08BzTlmtA..

1 Zeile wurde ausgewählt.

Auch die Zuordnung der Rolle zur Client-Anwendung kann man sich ansehen.

SQL> select * from user_ords_client_roles;

 CLIENT_ID CLIENT_NAME                       ROLE_ID ROLE_NAME
---------- ------------------------------ ---------- ---------------
     10508 OAuth EMP List Application          10489 EmpAdmin

1 Zeile wurde ausgewählt.

Nun wollen wir die OAuth-Authentifizierung testen. Dazu braucht es im ersten Schritt die oben angezeigten Client-ID und das Client-Secret (bei euch sind das natürlich dann andere Werte). Der erste REST-Call mit curl dient nun allein der Authentifizierung der Client-Anwendung - und wir bekommen demzufolge auch nur ein Access Token zurück. Mit diesem führt man danach den eigentlichen REST-Call durch. Zuerst also der Call zur Authentifizierung ...

C:\> curl -i 
             --user oMQvSnxOUOTXWd3oKgZN9Q..:PyFS4qVFSe_hk08BzTlmtA.. 
             --data "grant_type=client_credentials" 
             http://localhost:8081/ords/restauth/oauth/token
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked

{"access_token":"LfXJilIBdzj5JPRn4xb5QQ..","token_type":"bearer","expires_in":3600}

Der OAuth-Dienst von ORDS steht unter dem URL-Pattern oauth/token bereit. Client-ID und Client-Secret werden wie als HTTP Basic Authentication mitgegeben. In einem REST-Client im Browser sieht das in etwa wie folgt aus.

In der JSON-Antwort findet sich nun das Access-Token, dass bei fortfolgenden REST-Anfragen wie folgt als HTTP-Header Authorization mitgegeben wird.

C:\> curl -H"Authorization: Bearer nHpPl5J_0NgtFephW7n6gQ.." -i http://localhost:8081/ords/restauth/emp/list/
 
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "AwR8DXNHtIkyk4PbmSr0PJnZbaCYETdkmg17Yi28Q3dKq4a6+De3o/GMEbQiv+TjY5z/Owyr3hnHfk2RPmd7Pg=="
Transfer-Encoding: chunked

{
    "items" : [ {
        "empno" : 7369,
        "ename" : "SMITH",
        "job" : "CLERK",
        "mgr" : 7902,
        "hiredate" : "1980-12-16T23:00:00Z",
        "sal" : 800,
        "comm" : null,
        "deptno" : 20

Das Token ist 3600 Sekunden, also eine Stunde lang gültig. Solange müssen Client-ID und Client-Secret nicht mehr über die Leitung gesendet werden. Das reicht typischerweise für eine Anwendungssession aus.

Das OAuth-Protokoll geht aber noch weiter: Das Ziel ist es, dass eine externe Anwendung (losgelöst von ORDS) eine OAuth-Authentifizierung startet - woraufhin der Browser zur Anmeldemaske von ORDS geleitet wird. Nach erfolgreicher Anmeldung wird der Browser zur externen Anwendung zurückgeleitet und das OAuth-Token wird dabei mitgesendet. Die externe Anwendung kann mit diesem Token nun REST-Dienste im ORDS aufrufen. Die Anwendung muss sich keinerlei Usernamen und Passwörter merken und kennt allein die OAuth-Tokens. Der Endanwender gibt seine Usernamen und Passwörter nur in die Anmeldemaske des ORDS ein. Wie das aufgesetzt wird, wird dann in einem der nächsten Blog-Postings erläutert. Bis dahin viel Spaß beim Ausprobieren.

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.

Montag, 19. Oktober 2015

Modern Application Development - ab Januar 2016

Im Januar 2016 starten Karin Patenge, Rainer Willems, Beda Hammerschmidt, Kai Donato, Dietmar Aust und ich - in Zusammenarbeit mit der DOAG eine Online-Videoserie zum Thema Modern Application Development - in der Praxis.

In acht Vidoes geht es um JSON, REST-Services, HTML5, Node.js und ähnliche, aktuell heiß diskutierte Entwicklerthemen. Alles wird im Bezug zum Unternehmenseinsatz betrachtet - die Integration in die IT-Landschaft oder mit der Oracle-Datenbank spielt also eine besondere Rolle. Die Teilnahme ist auch hier kostenlos - wenn Ihr auf aktuellem Stand bleiben möchtet, meldet euch gleich an (Klickt oben rechts auf Anmelden). Abgeschlossen wird die Reihe mit einer Online Q & A, bei der Ihr die Gelegenheit habt, mit den Experten zu diskutieren und eure Fragen zu stellen.

Schaut mal rein - sehen wir uns ...?

Dienstag, 29. September 2015

Dateien in Tabellen speichern: Komplette REST-API mit ORDS

Heute geht es doch nochmals um ORDS - das angekündigte Posting zu Node.js muss noch etwas warten. Ich möchte heute zeigen, wie Ihr mit der Oracle-Datenbank und ORDS einen REST-Service aufsetzt, der Dateien in einer Tabelle speichert und aus dieser abrufen kann. PUT und POST Requests sollen Dateien (Bilder, Dokumente) in eine Tabelle laden, GET-Requests sollen sie abrufen; DELETE logischerweise löschen. Dazu soll genau ein REST-Modul entstehen, welches diese vier Fälle (und noch einen mehr) komplett abdeckt.
Im letzten Blog-Posting wurde ja das neue rest-Kommando im neuen SQL Developer Command Line Interface (sqlcl vorgestellt - die Ausgabe dieses Tools ist die Vorlage für das Erzeugen unseres neuen REST-Service, denn ich möchte euch gerne die Skripte zeigen; die lassen sich doch wesentlich leichter nachvollziehen als das Klicken in einem Werkzeug. Wir werden in diesem Posting viel mit dem (derzeit undokumentierten) PL/SQL-Paket ORDS_SERVICES arbeiten.
Dieses Beispiel lässt sich übrigens auch ganz wunderbar im Oracle Database Cloud Service nachvollziehen; das ist sicherlich einer der einfachsten und schnellsten Wege, an einen laufenden Datenbank-Service im Internet zu kommen.
Zuerst braucht es, wie immer in einer Datenbank, eine Tabelle. Die legen wir auch genz einfach an. Dieses Blog-Posting geht davon aus, dass euer Datenbankschema DOCUMENTS heißt - passt die Skripte bei Bedarf einfach an.
create table my_documents(
  id           number(10)   generated always as identity
 ,mimetype     varchar2(200) 
 ,content      blob
 ,created      date         default sysdate not null
 ,last_updated date         default sysdate not null
 ,constraint  pk_mydocuments primary key (id)
)
/
Danach geht es an die Erstellung der REST-Services. Der erste Service soll zum Hochladen von Dokumenten dienen. Ihr könnt die REST-Services mit dem SQL Developer anlegen, in diesem Blog-Posting zeige ich aber den Skriptbasierten Ansatz; Ihr könnt also in eurem SQL-Tool mit Copy & Paste arbeiten. Zuerst sollte das Datenbankschema für REST-Zugriffe freigegeben werden (ORDS.ENABLE_SCHEMA). Führt diesen Call nicht als DBA; sondern als User DOCUMENTS aus. Ich gehe davon aus, dass euer ORDS bereits läuft - wenn nicht, könnt Ihr hier Details zum initiale Setup nachlesen.
BEGIN
  ORDS.ENABLE_SCHEMA(
    p_enabled             => TRUE,
    p_schema              => 'DOCUMENTS',
    p_url_mapping_type    => 'BASE_PATH',
    p_url_mapping_pattern => 'documents',
    p_auto_rest_auth      => FALSE
  );
END;
/

COMMIT
/
Als nächstes erzeugen wir den REST-Service. Das folgende Skript löscht zuerst ein etwaiges bereits definiertes REST-Modul docmodule; danach wird es neu angelegt. Ein Module ist als "Container" für mehrere zusammengehörige REST-Dienste zu verstehen. Direkt danach kommt die Definition der URL-Templates. Einem URL-Template können dann mit ORDS_SERVICES.ADD_HANDLER die REST-Dienste zugeordnet werden - und zwar genau ein Dienst pro HTTP-Methode (GET, PUT, POST, DELETE und andere).
declare
  l_moduleid   number;
  l_templateid number;
  l_handlerid  number;
begin
  -- evtl. vorhandenes REST-Modul löschen
  ords_services.delete_module('docmodule');

  -- REST Modul anlegen
  l_moduleid := ords_services.create_module(
    p_name       => 'docmodule'
   ,p_uri_prefix => 'doc/'
   ,p_status     => 'PUBLISHED'
  );

  -- 1. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/'
  );

  -- POST-Handler für das URL Template. Der REQUEST führt
  -- PL/SQL-Code aus und speichert die per Request hochgeladene Datei (:body) in die
  -- Tabelle ab.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'declare
                         l_id my_documents.id%type;
                       begin 
                        insert into my_documents(
                          mimetype, content
                        ) values (
                          :content_type, :body
                        ) returning id into l_id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "created", "id": ''||l_id||''}'');
                       end;'
   ,p_method       => 'POST'
  );
end;
/

commit
/
Der entscheidende Code befindet sich im Aufruf von ORDS_SERVICES.ADD_HANDLER. Hier wird als Argument für P_SOURCE ein PL/SQL-Block übergeben. Der macht im Grunde nichts weiter als ein SQL INSERT in die oben erzeugte Tabelle MY_DOCUMENTS. Wenn Ihr hier PL/SQL-Code schreibt, stellt ORDS euch auch einige Umgebungsvariablen bereit: Zwei seht ihr hier: :body enthält den konkreten Request Body des POST-Requests und :content_type enthält den Mime-Type, den der REST-Client im HTTP-Header Content-Type mitgeschickt hat. Es gibt aber noch ein paar mehr - dieses Blog-Posting von Jon Dixon listet sie auf. Wenn der INSERT erfolgt ist, wird die neu erzeugte ID des Dokument in einem JSON-Format zurückgegeben. Probiert den neuen REST-Service mal aus - mit dem Chrome Advanced REST Client sieht das ganze so aus ...
... und mit dem Kommandozeilentool curl so (hier sind noch einige Statusmeldungen dabei). Ich persönlich habe eine Vorliebe für curl zum Testen von REST-Services entwickelt - damit ist man einfach nicht mehr den "Launen" der verschiedenen GUI-Werkzeuge ausgeliefert (und irgendeine Marotte haben sie alle).
D:\>curl -v -X POST "http://localhost:8081/ords/documents/doc/documents/" --data-binary "@ar.png" -H "Content-Type:image/png"
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8081 (#0)
> POST /ords/documents/doc/documents/ HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.44.0
> Accept: */*
> Content-Type:image/png
> Content-Length: 46098
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Transfer-Encoding: chunked
<
{"status": "created", "id": 2}
* Connection #0 to host localhost left intact
Schauen wir uns die Daten in der Tabelle an - das geschieht im SQL-Client.
SQL> select * from my_documents;

  ID MIMETYPE   CONTENT                        CREATED             LAST_UPDATED
---- ---------- ------------------------------ ------------------- -------------------
   3 image/png  89504E470D0A1A0A0000000D494844 29.09.2015 11:01:22 29.09.2015 11:01:22
                52000003850000021B0802000000D3
                A9AC70000000097048597300000EC4
                00000EC401952B0E1B000020004944
                4154789CEC9D796055C5D9FF9F67CE
                0D9B408424

   4 image/png  89504E471A01DFDA562FD82A0151AA 29.09.2015 11:01:58 29.09.2015 11:01:58
                C43D8A8822EE0BB1EFFB6B53B1AE68
                5B3535EDAB2501AD5A2D4A6D956A5B
                631537625D63DFAA80D1BEF5B56025
                B26917D942EE39F3FCFE983973E62C
                F7DE736F2E
Das ist doch ein sehr einfacher und angenehmer Weg, Dateien in eine Tabelle zu laden, oder? Und damit ist der erste REST-Service (Upload) auch schon fertig. Die nächsten drei Services liegen nahe. Für das URL-Pattern /doc/documents/{id} soll das Dokument mit der jeweiligen ID ...
  • mit der Methode GET abgerufen
  • mit der Methode PUT aktualisiert
  • mit der Methode DELETE gelöscht
... werden. Das Skript dafür sieht so aus (damit Ihr einfaches Copy & Paste machen könnt, hier nochmals das vollständige Skript. Der bereits vorhandene erste Teil ist ausgegraut.
declare
  l_moduleid   number;
  l_templateid number;
  l_handlerid  number;
begin
  -- evtl. vorhandenes REST-Modul löschen
  ords_services.delete_module('docmodule');

  -- REST Modul anlegen
  l_moduleid := ords_services.create_module(
    p_name       => 'docmodule'
   ,p_uri_prefix => 'doc/'
   ,p_status     => 'PUBLISHED'
  );

  -- 1. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/'
  );

  -- POST-Handler für das URL Template. Der REQUEST führt
  -- PL/SQL-Code aus und speichert die per Request hochgeladene Datei (:body) in die
  -- Tabelle ab.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'declare
                         l_id my_documents.id%type;
                       begin 
                        insert into my_documents(
                          mimetype, content
                        ) values (
                          :content_type, :body
                        ) returning id into l_id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "created", "id": ''||l_id||''}'');
                       end;'
   ,p_method       => 'POST'
  );

  -- 2. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/{id}'
  );

  -- GET-Handler für das URL Template. Das angefragte Dokument ({id}) wird
  -- per SQL Query abgerufen und ausgegeben. Dazu bietet ORDS einen fertigen
  -- "Source Type" MEDIA QUERY an.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_media
   ,p_source       => 'select mimetype, content from my_documents where id = :id'
   ,p_method       => 'GET'
  );

  -- DELETE-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL DELETE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        delete from my_documents where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "deleted"}'');
                       end;'
   ,p_method       => 'DELETE'
  );

  -- PUT-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL UPDATE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        update my_documents set content = :body, mimetype = :content_type, last_updated = sysdate where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "updated", "id": ''||:id||''}'');
                       end;'
   ,p_method       => 'PUT'
  );
end;
/

commit
/
Testen wir die neuen REST-Dienste.
  • Beginnen wir mit GET (das Dokument soll in die Datei file.bin) gespeichert werden:
    D:\>curl -v -X GET http://localhost:8081/ords/documents/doc/documents/3 -o file.bin
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1...
    * Connected to localhost (127.0.0.1) port 8081 (#0)
    > GET /ords/documents/doc/documents/5 HTTP/1.1
    > Host: localhost:8081
    > User-Agent: curl/7.44.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Content-Type: image/png
    < ETag: "ZuV0opwWReMeFm8NwmK8IPxuZfldcE+uB7n95A2Bwg6VE9z89b31m2VMNiKGWqTeD+IZ41hpPHAcZppbuOw/yA=="
    < Transfer-Encoding: chunked
    <
    { [16214 bytes data]
    100 46098    0 46098    0     0   261k      0 --:--:-- --:--:-- --:--:--  288k
    * Connection #0 to host localhost left intact
    
    An der Ausgabe (Content-Type) könnte der Client auch erkennen, was da konkret vom REST-Service ausgeliefert wird. In diesem Fall wäre es ein Bild (image/png).
  • Dann löschen wir ein Dokument ...
    D:\>curl -v -X DELETE http://localhost:8081/ords/documents/doc/documents/3
    *   Trying 127.0.0.1...
    * Connected to localhost (127.0.0.1) port 8081 (#0)
    > DELETE /ords/documents/doc/documents/3 HTTP/1.1
    > Host: localhost:8081
    > User-Agent: curl/7.44.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    < Transfer-Encoding: chunked
    <
    {"status": "deleted"}
    * Connection #0 to host localhost left intact
    
  • Und schließlich noch ein Update ...
    D:\>curl -v -X PUT "http://localhost:8081/ords/documents/doc/documents/4" --data-binary "@index.html" -H "Content-Type:text/html"
    *   Trying 127.0.0.1...
    * Connected to localhost (127.0.0.1) port 8081 (#0)
    > PUT /ords/documents/doc/documents/4 HTTP/1.1
    > Host: localhost:8081
    > User-Agent: curl/7.44.0
    > Accept: */*
    > Content-Type:text/html
    > Content-Length: 381
    >
    * upload completely sent off: 381 out of 381 bytes
    < HTTP/1.1 200 OK
    < Content-Type: text/html; charset=UTF-8
    < Transfer-Encoding: chunked
    <
    {"status": "updated", "id": 4}
    * Connection #0 to host localhost left intact
    
Fehlt zum Abschluß nur noch eine Übersicht über die bereits vorhandenen Dokumente - hierzu bietet sich wiederum ein GET-Request auf die Collection selbst an - also das URL-Pattern /doc/documents/. Wenn eine ID angehägt wird, soll das konkrete Dokument ausgeliefert werden (das ist bereits fertig), wenn keine ID angehängt wird, soll eine Übersicht - im JSON-Format mit Links zu den einzelnen Dokumenten - ausgegeben werden. Hier wiederum das komplette Skript - die Neuerungen sind farblich hervorgehoben.
declare
  l_moduleid   number;
  l_templateid number;
  l_handlerid  number;
begin
  -- evtl. vorhandenes REST-Modul löschen
  ords_services.delete_module('docmodule');

  -- REST Modul anlegen
  l_moduleid := ords_services.create_module(
    p_name       => 'docmodule'
   ,p_uri_prefix => 'doc/'
   ,p_status     => 'PUBLISHED'
  );

  -- 1. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/'
  );

  -- POST-Handler für das URL Template. Der REQUEST führt
  -- PL/SQL-Code aus und speichert die per Request hochgeladene Datei (:body) in die
  -- Tabelle ab.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'declare
                         l_id my_documents.id%type;
                       begin 
                        insert into my_documents(
                          mimetype, content
                        ) values (
                          :content_type, :body
                        ) returning id into l_id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "created", "id": ''||l_id||''}'');
                       end;'
   ,p_method       => 'POST'
  );

  -- GET-Handler für das URL Template "documents/" (ohne ID). Es wird (Source Type "feed")
  -- eine Übersicht mit allen Dokumenten ausgegeben.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_feed
   ,p_source       => 'select 
                         id, 
                         mimetype, 
                         dbms_lob.getlength(content) as doc_size, 
                         created, 
                         last_updated 
                       from my_documents'
   ,p_method       => 'GET'
  );

  -- 2. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/{id}'
  );

  -- GET-Handler für das URL Template. Das angefragte Dokument ({id}) wird
  -- per SQL Query abgerufen und ausgegeben. Dazu bietet ORDS einen fertigen
  -- "Source Type" MEDIA QUERY an.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_media
   ,p_source       => 'select mimetype, content from my_documents where id = :id'
   ,p_method       => 'GET'
  );

  -- DELETE-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL DELETE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        delete from my_documents where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "deleted"}'');
                       end;'
   ,p_method       => 'DELETE'
  );

  -- PUT-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL UPDATE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        update my_documents set content = :body, mimetype = :content_type, last_updated = sysdate where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "updated", "id": ''||:id||''}'');
                       end;'
   ,p_method       => 'PUT'
  );
end;
/

commit
/
Probieren wir auch diesen Service aus ... erst Mal mit curl ...
D:\>curl -X GET -v http://localhost:8081/ords/documents/doc/documents/
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /ords/documents/doc/documents/ HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.44.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< ETag: "Fr7HbBdEKCjVbKL+qMgCZu5WTPRr7dRMvrsdVzUskvYWkqM/OCEVrCeqYEbwUZ7Ki4u3deYNXSxIVJhIidX5Uw=="
< Transfer-Encoding: chunked
<
{"items":[{"uri":{"$ref":"http://localhost:8081/ords/documents/doc/documents/3"},"id":3,"mimetype":"image/png","doc_size
":46098,"created":"2015-09-29T09:01:22Z","last_updated":"2015-09-29T09:01:22Z"},{"uri":{"$ref":"http://localhost:8081/or
ds/documents/doc/documents/4"},"id":4,"mimetype":"text/html","doc_size":381,"created":"2015-09-29T09:01:58Z","last_updat
ed":"2015-09-29T09:01:58Z"}],"first":{"$ref":"http://localhost:8081/ords/documents/doc/documents/"}}
* Connection #0 to host localhost left intact
Dieser einfache GET-Request kann auch im Browser getestet werden ...
Auf diese Art und Weise könnt Ihr nun noch weitere Dienste hinzufügen und so einen komplette REST-Schnittstelle für die Tabelle schaffen - in der Praxis braucht es sicherlich noch ein paar Tabellen mehr. Die REST-Dienste können dann von beliebigen Anwendungen genutzt werden; ob Node.js, PHP, Java oder .NET: Alle greifen über die einheitliche REST-Schnittstelle zu; der konkrete Datenbanktreiber wird bedeutungslos und die Tabellenzugriffe werden so einfach wie noch nie. Viel Spaß damit.

Mittwoch, 23. September 2015

Oracle Rest Data Services (ORDS) jetzt auch im SQLcl

Heute gibt es nur ein sehr kurzes Blog-Posting (wieder von Rainer Willems), was aber gut zu den bereits vorhandenen Postings zu Oracle REST Data Services passt. In den nächsten Tagen kommt dann nochmals was umfangreicheres - zu Node.js - versprochen ;-)
Mit der aktuellen Version des neuen command line Interfaces SQLcl  (Release Candidate 4.2.0.15.254.0747) gibt es nun auch eine direkte Unterstützung des ORDS und der REST Data Services. Eine kleine, aber sehr nützliche Hilfestellung.
SQL> help rest

REST ------ REST allows to export ORDS 3.X services. REST export - All modules REST export <module_name> - Export a specific module REST export <module_prefix> - Export a specific module related to a given prefix REST modules - List the available modules REST privileges - List the existing privileges REST schemas - List the avaiable schemas
So bekommt man einen schnellen Überblick über die vorhandenen Einstellungen und eine sehr schnelle Export-Möglichkeit der Module.REST modules zeigt schön die vorhandenen Module (vorher set sqlformat ansiconsole absetzten, dann sieht es auch hübsch aus).


Mit REST EXPORT <module_name> bekommt man ein ein kleines PL/SQL-Skript generiert, mit dem das genannte Modul per Kommandozeile angelegt werden kann.


Damit erhält man also auch gleich ein kleines anschauliches Beispiel zum Entwickeln von Services mit dem Package ORDS_METADATA geliefert. In den vorhergehenden Posts wurde das Package ORDS mit der Methode CREATE_SERVICE zum Anlegen solcher Services verwendet. Mit diesem kann man aber aktuell nur einen Handler pro Modul anlegen.

Dienstag, 1. September 2015

REST-Services - völlig ohne Schema - mit "SODA": So geht's.

Ich freue mich, dass heutige Blog-Posting von Rainer Willems vorstellen zu können. Rainer zeigt, wie man SODA (Simple Oracle Document Access) einrichtet und damit REST-Services in der Oracle-Datenbank - völlig ohne Schema bereitstellen kann. Jedes beliebige JSON wird entgegengenommen. Ihr könnt Rainer auf Twitter folgen: @josifabr.

Als Abwechslung zur skriptbasierten Vorgehensweise, die ich in den letzten Blog-Postings verwendet habe, seht Ihr hier die Vorgehensweise mit dem SQL Developer. Viel Spaß beim Lesen und Ausprobieren.



Heute wollen wir uns mit SODA beschäftigen. Hierbei handelt es sich nicht um eine dem aktuellen Wetter angepasste Erfrischung, sondern um Simple Oracle Document Access. Carsten hat im Blog schon gezeigt, wie einfach eine bestehende Tabelle mit ORDS REST-fähig gemacht werden kann oder wie ein solcher Service mit PL/SQL aufgebaut werden kann.

Dies erfüllt aber noch nicht die Anforderungen von Entwicklern, vollständig flexibel - also ohne vorgegebenes Schema - arbeiten so können. NoSQL-Datenbanken preisen dies als Vorteil an. Auch Oracle wartet hier mit einer NoSQL-Datenbank auf. Habe ich aber ohnehin Oracle 12c im Einsatz, wäre es doch viel einfacher, diese eine (unter vielen, vielen anderen) Anforderung mit der gleichen Datenbank abzudecken. Und genau hier kommt SODA ins Spiel. SODA bietet die Möglichkeit die Datenbank als flexiblen schemaless Document-Store zu verwenden: Oracle DB als NoSQL JSON document store

Wir betrachten betrachten im Folgenden nun SODA for REST, ebenfalls verfügbar ist SODA for Java.

Voraussetzung auf Datenbankseite ist das Release 12.1.0.2 und der Patch 20885778 Ist dieser installiert und der ORDS wie beschrieben installiert, kann es losgehen; oder besser gesagt, sind wir eigentlich schon fertig.

Der Vollständigkeit halber hier die Beschreibung der Installation des ORDS mit dem SQL Developer. Unter Tools-REST data Services findet man den Punkt Install

Wir wählen entweder den in der aktuellen SQL Developer Version mitkommenden ORDS oder einen anderen, ggf. aktuelleren (Im momentan aktuellen SQL Developer 4.1.0.19 befindet sich auch der momentan aktuellste ORDS). Dazu wählen wir eine Lokation für die Konfigurationsfiles:


Wir verbinden uns mit der Datenbank und legen einen Public User an, welcher vergleicbar mit dem APEX_PUBLIC_USER ist, nur eben für die REST-Services.


Dies muss als sys geschehen, da in der Datenbank das Metadaten-Repository für die Services angelegt wird (Schema ORDS_METADATA)


Wir legen die Tablespaces fest.


und ignorieren in den kommenden beiden Steps die Einstellungen zum PL/SQL Gateway bzw. APEX.



Wir möchten die Services zum Testen Standalone laufen lassen und definieren den HTTP Port.



Achtung. Wer hier in Konflikt mit einem bereits verwendeten Port kommt und die Installation dann ein weiteres mal versuchen möchte, muss erst die Konfigurationsdatei im Filesystem löschen, da sonst weiterhin die beim ersten Versuch angegebene Port-Nummer verwendet wird.

Für Entwicklungszwecke definieren wir noch einen Admin- und einen Entwicklungsuser (keine Datenbankuser).




Ist der ORDS dann gestartet, sollten wir auch die dementsprechende Anzeige im Log bekommen.



Läuft der ORDS dann, erkennt man das auch an einem roten Quadrat in der Menuleiste des SQL Developer. Clickt man dieses an, wird der ORDS beendet ...


... und die Installation kann unter Tools - REST Data Services - Run wieder gestartet werden (wobei man wieder ein paar Fragen beantworten muss)





Jetzt braucht man nur noch das gewünschte Datenbankschema (das was nachher herauskommt ist trotzdem "schema-less"!) "REST-fähig" machen. Das kann man einfach über das Kontextmenu in den Verbindungen machen.


Der Schemaalias wird nachher in den URLs der REST Services verwendet.



Letztendlich wird hier die Prozedur ORDS.ENABLE_SCHEMA ausgeführt:
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN

    ORDS.ENABLE_SCHEMA(p_enabled => TRUE,
                       p_schema => 'SCOTT',
                       p_url_mapping_type => 'BASE_PATH',
                       p_url_mapping_pattern => 'scott',
                       p_auto_rest_auth => FALSE);
    
    commit;
END;

FERTIG !!!! Die Datenbank kann nun via REST als JSON Document Store verwendet werden. Zum Testen deaktivieren wir nur noch die Security (als User scott in diesem Fall hier). Aber bitte nur zum Testen:
begin
     ords.delete_privilege_mapping('oracle.soda.privilege.developer','/soda/*');
end;
 /

Ich verwende übrigens gerne den Postman, eine Extension für den Chrome-Browser, um REST-Zugriffe zu testen.

Mittels eines GET auf http://mymachine:port/myords/myschema/soda/latest/ kann man sich nun anschauen, welche Collections in unserem Schema vorhanden sind:



Es sind natürlich noch keine da. Eine neue Collection läßt sich mittels eines PUT unter Angabe des gewünschten Collection-Namens erzeugen



Diese erscheint direkt als Datenbank-Tabelle im dementsprechenden Schema.


Neben der ID und dem JSON Dokument als solchem werden noch die Version (von SODA), das Erstelldatum und die letzte Modifikation in der Tabelle gepflegt.



Frage ich nun nochmal alle Collections ab, erhalte ich meine gerade angelegte Collection:
{
  "items": [
    {
      "name": "mycollection",
      "properties": {
        "schemaName": "SCOTT",
        "tableName": "mycollection",
        "keyColumn": {
          "name": "ID",
          "sqlType": "VARCHAR2",
          "maxLength": 255,
          "assignmentMethod": "UUID"
        },
        "contentColumn": {
          "name": "JSON_DOCUMENT",
          "sqlType": "BLOB",
          "compress": "NONE",
          "cache": true,
          "encrypt": "NONE",
          "validation": "STANDARD"
        },
        "versionColumn": {
          "name": "VERSION",
          "type": "String",
          "method": "SHA256"
        },
        "lastModifiedColumn": {
          "name": "LAST_MODIFIED"
        },
        "creationTimeColumn": {
          "name": "CREATED_ON"
        },
        "readOnly": false
      },
      "links": [
        {
          "rel": "canonical",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection"
        }
      ]
    }
  ],
  "more": false
}

Die Dokumente werden hier letztendlich in einem BLOB abgelegt. Dieses BLOB wird mittels eines Check-Constraints mit der Bedingung
      "JSON_DOCUMENT" is json format json
überwacht, damit dort auch nur JSON Dokumente abgelegt werden können. Spätestens hier wird klar, weshalb wir für SODA 12.1.0.2 benötigen, da die JSON-Funktionalität hier erst eingeführt wurde.

Mit einem einfachen POST kann man nun ein JSON Dokument in die Collection schieben ...


... und bekommt in der Antwort auch die ID zurückgeliefert, die dieses Dokument bekommen hat.


Fragt man nun die Collection ab (also ein GET auf http://localhost:8080/ords/scott/soda/latest/mycollection), bekommt man eine Aufstellung der Daten:
{
  "items": [
    {
      "id": "CBD1D6D35A66419390F4DB73F0B842E7",
      "etag": "5B4B2052A0927083D1C835CAD4DC59BA4AA6D6C8F99264775EEABE9E6F9BCE49",
      "lastModified": "2015-07-01T15:58:37.925000Z",
      "created": "2015-07-01T15:58:37.925000Z",
      "links": [
        {
          "rel": "self",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection/CBD1D6D35A66419390F4DB73F0B842E7"
        }
      ],
      "value": {
        "first": "Guenther",
        "last": "Stuerner",
        "address": {
          "Street": "Liebknechtstrasse",
          "City": "Stuttgart",
          "PLZ": "70565"
        }
      }
    },
    {
      "id": "E4D885A4B3F04FBC8457482E1DA0C01E",
      "etag": "511BAD96CC7C4037E8B20E5E65EBC29E7A034A273684AC2EA9029157C4EF1812",
      "lastModified": "2015-07-01T15:58:20.779000Z",
      "created": "2015-07-01T15:58:20.779000Z",
      "links": [
        {
          "rel": "self",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection/E4D885A4B3F04FBC8457482E1DA0C01E"
        }
      ],
      "value": {
        "first": "Rainer",
        "last": "Willems",
        "address": {
          "Street": "Robert-Bosch-Strasse",
          "City": "Dreieich",
          "PLZ": "63303"
        }
      }
    },
    {
      "id": "27200D52F15C4A8F82868C353112E350",
      "etag": "51948BD2C13AE6A0962304FCC325743D1A5F976D687D0A03BFB732D2436CF316",
      "lastModified": "2015-07-01T15:58:29.077000Z",
      "created": "2015-07-01T15:58:29.077000Z",
      "links": [
        {
          "rel": "self",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection/27200D52F15C4A8F82868C353112E350"
        }
      ],
      "value": {
        "first": "Paul",
        "last": "Wehner",
        "address": {
          "Street": "Robert-Bosch-Strasse",
          "City": "Dreieich",
          "PLZ": "63303"
        }
      }
    }
  ],
  "hasMore": false,
  "count": 3,
  "offset": 0,
  "limit": 100,
  "totalResults": 3,
  "links": []
}
Hierbei lassen sich natürlich Parameter mitgeben, um die Ausgabe zu Filtern (darüber werden wir noch berichten). Für die Abfrage eines einzelnen JSON-Dokumentes hänge ich einfach die ID an das GET an.



Folgende Befehle sind nun also möglich
PUT    - http://localhost:8080/ords/scott/soda/latest/mycollection     Anlegen einer Collection
GET    - http://localhost:8080/ords/scott/soda/latest/                 Anzeige aller Collections
GET    - http://localhost:8080/ords/scott/soda/latest/mycollection     Anzeige des Inhaltes einer Collection
GET    - http://localhost:8080/ords/scott/soda/latest/mycollection/id  Anzeige eines JSON-Dokumentes
POST   - http://localhost:8080/ords/scott/soda/latest/mycollection     Übergabe und Anlage eines Dokumentes
DELETE - http://localhost:8080/ords/scott/soda/latest/mycollection/id  Löschen eines Dokumentes
  ... besser nicht verwechseln mit
DELETE - http://localhost:8080/ords/scott/soda/latest/mycollection     ... denn dann ist die Collection weg

Weitere Möglichkeiten wie Bulk-Insert und Delete sowie verfügbare Actions sind in der Dokumentation ausführlich beschrieben.
Der riesige Vorteil, den wir nun erhalten, findet sich in der Datenbank. Da diese per SQL mit JSON-Dokumenten umgehen kann, sind der Suche in diesen Dokumenten oder der Verknüpfung dieser mit relationalen Inhalten keine Grenzen gesetzt. Und das mit ein paar einfachen Schritten. Wer will da noch eine zusätzliche Datenbank installieren, verwalten, sichern, ....

Noch ein kleiner Hinweis, falls die Restservices wieder entfernt werden sollen. Da die Metaobjekte aus der Datenbank wieder entfernt werden muss man sich mit SYSDBA Privilegien anmelden. Der Zusatz "as sysdba" wird aber aktuell (SQL Developer 4.1.0.19) im Gegensatz zur Installation vom Tool nicht automatisch angehängt, sondern muss manuel eingetragen werden.