Zum Inhalt springen

OBS/Kostenpflichtige Module/RESTServer/Beispiel4: Unterschied zwischen den Versionen

Aus OBS Wiki
Rademacker (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
Rademacker (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
 
Zeile 16: Zeile 16:


Die Platzhalter <code>{uid}</code> und <code>{code}</code> stehen im Skript als
Die Platzhalter <code>{uid}</code> und <code>{code}</code> stehen im Skript als
<code>oParams.Values['_OBS_PATH_uid']</code> bzw.
<code>oReader.Path('uid')</code> bzw. <code>oReader.Path('code')</code> zur
<code>oParams.Values['_OBS_PATH_code']</code> zur Verfügung.
Verfügung.


== Einrichtung in OBS ==
== Einrichtung in OBS ==
Zeile 38: Zeile 38:


<syntaxhighlight lang="pascal">
<syntaxhighlight lang="pascal">
// Hilfsfunktion: ein Feld aus dem JSON-Body als String lesen.
// Hilfsfunktion: die FELDER eines Auftrags. Der Helfer bekommt den Writer und
// Generics gibt es im Skript nicht - oBody.TryGetValue<string>(...) laesst sich
// schreibt in das Objekt, das der Aufrufer schon geoeffnet hat - so laesst sich
// nicht uebersetzen. GetValue liefert nil, wenn das Feld fehlt.
// derselbe Auftrag einmal als Listeneintrag und einmal als ganze Antwort
function _BodyStr(oBody: TJSONObject; const cFeld: string): string;
// ausgeben, ohne den Code zu verdoppeln.
var oVal: TJSONValue;
procedure _OrderFelder(oWriter: TxRestWriter; qOrder: TqSQL;
                      const cOffset: string);
begin
begin
     result := '';
     oWriter.Str ('uid'   , qOrder.A2UID());
    if (oBody = nil) then begin
     oWriter.Str ('nr'   , qOrder.A2C('au_nr'));
        exit;
     oWriter.Str ('kunde' , qOrder.A2C('au_kunde'));
    end;
     oWriter.Str ('status', qOrder.A2C('au_status'));
    oVal := oBody.GetValue(cFeld);
     // ISO-8601 mit Offset - siehe den Hinweis unter der Liste.
    if (Assigned(oVal)) then begin
    oWriter.StrN('aenderung', RestIsoFromDate(qOrder.A2D('au_aend_dat'), cOffset));
        result := oVal.Value;
    end;
end;
 
// Hilfsfunktion: ein Auftrag als JSON-Objekt
function _OrderAsJSON(qOrder: TqSQL): TJSONObject;
begin
    result := TJSONObject.Create();
    result.AddPair('uid'     , qOrder.A2UID());
     result.AddPair('nr'       , qOrder.A2C('au_nr'));
     result.AddPair('kunde'   , qOrder.A2C('au_kunde'));
     result.AddPair('status'   , qOrder.A2C('au_status'));
     result.AddPair('aenderung', DTToSQL(qOrder.A2D('au_aend_dat')));
end;
end;


function Get(oParams: TStrings; oBody: TJSONObject): string;
procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
var cSql : string;
var cSql   : string;
     qData: TqSQL;
     qData : TqSQL;
     oArr : TJSONArray;
     cOffset: string;
begin
begin
     oArr := TJSONArray.Create();
     cOffset := oReader.Offset();
    // Die Antwort ist eine nackte Liste - RootArr vor dem ersten Eintrag.
    // Findet die Abfrage nichts, ist es eine leere Liste und kein leeres
    // Objekt: deshalb steht der Aufruf VOR der Abfrage.
    oWriter.RootArr();


     cSql := 'SELECT * FROM auftraege' +
     cSql := 'SELECT * FROM auftraege' +
Zeile 77: Zeile 69:
     if (DB_SOpen(oDB, cSql, qData)) then begin
     if (DB_SOpen(oDB, cSql, qData)) then begin
         while (not qData.EoF) do begin
         while (not qData.EoF) do begin
             oArr.Add(_OrderAsJSON(qData));
             oWriter.ObjBegin('');
                _OrderFelder(oWriter, qData, cOffset);
            oWriter.ObjEnd;
             qData.Next();
             qData.Next();
         end;
         end;
     end;
     end;
 
     DB_Close(qData);
     result := oArr.ToJSON();
end;
end;
</syntaxhighlight>
</syntaxhighlight>
Zeile 89: Zeile 82:


<syntaxhighlight lang="pascal">
<syntaxhighlight lang="pascal">
//------------------------------------------------------------------------------
// Derselbe Helfer wie im Listen-Skript - hier schreibt er in die WURZEL, weil
// Hilfsfunktion: Fehlerantwort im einheitlichen Format aufbauen.
// der Auftrag selbst die Antwort ist. Eine eigene Fehlerfunktion braucht es
// Fachliche Ablehnungen als 4xx melden, nicht als 200 mit Fehlertext.
// nicht mehr: oWriter.Error setzt Statuscode, code, message, uid und traceId.
//------------------------------------------------------------------------------
// Derselbe Helfer wie im Listen-Skript - hier schreibt er in die WURZEL,
 
// weil der Auftrag selbst die Antwort ist.
function _Fehler(oParams: TStrings; nStatus: integer; const cCode, cMsg: string): string;
procedure _OrderFelder(oWriter: TxRestWriter; qOrder: TqSQL;
var oRes: TJSONObject;
                      const cOffset: string);
    oErr: TJSONObject;
begin
begin
     oErr := TJSONObject.Create();
     oWriter.Str ('uid'  , qOrder.A2UID());
     oErr.AddPair('code'   , cCode);
     oWriter.Str ('nr'   , qOrder.A2C('au_nr'));
     oErr.AddPair('message', cMsg);
     oWriter.Str ('kunde' , qOrder.A2C('au_kunde'));
     oErr.AddPair('traceId', oParams.Values['_OBS_TRACE_ID']);
     oWriter.Str ('status', qOrder.A2C('au_status'));
 
     oWriter.StrN('aenderung', RestIsoFromDate(qOrder.A2D('au_aend_dat'), cOffset));
    oRes := TJSONObject.Create();
     try
        oRes.AddPair('_OBS_HTTP_STATUS', nStatus);
        oRes.AddPair('error', oErr);
        result := oRes.ToJSON();
    finally
        MyFreeAndNil(oRes);
    end;
end;
end;


function _OrderAsJSON(qOrder: TqSQL): TJSONObject;
procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
begin
var cUid     : string;
    result := TJSONObject.Create();
     cSql     : string;
     result.AddPair('uid'      , qOrder.A2UID());
    qData    : TqSQL;
     result.AddPair('nr'      , qOrder.A2C('au_nr'));
     lGefunden: Boolean;
     result.AddPair('kunde'    , qOrder.A2C('au_kunde'));
     cOffset  : string;
    result.AddPair('status'  , qOrder.A2C('au_status'));
    result.AddPair('aenderung', DTToSQL(qOrder.A2D('au_aend_dat')));
end;
 
function Get(oParams: TStrings; oBody: TJSONObject): string;
var cUid : string;
     cSql : string;
     qData: TqSQL;
begin
begin
     // Pfad-Parameter aus /orders/{uid}
     // Pfad-Parameter aus /orders/{uid}
     cUid := oParams.Values['_OBS_PATH_uid'];
     cUid   := oReader.Path('uid');
    cOffset := oReader.Offset();
     if (cUid = '') then begin
     if (cUid = '') then begin
         result := _Fehler(oParams, 422, 'VALIDATION_FAILED', 'Pfad-Parameter "uid" fehlt');
         oWriter.Error(422, 'VALIDATION_FAILED', 'Pfad-Parameter "uid" fehlt');
         exit;
         exit;
     end;
     end;


    lGefunden := false;
     cSql := 'SELECT * FROM auftraege WHERE sys_uid = ' + DB_SQLVal(cUid);
     cSql := 'SELECT * FROM auftraege WHERE sys_uid = ' + DB_SQLVal(cUid);
     if (DB_SOpen(oDB, cSql, qData)) and (not qData.EoF) then begin
     if (DB_SOpen(oDB, cSql, qData)) then begin
        result := _OrderAsJSON(qData).ToJSON();
        if (not qData.EoF) then begin
     end else begin
            _OrderFelder(oWriter, qData, cOffset);
         result := _Fehler(oParams, 404, 'NOT_FOUND', 'Auftrag nicht gefunden');
            lGefunden := true;
        end;
    end;
    DB_Close(qData);
 
     if (not lGefunden) then begin
         oWriter.Error(404, 'NOT_FOUND', 'Auftrag nicht gefunden');
     end;
     end;
end;
end;
Zeile 147: Zeile 131:


<syntaxhighlight lang="pascal">
<syntaxhighlight lang="pascal">
//------------------------------------------------------------------------------
procedure Put(oReader: TxRestReader; oWriter: TxRestWriter);
// Hilfsfunktion: Fehlerantwort im einheitlichen Format aufbauen.
// Fachliche Ablehnungen als 4xx melden, nicht als 200 mit Fehlertext.
//------------------------------------------------------------------------------
 
function _Fehler(oParams: TStrings; nStatus: integer; const cCode, cMsg: string): string;
var oRes: TJSONObject;
    oErr: TJSONObject;
begin
    oErr := TJSONObject.Create();
    oErr.AddPair('code'  , cCode);
    oErr.AddPair('message', cMsg);
    oErr.AddPair('traceId', oParams.Values['_OBS_TRACE_ID']);
 
    oRes := TJSONObject.Create();
    try
        oRes.AddPair('_OBS_HTTP_STATUS', nStatus);
        oRes.AddPair('error', oErr);
        result := oRes.ToJSON();
    finally
        MyFreeAndNil(oRes);
    end;
end;
 
function Put(oParams: TStrings; oBody: TJSONObject): string;
var cUid  : string;
var cUid  : string;
     cCode  : string;
     cCode  : string;
Zeile 182: Zeile 142:
begin
begin
     // Beide Pfad-Parameter
     // Beide Pfad-Parameter
     cUid  := oParams.Values['_OBS_PATH_uid'];
     cUid  := oReader.Path('uid');
     cCode := oParams.Values['_OBS_PATH_code'];
     cCode := oReader.Path('code');
     if (cUid = '') or (cCode = '') then begin
     if (cUid = '') or (cCode = '') then begin
         result := _Fehler(oParams, 422, 'VALIDATION_FAILED', 'Pfad-Parameter "uid" oder "code" fehlt');
         oWriter.Error(422, 'VALIDATION_FAILED', 'Pfad-Parameter "uid" oder "code" fehlt');
         exit;
         exit;
     end;
     end;


     // Nutzdaten kommen aus dem JSON-Body
     // Nutzdaten kommen aus dem JSON-Body. Der Rueckgabewert sagt, ob das Feld
     cStatus := _BodyStr(oBody, 'status');
    // ueberhaupt gesendet wurde - eine eigene Hilfsfunktion dafuer braucht es
    // nicht mehr.
     cStatus := '';
    oReader.Str('status', cStatus);
     if (cStatus = '') then begin
     if (cStatus = '') then begin
         result := _Fehler(oParams, 422, 'VALIDATION_FAILED', 'Feld "status" fehlt');
         oWriter.Error(422, 'VALIDATION_FAILED', 'Feld "status" fehlt');
         exit;
         exit;
     end;
     end;
Zeile 210: Zeile 173:


     if (cModUid = '') then begin
     if (cModUid = '') then begin
         result := _Fehler(oParams, 404, 'NOT_FOUND', 'Modul nicht gefunden');
         oWriter.Error(404, 'NOT_FOUND', 'Modul nicht gefunden');
         exit;
         exit;
     end;
     end;
Zeile 226: Zeile 189:


     if (not lOk) then begin
     if (not lOk) then begin
         result := '{"error":{"code":"INTERNAL_ERROR","message":"Status konnte nicht gespeichert werden"},"_OBS_HTTP_STATUS":500}';
         oWriter.Error(500, 'INTERNAL_ERROR', 'Status konnte nicht gespeichert werden');
         exit;
         exit;
     end;
     end;


     result := '{"status":"ok"}';
     oWriter.Str('status', 'ok');
end;
end;
</syntaxhighlight>
</syntaxhighlight>
Zeile 254: Zeile 217:


* '''Eine Ressource, mehrere Pfad-Formen''' über getrennte Endpunkt-Einträge mit eigenem Skript und eigener Berechtigung.
* '''Eine Ressource, mehrere Pfad-Formen''' über getrennte Endpunkt-Einträge mit eigenem Skript und eigener Berechtigung.
* '''Pfad-Parameter''' werden über <code>{name}</code> im Template erfasst und im Skript als <code>oParams.Values['_OBS_PATH_name']</code> gelesen.
* '''Pfad-Parameter''' werden über <code>{name}</code> im Template erfasst und im Skript mit <code>oReader.Path('name')</code> gelesen.
* Reihenfolge der Skript-Parameter: '''<code>oParams</code> zuerst, <code>oBody</code> danach''' (umgekehrt zur internen Dispatch-Reihenfolge).
* Reihenfolge der Skript-Parameter: '''<code>oReader</code> zuerst, <code>oWriter</code> danach'''.
* '''Ein Helfer bekommt den Writer''' und schreibt an der Stelle hinein, an der er gerufen wird. Derselbe <code>_OrderFelder</code> bedient den Listeneintrag und die Einzelantwort - einmal in einem <code>ObjBegin('')</code>, einmal in der Wurzel.
* Präzedenz: ein statisches Segment (z.&nbsp;B. <code>/orders/summary</code>) hätte Vorrang vor <code>/orders/{uid}</code>.
* Präzedenz: ein statisches Segment (z.&nbsp;B. <code>/orders/summary</code>) hätte Vorrang vor <code>/orders/{uid}</code>.



Aktuelle Version vom 9. September 2026, 13:31 Uhr

Kostenpflichtige Module

Internet-Shop
UPS
IMS Professional
SMS
Mehrlager-Verwaltung
Mehrsprachen Modul
Multilanguage Modul
EVA Marketing Tool
Termin-Projekte
Edifact-Schnittstelle
Backup Überwachung Email
OBS Geo Daten
DeliSprint / DPD
Filialen
Cashback
Moebelschnittstelle
Dokumenten Manager
DocuWare-Schnittstelle
OFML-Kalkulation
Versicherungsschaden
Gutschriftsanzeigen
Kameraverwaltung
DataInOut
OpenMasterData / IDS
Sammelpositionen



Beispiel 4 – Pfad-Parameter (REST-Routing über Templates)

Dieses Beispiel zeigt das Pfad-Template-Routing mit Platzhaltern. Eine Auftrags-Ressource wird über drei Endpunkte abgebildet:

Methode + Pfad Zweck
GET /orders Liste aller offenen Aufträge
GET /orders/{uid} Einzelner Auftrag per UID
PUT /orders/{uid}/modules/{code} Status eines Auftrags-Moduls ändern

Die Platzhalter {uid} und {code} stehen im Skript als oReader.Path('uid') bzw. oReader.Path('code') zur Verfügung.

Einrichtung in OBS

  • Server-Profil: Standard-TLS-Profil Public-API auf 0.0.0.0:443.
  • Zugang: Orders-Client mit API-Key, Zugriff auf die drei Endpunkte.
  • Endpunkte (je ein Eintrag in RESTSRV_ENDPOINTS, alle Profil Public-API):
Endpunkt Pfad-Template Skript-Methode
orders /orders Get (Liste)
orders /orders/{uid} Get (Einzel)
orders /orders/{uid}/modules/{code} Put

Endpunkt-Skript 1: GET /orders (Liste)

// Hilfsfunktion: die FELDER eines Auftrags. Der Helfer bekommt den Writer und
// schreibt in das Objekt, das der Aufrufer schon geoeffnet hat - so laesst sich
// derselbe Auftrag einmal als Listeneintrag und einmal als ganze Antwort
// ausgeben, ohne den Code zu verdoppeln.
procedure _OrderFelder(oWriter: TxRestWriter; qOrder: TqSQL;
                       const cOffset: string);
begin
    oWriter.Str ('uid'   , qOrder.A2UID());
    oWriter.Str ('nr'    , qOrder.A2C('au_nr'));
    oWriter.Str ('kunde' , qOrder.A2C('au_kunde'));
    oWriter.Str ('status', qOrder.A2C('au_status'));
    // ISO-8601 mit Offset - siehe den Hinweis unter der Liste.
    oWriter.StrN('aenderung', RestIsoFromDate(qOrder.A2D('au_aend_dat'), cOffset));
end;

procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
var cSql   : string;
    qData  : TqSQL;
    cOffset: string;
begin
    cOffset := oReader.Offset();
    // Die Antwort ist eine nackte Liste - RootArr vor dem ersten Eintrag.
    // Findet die Abfrage nichts, ist es eine leere Liste und kein leeres
    // Objekt: deshalb steht der Aufruf VOR der Abfrage.
    oWriter.RootArr();

    cSql := 'SELECT * FROM auftraege' +
            ' WHERE au_status <> ' + DB_SQLVal('9') +
            ' ORDER BY au_nr';
    if (DB_SOpen(oDB, cSql, qData)) then begin
        while (not qData.EoF) do begin
            oWriter.ObjBegin('');
                _OrderFelder(oWriter, qData, cOffset);
            oWriter.ObjEnd;
            qData.Next();
        end;
    end;
    DB_Close(qData);
end;

Endpunkt-Skript 2: GET /orders/{uid} (Einzel)

// Derselbe Helfer wie im Listen-Skript - hier schreibt er in die WURZEL, weil
// der Auftrag selbst die Antwort ist. Eine eigene Fehlerfunktion braucht es
// nicht mehr: oWriter.Error setzt Statuscode, code, message, uid und traceId.
// Derselbe Helfer wie im Listen-Skript - hier schreibt er in die WURZEL,
// weil der Auftrag selbst die Antwort ist.
procedure _OrderFelder(oWriter: TxRestWriter; qOrder: TqSQL;
                       const cOffset: string);
begin
    oWriter.Str ('uid'   , qOrder.A2UID());
    oWriter.Str ('nr'    , qOrder.A2C('au_nr'));
    oWriter.Str ('kunde' , qOrder.A2C('au_kunde'));
    oWriter.Str ('status', qOrder.A2C('au_status'));
    oWriter.StrN('aenderung', RestIsoFromDate(qOrder.A2D('au_aend_dat'), cOffset));
end;

procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
var cUid     : string;
    cSql     : string;
    qData    : TqSQL;
    lGefunden: Boolean;
    cOffset  : string;
begin
    // Pfad-Parameter aus /orders/{uid}
    cUid    := oReader.Path('uid');
    cOffset := oReader.Offset();
    if (cUid = '') then begin
        oWriter.Error(422, 'VALIDATION_FAILED', 'Pfad-Parameter "uid" fehlt');
        exit;
    end;

    lGefunden := false;
    cSql := 'SELECT * FROM auftraege WHERE sys_uid = ' + DB_SQLVal(cUid);
    if (DB_SOpen(oDB, cSql, qData)) then begin
        if (not qData.EoF) then begin
            _OrderFelder(oWriter, qData, cOffset);
            lGefunden := true;
        end;
    end;
    DB_Close(qData);

    if (not lGefunden) then begin
        oWriter.Error(404, 'NOT_FOUND', 'Auftrag nicht gefunden');
    end;
end;

Endpunkt-Skript 3: PUT /orders/{uid}/modules/{code}

procedure Put(oReader: TxRestReader; oWriter: TxRestWriter);
var cUid   : string;
    cCode  : string;
    cStatus: string;
    cSql   : string;
    qChk   : TqSQL;
    xMod   : TqSQL;
    cModUid: string;
    lOk    : Boolean;
begin
    // Beide Pfad-Parameter
    cUid  := oReader.Path('uid');
    cCode := oReader.Path('code');
    if (cUid = '') or (cCode = '') then begin
        oWriter.Error(422, 'VALIDATION_FAILED', 'Pfad-Parameter "uid" oder "code" fehlt');
        exit;
    end;

    // Nutzdaten kommen aus dem JSON-Body. Der Rueckgabewert sagt, ob das Feld
    // ueberhaupt gesendet wurde - eine eigene Hilfsfunktion dafuer braucht es
    // nicht mehr.
    cStatus := '';
    oReader.Str('status', cStatus);
    if (cStatus = '') then begin
        oWriter.Error(422, 'VALIDATION_FAILED', 'Feld "status" fehlt');
        exit;
    end;

    // Existenzpruefung - und zugleich der Schreib-Index. Ein SELECT auf die
    // sys_uid liefert beides; ein 'SELECT *' waere hier verschenkte Arbeit.
    cSql := 'SELECT sys_uid FROM auftrag_module' +
            ' WHERE am_auftrag = ' + DB_SQLVal(cUid) +
            ' AND am_code = ' + DB_SQLVal(cCode) + ' LIMIT 1';
    cModUid := '';
    if (DB_SOpen(oDB, cSql, qChk)) then begin
        if (not qChk.EoF) then begin
            cModUid := qChk.A2UID();
        end;
    end;
    DB_Close(qChk);

    if (cModUid = '') then begin
        oWriter.Error(404, 'NOT_FOUND', 'Modul nicht gefunden');
        exit;
    end;

    // Update
    // Der Satz wird ueber seine sys_uid geschrieben - qSqlRead waere hier
    // ein zusaetzlicher Lesezugriff, und ein unveraenderter Wert wuerde gar
    // kein Write erzeugen (SaveData liefert dann False).
    xMod := qSqlInit(oDB, 'auftrag_module');
    xMod.qSet('sys_uid'  , cModUid);
    xMod.qSet('am_status', cStatus);
    // Rueckgabewert auswerten - ein Schreibfehler wirft keine Exception
    lOk := xMod.SaveData(UPDATE_RECORD);
    qSqlFree(xMod);

    if (not lOk) then begin
        oWriter.Error(500, 'INTERNAL_ERROR', 'Status konnte nicht gespeichert werden');
        exit;
    end;

    oWriter.Str('status', 'ok');
end;

Test mit curl

# Liste
curl -H "apikey: GEHEIM123" https://api.meinserver.de/orders

# Einzelner Auftrag (uid als Pfad-Parameter)
curl -H "apikey: GEHEIM123" https://api.meinserver.de/orders/4711

# Modul-Status aendern (uid + code als Pfad-Parameter, status im Body)
curl -X PUT \
     -H "apikey: GEHEIM123" \
     -H "Content-Type: application/json" \
     -d '{"status":"21"}' \
     https://api.meinserver.de/orders/4711/modules/A1

Was dieses Beispiel zeigt

  • Eine Ressource, mehrere Pfad-Formen über getrennte Endpunkt-Einträge mit eigenem Skript und eigener Berechtigung.
  • Pfad-Parameter werden über {name} im Template erfasst und im Skript mit oReader.Path('name') gelesen.
  • Reihenfolge der Skript-Parameter: oReader zuerst, oWriter danach.
  • Ein Helfer bekommt den Writer und schreibt an der Stelle hinein, an der er gerufen wird. Derselbe _OrderFelder bedient den Listeneintrag und die Einzelantwort - einmal in einem ObjBegin(), einmal in der Wurzel.
  • Präzedenz: ein statisches Segment (z. B. /orders/summary) hätte Vorrang vor /orders/{uid}.

Siehe auch