OBS/Kostenpflichtige Module/RESTServer/Scripting: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
| Zeile 75: | Zeile 75: | ||
|- | |- | ||
| _OBS_JWT_AUDIENCE || Audience (''aud''-Claim) - typisch Mandant / Rolle | | _OBS_JWT_AUDIENCE || Audience (''aud''-Claim) - typisch Mandant / Rolle | ||
|- | |||
| _OBS_JWT_CLAIM_<name> || Beliebiger Custom-Claim des Tokens (z.B. _OBS_JWT_CLAIM_tenant, _OBS_JWT_CLAIM_roles); im Folge-Skript lesbar | |||
|- | |||
| _OBS_TRACE_ID || Korrelations-ID des Requests (auch als Response-Header X-Trace-Id) | |||
|} | |} | ||
| Zeile 107: | Zeile 111: | ||
oRes.AddPair('wert_string', '123'); | oRes.AddPair('wert_string', '123'); | ||
oRes.AddPair('wert_int' , 456); | oRes.AddPair('wert_int' , 456); | ||
result := oRes.ToJSON(); | |||
finally | |||
MyFreeAndNil(oRes); | |||
end; | |||
end; | |||
</syntaxhighlight> | |||
==Antwort steuern: Statuscode, Header, traceId== | |||
Ein Skript kann den HTTP-Statuscode und beliebige Response-Header Über reservierte Felder in der Antwort setzen. Der Server wertet sie aus und entfernt sie vor dem Senden aus dem Body. | |||
{| class="wikitable" | |||
! Antwort-Feld !! Wirkung | |||
|- | |||
| _OBS_HTTP_STATUS (Zahl) || HTTP-Statuscode (z.B. 201, 204, 400, 409, 422). Ohne Angabe: 200. Bei 204 wird kein Body gesendet. | |||
|- | |||
| _OBS_HEADERS (Objekt) || Beliebige Response-Header, z.B. ETag, Location, Retry-After. CR/LF in Namen/Werten werden entfernt (Schutz vor Header-Injection). | |||
|} | |||
Zusätzlich trÄgt jede Antwort den Header '''X-Trace-Id''' (Korrelations-ID). Dieselbe ID liegt dem Skript als ''oParams.Values['_OBS_TRACE_ID']'' vor und erscheint in jeder Server-Fehler-Logzeile in '''RESTSRV_PROTO''' - so lÄsst sich ein Fehler ohne GerÄtezugriff im Log wiederfinden. | |||
===Beispiel: Anlegen mit 201, Location und ETag=== | |||
<syntaxhighlight lang="pascal" line> | |||
function Post(oParams: TStrings; oBody: TJSONObject): string; | |||
var oRes, oHdr: TJSONObject; | |||
begin | |||
oRes := TJSONObject.Create(); | |||
try | |||
// ... Datensatz anlegen, neue UID + Version (ETag) ermitteln ... | |||
oHdr := TJSONObject.Create(); | |||
oHdr.AddPair('Location', '/v1/orders/' + cNeueUid); | |||
oHdr.AddPair('ETag', '1'); | |||
oRes.AddPair('_OBS_HTTP_STATUS', 201); | |||
oRes.AddPair('_OBS_HEADERS', oHdr); | |||
oRes.AddPair('uid', cNeueUid); | |||
result := oRes.ToJSON(); | |||
finally | |||
MyFreeAndNil(oRes); | |||
end; | |||
end; | |||
</syntaxhighlight> | |||
===Beispiel: Strukturierter Fehler mit Statuscode und traceId=== | |||
<syntaxhighlight lang="pascal" line> | |||
function Post(oParams: TStrings; oBody: TJSONObject): string; | |||
var oRes, oErr: TJSONObject; | |||
begin | |||
oRes := TJSONObject.Create(); | |||
try | |||
oErr := TJSONObject.Create(); | |||
oErr.AddPair('code', 'VALIDATION_FAILED'); | |||
oErr.AddPair('message', 'Feld "menge" fehlt'); | |||
oErr.AddPair('traceId', oParams.Values['_OBS_TRACE_ID']); | |||
oRes.AddPair('_OBS_HTTP_STATUS', 422); | |||
oRes.AddPair('error', oErr); | |||
result := oRes.ToJSON(); | |||
finally | |||
MyFreeAndNil(oRes); | |||
end; | |||
end; | |||
</syntaxhighlight> | |||
===Beispiel: Optimistic Concurrency (ETag / If-Match)=== | |||
Mit Statuscode, Headern und dem lesbaren Header ''If-Match'' fÜhrt das Skript je Datensatz einen VersionszÄhler und lehnt veraltete Schreibzugriffe mit 409 ab: | |||
<syntaxhighlight lang="pascal" line> | |||
function Put(oParams: TStrings; oBody: TJSONObject): string; | |||
var oRes, oHdr: TJSONObject; | |||
nAktuell, nIfMatch: integer; | |||
begin | |||
oRes := TJSONObject.Create(); | |||
try | |||
nAktuell := AuftragVersion(oParams.Values['_OBS_PATH_uid']); | |||
nIfMatch := iVal(oParams.Values['if-match']); | |||
if (nIfMatch <> nAktuell) then begin | |||
oHdr := TJSONObject.Create(); | |||
oHdr.AddPair('ETag', xStr(nAktuell)); | |||
oRes.AddPair('_OBS_HTTP_STATUS', 409); | |||
oRes.AddPair('_OBS_HEADERS', oHdr); | |||
oRes.AddPair('error', TJSONObject.Create.AddPair('code', 'VERSION_CONFLICT')); | |||
result := oRes.ToJSON(); | |||
exit; | |||
end; | |||
// ... speichern, Version hochzÄhlen ... | |||
oHdr := TJSONObject.Create(); | |||
oHdr.AddPair('ETag', xStr(nAktuell + 1)); | |||
oRes.AddPair('_OBS_HEADERS', oHdr); | |||
result := oRes.ToJSON(); | result := oRes.ToJSON(); | ||
finally | finally | ||
| Zeile 167: | Zeile 265: | ||
| 9 || Misserfolg, der Server antwortet mit 403 und protokolliert den Wert von ''error'' | | 9 || Misserfolg, der Server antwortet mit 403 und protokolliert den Wert von ''error'' | ||
|} | |} | ||
===Custom-Claims, serverTime und Refresh=== | |||
Das Authenticate-Skript kann dem Token zusÄtzlich '''beliebige Custom-Claims''' mitgeben - z.B. Mandant und Rollen - und optional ein '''Refresh-Token''' anstoßen: | |||
{| class="wikitable" | |||
! RÜckgabefeld !! Bedeutung | |||
|- | |||
| _OBS_JWT_CLAIM_<name> || Beliebiger Custom-Claim, z.B. _OBS_JWT_CLAIM_tenant, _OBS_JWT_CLAIM_roles. Im Folge-Skript lesbar als oParams.Values['_OBS_JWT_CLAIM_<name>']. | |||
|- | |||
| _OBS_JWT_REFRESH_ID || (optional) jti des Refresh-Tokens. Nur wenn gesetzt, stellt der Server ein Refresh-Token aus. | |||
|- | |||
| _OBS_JWT_REFRESH_EXP || (optional) Lebensdauer des Refresh-Tokens in Minuten (Default 90 Tage = 129600). | |||
|} | |||
Jedes Token trÄgt automatisch den Claim ''token_use'' (''access'' bzw. ''refresh''). Die Antwort enthÄlt bei Erfolg ''serverTime'' (ISO-8601 mit Offset), bei ausgestelltem Refresh zusÄtzlich ''refreshToken'': | |||
{"token":"...", "refreshToken":"...", "serverTime":"2026-06-29T15:30:12+02:00"} | |||
{{Hinweis|Mandant und Rollen immer aus dem Token lesen (''_OBS_JWT_CLAIM_*''), nie aus Body oder Query.}} | |||
====Refresh-Methode==== | |||
Der Refresh lÄuft Über '''denselben JWT-Endpunkt''' und '''dasselbe Skript'''. Liegt am JWT-Endpunkt ein ''Authorization: Bearer <token>'' vor, ruft der Server statt ''Authenticate'' die Methode '''Refresh''' auf (sonst Login). Der Server verifiziert den Refresh-Token zuvor (Signatur, Ablauf, ''token_use=refresh''); das alte Refresh-jti steht als ''oParams.Values['_OBS_JWT_ID']'' bereit. Das Skript prÜft/rotiert seine Sperrtabelle und liefert wie ''Authenticate'' neue Claims zurÜck. Der Server stellt daraufhin ein rotiertes Token-Paar aus; ein bereits benutztes Refresh-Token wird mit 401 abgelehnt. | |||
<syntaxhighlight lang="pascal" line> | |||
function Refresh(oParams: TStrings; oBody: TJSONObject): string; | |||
var oRes: TJSONObject; | |||
cAltesJti, cNeuesJti: string; | |||
begin | |||
oRes := TJSONObject.Create(); | |||
try | |||
cAltesJti := oParams.Values['_OBS_JWT_ID']; | |||
// altes Refresh-jti gegen eigene Sperrtabelle prÜfen | |||
if (not RefreshJtiGueltig(cAltesJti)) then begin | |||
oRes.AddPair('status', 9); | |||
oRes.AddPair('error' , 'AUTH_EXPIRED'); | |||
result := oRes.ToJSON(); | |||
exit; | |||
end; | |||
cNeuesJti := GlobalUID(); | |||
RefreshJtiRotieren(cAltesJti, cNeuesJti); // altes sperren, neues ablegen | |||
oRes.AddPair('status' , 1); | |||
oRes.AddPair('_OBS_JWT_ID' , GlobalUID()); | |||
oRes.AddPair('_OBS_JWT_SUBJECT' , oParams.Values['_OBS_JWT_SUBJECT']); | |||
oRes.AddPair('_OBS_JWT_CLAIM_tenant', oParams.Values['_OBS_JWT_CLAIM_tenant']); | |||
oRes.AddPair('_OBS_JWT_CLAIM_roles' , oParams.Values['_OBS_JWT_CLAIM_roles']); | |||
oRes.AddPair('_OBS_JWT_REFRESH_ID' , cNeuesJti); | |||
result := oRes.ToJSON(); | |||
finally | |||
MyFreeAndNil(oRes); | |||
end; | |||
end; | |||
</syntaxhighlight> | |||
==Fehlerbehandlung im Skript== | ==Fehlerbehandlung im Skript== | ||
| Zeile 212: | Zeile 367: | ||
* [[OBS/Kostenpflichtige Module/RESTServer/Beispiel2|Beispiel: Datensatz anlegen mit JSON-Body]] | * [[OBS/Kostenpflichtige Module/RESTServer/Beispiel2|Beispiel: Datensatz anlegen mit JSON-Body]] | ||
* [[OBS/Kostenpflichtige Module/RESTServer/Beispiel4|Beispiel: Pfad-Parameter im Routing]] | * [[OBS/Kostenpflichtige Module/RESTServer/Beispiel4|Beispiel: Pfad-Parameter im Routing]] | ||
* [[OBS/Kostenpflichtige Module/RESTServer/Beispiel5|Beispiel: Schreibzugriff mit Statuscodes, ETag und Idempotenz]] | |||
Version vom 29. Juni 2026, 11:06 Uhr
- A Preise aktualisieren
- C Personen übertragen
- E Kategorien verwalten
- G Kataloge verwalten
- I Merkliste übertragen
- K Varianten übertragen
- L Artikelvarianten übertragen
- M Referenzarten übertragen
- N Lagerbestände verwalten
- U Bestellungen einlesen
- V leere Passworte füllen
- W Update-Informationen zurücksetzen
- X Konfiguration
- Z Protokoll
Anleitung für Endpunkt-Skripte
Jede Anfrage an einen Endpunkt wird nach erfolgreicher Authentifizierung und Autorisierung an das hinterlegte Skript weitergegeben. Das Skript ist in Object Pascal geschrieben und greift auf die OBS-Bibliothek (DB-Zugriff, Hilfsfunktionen) zu.
Sicherheitshinweise
- Eingaben gegen erwartete Werte prüfen (z.B. Pflichtfelder, erlaubte Typen, Wertebereiche).
- Nur das zurückgeben, was der Konsument wirklich braucht - keine internen IDs, keine Sys-Felder, keine Passwörter.
- Bei Fehlern keine internen Details an den Konsumenten zurückgeben; stattdessen schreibt der Server ohnehin Detail-Einträge in RESTSRV_PROTO.
Methoden-Signatur
Pro HTTP-Methode wird im Skript eine gleichnamige Funktion implementiert. Der Server ruft genau die Funktion auf, die zur Methode des eingehenden Requests passt.
function Get (oParams: TStrings; oBody: TJSONObject): string; function Post (oParams: TStrings; oBody: TJSONObject): string; function Put (oParams: TStrings; oBody: TJSONObject): string; function Delete(oParams: TStrings; oBody: TJSONObject): string; function Patch (oParams: TStrings; oBody: TJSONObject): string;
Nicht implementierte Methoden liefern automatisch 405-ähnliche Fehler über die generische Skript-Antwort.
Parameter
oParams (TStrings)
Enthält alle Query-Parameter, POST-Parameter (form-urlencoded) sowie die durchgereichten HTTP-Header. Werte sind immer Strings.
Filterung durch den Server:
- Geblockte Header werden nicht durchgereicht: authorization, cookie, proxy-authorization, x-forwarded-for, x-real-ip, apikey, api_key.
- Parameter mit Präfix _OBS_ können von aussen nicht gesetzt werden; sie sind für interne Werte reserviert.
- Werte werden auf max. 1024 Zeichen begrenzt.
- Null-Bytes und Steuerzeichen (ausser Tab, CR, LF) werden entfernt.
Zugriff im Skript:
if (not Empty(oParams.Values['kundennr'])) then begin
cKundenNr := oParams.Values['kundennr'];
end;
oBody (TJSONObject)
Enthält den Request-Body, sofern dieser ein gültiges JSON-Objekt ist. Bei leerem oder nicht-JSON-Body wird nil übergeben.
cUser := ;
cPass := ;
if (Assigned(oBody)) then begin
oVal := oBody.GetValue('username');
if (Assigned(oVal)) then begin
cUser := oVal.Value;
end;
oVal := oBody.GetValue('password');
if (Assigned(oVal)) then begin
cPass := oVal.Value;
end;
end;
Maximale Body-Grösse: 10 MB.
Reservierte _OBS_-Parameter
Bei aktiver JWT-Authentifizierung stehen die Token-Claims als Parameter zur Verfügung:
| Parameter | Inhalt |
|---|---|
| _OBS_JWT_ID | JWT-Id (jti-Claim) - typisch die User-Id |
| _OBS_JWT_SUBJECT | Subject (sub-Claim) - typisch Benutzername |
| _OBS_JWT_AUDIENCE | Audience (aud-Claim) - typisch Mandant / Rolle |
| _OBS_JWT_CLAIM_<name> | Beliebiger Custom-Claim des Tokens (z.B. _OBS_JWT_CLAIM_tenant, _OBS_JWT_CLAIM_roles); im Folge-Skript lesbar |
| _OBS_TRACE_ID | Korrelations-ID des Requests (auch als Response-Header X-Trace-Id) |
Diese Werte werden vom Server gesetzt und können vom Skript für Berechtigungs- und Mandantenprüfungen verwendet werden.
Pfad-Parameter
Stammt der Endpunkt aus einem Pfad-Template mit Platzhaltern (siehe Endpunkte), stehen die aus den Platzhaltern erfassten Werte als reservierte Parameter mit Präfix _OBS_PATH_ in oParams bereit:
| Template | Zugriff im Skript |
|---|---|
/orders/{uid} |
oParams.Values['_OBS_PATH_uid'] |
/orders/{uid}/modules/{code} |
oParams.Values['_OBS_PATH_uid'], oParams.Values['_OBS_PATH_code'] |
Da der Präfix _OBS_ für von aussen gelieferte Header- und Query-Parameter gesperrt ist, sind diese Werte nicht durch den Client fälschbar. Ein vollständiges Beispiel zeigt Beispiel 4 - Pfad-Parameter.
Rückgabe
Die Rückgabe ist immer ein JSON-String. Wird ein leerer String zurückgegeben, antwortet der Server automatisch mit {}. Der Server setzt Content-Type auf application/json; charset=utf-8 und Status auf 200, sofern das Skript nicht selbst einen Fehler signalisiert.
Einfaches Beispiel:
function Get(oParams: TStrings; oBody: TJSONObject): string;
var oRes: TJSONObject;
begin
oRes := TJSONObject.Create();
try
oRes.AddPair('wert_string', '123');
oRes.AddPair('wert_int' , 456);
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
Antwort steuern: Statuscode, Header, traceId
Ein Skript kann den HTTP-Statuscode und beliebige Response-Header Über reservierte Felder in der Antwort setzen. Der Server wertet sie aus und entfernt sie vor dem Senden aus dem Body.
| Antwort-Feld | Wirkung |
|---|---|
| _OBS_HTTP_STATUS (Zahl) | HTTP-Statuscode (z.B. 201, 204, 400, 409, 422). Ohne Angabe: 200. Bei 204 wird kein Body gesendet. |
| _OBS_HEADERS (Objekt) | Beliebige Response-Header, z.B. ETag, Location, Retry-After. CR/LF in Namen/Werten werden entfernt (Schutz vor Header-Injection). |
Zusätzlich trÄgt jede Antwort den Header X-Trace-Id (Korrelations-ID). Dieselbe ID liegt dem Skript als oParams.Values['_OBS_TRACE_ID'] vor und erscheint in jeder Server-Fehler-Logzeile in RESTSRV_PROTO - so lÄsst sich ein Fehler ohne GerÄtezugriff im Log wiederfinden.
Beispiel: Anlegen mit 201, Location und ETag
function Post(oParams: TStrings; oBody: TJSONObject): string;
var oRes, oHdr: TJSONObject;
begin
oRes := TJSONObject.Create();
try
// ... Datensatz anlegen, neue UID + Version (ETag) ermitteln ...
oHdr := TJSONObject.Create();
oHdr.AddPair('Location', '/v1/orders/' + cNeueUid);
oHdr.AddPair('ETag', '1');
oRes.AddPair('_OBS_HTTP_STATUS', 201);
oRes.AddPair('_OBS_HEADERS', oHdr);
oRes.AddPair('uid', cNeueUid);
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
Beispiel: Strukturierter Fehler mit Statuscode und traceId
function Post(oParams: TStrings; oBody: TJSONObject): string;
var oRes, oErr: TJSONObject;
begin
oRes := TJSONObject.Create();
try
oErr := TJSONObject.Create();
oErr.AddPair('code', 'VALIDATION_FAILED');
oErr.AddPair('message', 'Feld "menge" fehlt');
oErr.AddPair('traceId', oParams.Values['_OBS_TRACE_ID']);
oRes.AddPair('_OBS_HTTP_STATUS', 422);
oRes.AddPair('error', oErr);
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
Beispiel: Optimistic Concurrency (ETag / If-Match)
Mit Statuscode, Headern und dem lesbaren Header If-Match fÜhrt das Skript je Datensatz einen VersionszÄhler und lehnt veraltete Schreibzugriffe mit 409 ab:
function Put(oParams: TStrings; oBody: TJSONObject): string;
var oRes, oHdr: TJSONObject;
nAktuell, nIfMatch: integer;
begin
oRes := TJSONObject.Create();
try
nAktuell := AuftragVersion(oParams.Values['_OBS_PATH_uid']);
nIfMatch := iVal(oParams.Values['if-match']);
if (nIfMatch <> nAktuell) then begin
oHdr := TJSONObject.Create();
oHdr.AddPair('ETag', xStr(nAktuell));
oRes.AddPair('_OBS_HTTP_STATUS', 409);
oRes.AddPair('_OBS_HEADERS', oHdr);
oRes.AddPair('error', TJSONObject.Create.AddPair('code', 'VERSION_CONFLICT'));
result := oRes.ToJSON();
exit;
end;
// ... speichern, Version hochzÄhlen ...
oHdr := TJSONObject.Create();
oHdr.AddPair('ETag', xStr(nAktuell + 1));
oRes.AddPair('_OBS_HEADERS', oHdr);
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
JWT-Authentifizierungs-Skript
Bei Zugängen mit aktiver JWT-Pflicht wird über den JWT-Endpunkt das im Zugang hinterlegte Authentifizierungs-Skript aufgerufen (F7 in der Zugänge-Liste). Das Skript muss eine Methode Authenticate bereitstellen:
function Authenticate(oParams: TStrings; oBody: TJSONObject): string;
var oRes : TJSONObject;
cUser : string;
cPass : string;
begin
oRes := TJSONObject.Create();
try
// Eingangsdaten lesen (Body oder Query-Param)
cUser := '';
cPass := '';
if (Assigned(oBody)) then begin
oVal := oBody.GetValue('username');
if (Assigned(oVal)) then begin
cUser := oVal.Value;
end;
oVal := oBody.GetValue('password');
if (Assigned(oVal)) then begin
cPass := oVal.Value;
end;
end;
// Prüfung gegen eigene Tabelle, Hash-Verfahren, LDAP, ...
if (PasswortPasst(cUser, cPass)) then begin
oRes.AddPair('status' , 1);
oRes.AddPair('_OBS_JWT_ID' , cUser);
oRes.AddPair('_OBS_JWT_SUBJECT', cUser);
oRes.AddPair('_OBS_JWT_AUDIENCE', 'mandant1');
end else begin
oRes.AddPair('status', 9);
oRes.AddPair('error' , 'Login fehlgeschlagen');
end;
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
Rückgabewerte:
| status | Bedeutung |
|---|---|
| 1 | Erfolgreich, der Server erzeugt aus den _OBS_JWT_*-Werten einen Token (HS256) |
| 9 | Misserfolg, der Server antwortet mit 403 und protokolliert den Wert von error |
Custom-Claims, serverTime und Refresh
Das Authenticate-Skript kann dem Token zusÄtzlich beliebige Custom-Claims mitgeben - z.B. Mandant und Rollen - und optional ein Refresh-Token anstoßen:
| RÜckgabefeld | Bedeutung |
|---|---|
| _OBS_JWT_CLAIM_<name> | Beliebiger Custom-Claim, z.B. _OBS_JWT_CLAIM_tenant, _OBS_JWT_CLAIM_roles. Im Folge-Skript lesbar als oParams.Values['_OBS_JWT_CLAIM_<name>']. |
| _OBS_JWT_REFRESH_ID | (optional) jti des Refresh-Tokens. Nur wenn gesetzt, stellt der Server ein Refresh-Token aus. |
| _OBS_JWT_REFRESH_EXP | (optional) Lebensdauer des Refresh-Tokens in Minuten (Default 90 Tage = 129600). |
Jedes Token trÄgt automatisch den Claim token_use (access bzw. refresh). Die Antwort enthÄlt bei Erfolg serverTime (ISO-8601 mit Offset), bei ausgestelltem Refresh zusÄtzlich refreshToken:
{"token":"...", "refreshToken":"...", "serverTime":"2026-06-29T15:30:12+02:00"}
Refresh-Methode
Der Refresh lÄuft Über denselben JWT-Endpunkt und dasselbe Skript. Liegt am JWT-Endpunkt ein Authorization: Bearer <token> vor, ruft der Server statt Authenticate die Methode Refresh auf (sonst Login). Der Server verifiziert den Refresh-Token zuvor (Signatur, Ablauf, token_use=refresh); das alte Refresh-jti steht als oParams.Values['_OBS_JWT_ID'] bereit. Das Skript prÜft/rotiert seine Sperrtabelle und liefert wie Authenticate neue Claims zurÜck. Der Server stellt daraufhin ein rotiertes Token-Paar aus; ein bereits benutztes Refresh-Token wird mit 401 abgelehnt.
function Refresh(oParams: TStrings; oBody: TJSONObject): string;
var oRes: TJSONObject;
cAltesJti, cNeuesJti: string;
begin
oRes := TJSONObject.Create();
try
cAltesJti := oParams.Values['_OBS_JWT_ID'];
// altes Refresh-jti gegen eigene Sperrtabelle prÜfen
if (not RefreshJtiGueltig(cAltesJti)) then begin
oRes.AddPair('status', 9);
oRes.AddPair('error' , 'AUTH_EXPIRED');
result := oRes.ToJSON();
exit;
end;
cNeuesJti := GlobalUID();
RefreshJtiRotieren(cAltesJti, cNeuesJti); // altes sperren, neues ablegen
oRes.AddPair('status' , 1);
oRes.AddPair('_OBS_JWT_ID' , GlobalUID());
oRes.AddPair('_OBS_JWT_SUBJECT' , oParams.Values['_OBS_JWT_SUBJECT']);
oRes.AddPair('_OBS_JWT_CLAIM_tenant', oParams.Values['_OBS_JWT_CLAIM_tenant']);
oRes.AddPair('_OBS_JWT_CLAIM_roles' , oParams.Values['_OBS_JWT_CLAIM_roles']);
oRes.AddPair('_OBS_JWT_REFRESH_ID' , cNeuesJti);
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
Fehlerbehandlung im Skript
Tritt im Skript eine Exception auf oder schlägt die Syntax-Prüfung fehl, antwortet der Server mit 500 Interner Fehler und protokolliert die Detail-Meldung in RESTSRV_PROTO (mit Skript-Fehlertext). Der Konsument sieht keine internen Details.
Will das Skript einen spezifischen Fehler an den Konsumenten zurückgeben, ist eine eigene JSON-Struktur zu liefern:
function Post(oParams: TStrings; oBody: TJSONObject): string;
var oRes: TJSONObject;
begin
oRes := TJSONObject.Create();
try
if (Empty(oParams.Values['kundennr'])) then begin
oRes.AddPair('status', 'error');
oRes.AddPair('msg' , 'Parameter ''kundennr'' fehlt');
result := oRes.ToJSON();
exit;
end;
// ...
finally
MyFreeAndNil(oRes);
end;
end;
Skript-Cache
Der Server cached das kompilierte Skript pro Endpunkt (Schlüssel: sys_date des Endpunkts). Zusätzlich werden die Endpunkt-Definitionen pro Server-Profil in einem TTL-Cache (Standard 60 s) gehalten. Eine Skript-Änderung über F7 wird daher erst nach Ablauf dieses TTL (bzw. nach einer Cache-Invalidierung) wirksam - typischerweise innerhalb einer Minute, nicht zwingend sofort.
Verfügbare Bibliotheken
Im Skript können alle OBS-Standard-Bibliotheken verwendet werden. Typische Einstiegspunkte:
- Base.Tools - String-, Datum-, IIF-, Empty-Helper
- Base.DB / Base.xQuery - Datenbank-Operationen, DB_SQLVal, DB_SOpen, DB_LSeek
- Base.qSqlReg - qSqlInit, qSet, SaveData
- System.JSON - JSON-Objekte und -Arrays
- Base.ToolsConst - Konstanten wie CRLF, SINGELQUOTE, EMPTY_DATE
Konkrete Beispiele: