Zum Inhalt springen

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

Aus OBS Wiki
Rademacker (Diskussion | Beiträge)
Die Seite wurde neu angelegt: „{{Kostenpflichtige Module}} =Endpunkte= Ein '''Endpunkt''' definiert eine Adresse, ueber die der REST-Server eine konkrete Funktion anbietet. Hinter jedem Endpunkt steht entweder ein OBS-Skript (Standardfall) oder ein WebHook (Einmal-Antwort fuer asynchrone Rueckrufe). ==Aufruf== '''Stammdaten -> Z Weitere Stammdaten -> REST-Server -> Endpunkte''' ==Felder== {| class="wikitable" ! Feld !! Beschreibung |- | Endpunkt || Hauptname, erster Pfad-Best…“
MINERVA-Rademacker (Diskussion | Beiträge)
Idempotency-Key als Pflicht, Aufraeumen offener Reservierungen berichtigt, Fehlercodes 400/503/308 ergaenzt [Volltext ersetzt]
 
(14 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt)
Zeile 1: Zeile 1:
{{Kostenpflichtige Module}}
{{Kostenpflichtige Module}}


=Endpunkte=
= Endpunkte =


Ein '''Endpunkt''' definiert eine Adresse, ueber die der REST-Server eine konkrete Funktion anbietet. Hinter jedem Endpunkt steht entweder ein OBS-Skript (Standardfall) oder ein WebHook (Einmal-Antwort fuer asynchrone Rueckrufe).
Ein '''Endpunkt''' stellt eine Adresse bereit, über die der REST-Server konkrete
Funktionen anbietet. Jeder Endpunkt wird durch ein OBS-Skript oder einen WebHook
realisiert und ist genau einem Server-Profil zugeordnet.


==Aufruf==
== Routing über Pfad-Templates ==


'''Stammdaten -> Z Weitere Stammdaten -> REST-Server -> Endpunkte'''
Das Routing erfolgt über die Spalte '''Pfad-Template''' (<code>re_pathtemplate</code>).
Ein Template beschreibt den vollständigen Pfad nach dem Host und kann
'''Platzhalter''' enthalten:


==Felder==
* '''Statische Segmente''' müssen exakt übereinstimmen (Groß-/Kleinschreibung wird ignoriert).
* '''Platzhalter''' in geschweiften Klammern – z.&nbsp;B. <code>{uid}</code> – passen auf einen beliebigen Wert und werden als [[#Pfad-Parameter im Skript|Pfad-Parameter]] erfasst.
 
Beispiele für gültige Templates:


{| class="wikitable"
{| class="wikitable"
! Feld     !! Beschreibung
! Template !! Passt auf !! Pfad-Parameter
|-
| <code>/orders</code> || <code>/orders</code> || –
|-
| <code>/orders/{uid}</code> || <code>/orders/4711</code> || <code>uid = 4711</code>
|-
| <code>/orders/{uid}/modules/{code}</code> || <code>/orders/4711/modules/A1</code> || <code>uid = 4711</code>, <code>code = A1</code>
|-
| <code>/kalender/v1</code> || <code>/kalender/v1</code> || –
|}
 
=== Präzedenz bei mehreren Treffern ===
 
Passen mehrere Templates auf denselben Pfad, '''gewinnt das spezifischste''' –
also das mit den meisten statischen Segmenten. So schlägt <code>/orders/summary</code>
das Template <code>/orders/{uid}</code>, während <code>/orders/4711</code> auf
<code>/orders/{uid}</code> matcht.
 
== Hauptfelder eines Endpunkts ==
 
{| class="wikitable"
! Feld !! Spalte !! Zweck
|-
| '''Pfad-Template''' || <code>re_pathtemplate</code> || '''Maßgeblich fürs Routing.''' Vollständiger Pfad mit Platzhaltern, z.&nbsp;B. <code>/orders/{uid}/modules/{code}</code>
|-
| '''Server''' || <code>re_server</code> || Zuordnung zum Server-Profil (erforderlich)
|-
|-
| Endpunkt || Hauptname, erster Pfad-Bestandteil nach der Hostadresse (Pflichtfeld)
| '''Aktiv''' || <code>re_aktiv</code> || Statusflag; inaktive Endpunkte liefern 404
|-
|-
| Sub-URL  || Optionaler zweiter Pfad-Bestandteil, z.B. ''mobile'', ''office''
| '''Skript''' || <code>re_script</code> || DwScript-Quelltext des Handlers (siehe [[/OBS/Kostenpflichtige_Module/RESTServer/Scripting|Scripting]])
|-
|-
| Version  || Versionsbezeichner, z.B. ''v1'', ''v1.0'', ''v2''
| '''WebHook''' || <code>re_webhook</code> || optional: Verweis auf einen Einmal-Endpunkt statt Skript
|-
|-
| Server  || Zuordnung zu einem Server-Profil. Der Endpunkt ist nur ueber dieses Profil erreichbar (Pflichtfeld)
| '''Info''' || – || Dokumentations-Freitext
|-
|-
| Aktiv    || Inaktive Endpunkte sind nicht ansprechbar (404)
| '''Datei-Upload''' || <code>re_upload</code> || Erlaubt Datei-Uploads an diesem Endpunkt (<code>0</code> = aus, <code>1</code> = an). Ohne Freigabe werden Upload-Anfragen mit 415 abgelehnt.
|-
|-
| Info    || Freitext zur Dokumentation
| '''Max. Upload-Grösse''' || <code>re_upload_size</code> || Maximale Dateigrösse in MB. Leer/0 = Standard 25&nbsp;MB. Überschreitung wird mit 413 abgelehnt.
|}
|}


==Adressbildung==
== Pfad-Parameter im Skript ==


Die Adresse setzt sich aus Endpunkt, Sub-URL und Version zusammen:
Die aus den Platzhaltern erfassten Werte liest das Endpunkt-Skript über
<code>oReader.Path</code>:


http://[Hostadresse][:Port]/[Endpunkt][/Sub-URL]/[Version]
<syntaxhighlight lang="pascal">
procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
var cUid: string;
begin
    cUid := oReader.Path('uid');  // aus /orders/{uid}
    // ...
end;
</syntaxhighlight>


Beispiele:
Diese Werte stammen aus dem Routing und können nicht durch den Client
überschrieben werden.


https://api.meinserver.de/kalender/v1
== Datei-Uploads ==
https://api.meinserver.de/email/mein_konto/v1.0


Wird die Sub-URL leer gelassen, verkuerzt sich die Adresse auf ''/Endpunkt/Version''.
Ein Endpunkt nimmt Datei-Uploads nur entgegen, wenn er dafür freigeschaltet ist
(<code>re_upload = 1</code>). Andernfalls werden Upload-Anfragen mit
'''415 Unsupported Media Type''' abgelehnt und das Skript wird nicht ausgeführt.


==Versionierung==
Die maximal zulässige Dateigrösse wird pro Endpunkt über <code>re_upload_size</code>
(in MB) festgelegt; ohne Angabe gilt der Standard von '''25&nbsp;MB'''. Wird das
Limit überschritten, antwortet der Server mit '''413 Payload Too Large'''. Für
Uploads gilt nicht das JSON-Body-Limit (10&nbsp;MB), sondern dieses Endpunkt-Limit.
Der Originalname (beim resumable Upload über den Header <code>Upload-Metadata</code>) ist '''Pflicht'''; fehlt er, wird der Upload mit '''400''' abgelehnt, ohne dass eine Datei angelegt wird. Das Skript liest Pfad, Name, Content-Type und die SHA-256-Prüfsumme der gespeicherten Datei über <code>oReader.UploadPath()</code>, <code>UploadName()</code>, <code>UploadType()</code> und <code>UploadSha()</code>; bei <code>multipart/form-data</code> die übrigen Formularfelder über <code>oReader.Form('&lt;name&gt;')</code>.


Die Versionierung erlaubt es, eine Endpunkt-Funktionalitaet zu aendern, ohne bestehende Konsumenten zu brechen. Das Vorgehen:
'''Das Übertragungsprotokoll''' - Header, Statuscodes, Resume-Verhalten - steht auf einer eigenen Seite: [[OBS/Kostenpflichtige Module/RESTServer/Datei-Upload|Datei-Upload]].


# Den vorhandenen Endpunkt unveraendert lassen, weiterhin aktiv halten.
Unterstützt werden zwei Übertragungsarten:
# Einen neuen Endpunkt mit gleicher Endpunkt-/Sub-URL, aber neuer Version anlegen.
# Berechtigungen fuer die neuen Konsumenten setzen.
# Wenn alle Konsumenten umgestellt sind, den alten Endpunkt deaktivieren.


==Zuordnung zum Server-Profil==
* '''Einfacher Upload''' per <code>multipart/form-data</code> (eine Datei pro Request).
* '''Resumable/Chunked Upload''' per <code>Content-Range</code> (grosse Dateien, fortsetzbar nach Abbruch).


Ein Endpunkt ist immer genau einem Server-Profil zugeordnet. Damit lassen sich z.B. der gleiche Endpunkt-Name auf einem Public-Server (Standard-TLS) und einem Internal-Server (mTLS) parallel betreiben - die jeweils dem Server zugeordneten Endpunkte werden voneinander getrennt. Ein Konsument, der ueber das Public-Profil zugreift, kann nicht versehentlich die mTLS-Variante ansprechen.
Der Server legt die hochgeladene Datei in einem temporären Verzeichnis ab und
übergibt dem Endpunkt-Skript den Pfad. Was mit der Datei geschieht (Ablage,
DMS-Verknüpfung, Weiterverarbeitung), entscheidet allein das Skript. Details und
Beispiele: [[OBS/Kostenpflichtige Module/RESTServer/Scripting|Scripting]].


==Berechtigungen==
== Datei-Downloads ==


Damit ein Zugang einen Endpunkt aufrufen darf, muss er fuer diesen Endpunkt freigeschaltet sein. Aus der Endpunkt-Liste den Endpunkt markieren und '''F6''' druecken. Es oeffnet sich die Berechtigungs-Liste.
Ein Endpunkt kann eine '''Datei''' statt eines JSON-Körpers zurückgeben -
PDF, CSV, eine APK. Dafür ist '''keine Freischaltung''' nötig: ein Download
entsteht dadurch, dass das Skript ihn mit <code>oWriter.SendFile(...)</code>
bzw. <code>oWriter.SendTempFile(...)</code> erzeugt, nicht dadurch, dass ein
Client ihn anfragt.


In der Berechtigungs-Liste:
Content-Type, Dateiname, Bereichsanfragen (<code>Range</code>, '''206'''),
ETag und das Aufräumen temporärer Dateien übernimmt der Server. Einzelheiten:
[[OBS/Kostenpflichtige Module/RESTServer/Datei-Download|Datei-Download]].


* '''Einfg''' - oeffnet die Zugaenge-Liste.
== HTTP-Methode ==
* In der Zugaenge-Liste mit '''F5''' einen oder mehrere Zugaenge markieren.
* '''F2''' uebernimmt die markierten Zugaenge als Berechtigungen.


Ohne Markierung wird nur der aktuelle Zugang uebernommen.
Welche Funktion aufgerufen wird, ergibt sich aus dem HTTP-Verb: Der Server ruft
die gleichnamige Skript-Methode auf (<code>Get</code>/<code>Post</code>/<code>Put</code>/<code>Delete</code>/<code>Patch</code>).
Ein Template entspricht damit '''einem''' Endpunkt-Skript; unterschiedliche
Pfad-Formen (z.&nbsp;B. <code>/orders</code> vs. <code>/orders/{uid}</code>) sind
'''eigene Endpunkt-Einträge''' mit eigenem Skript und eigener Berechtigung.


==Endpunkt-Skript==
== URL-Struktur ==


Aus der Endpunkt-Liste den Endpunkt markieren und '''F7''' druecken. Es oeffnet sich der Skript-Editor. Im Skript werden GET-, POST-, PUT-, DELETE- bzw. PATCH-Funktionen implementiert, die der Server bei Aufruf der entsprechenden HTTP-Methode aufruft.
<pre>
http://[Host][:Port][Pfad-Template]
</pre>


Die genaue Skript-Syntax und alle verfuegbaren Parameter sind unter [[OBS/Kostenpflichtige Module/RESTServer/Scripting|Scripting]] beschrieben.
Beispiele:
* <code>https://api.meinserver.de/orders</code>
* <code>https://api.meinserver.de/orders/4711</code>
* <code>https://api.meinserver.de/orders/4711/modules/A1</code>


==WebHooks==
== Versionierung ==


Ein Endpunkt kann alternativ als WebHook konfiguriert werden. WebHooks sind '''Einmal-Endpunkte''' - sie werden in der Regel von OBS aus dynamisch fuer externe Dienste angelegt (z.B. Bezahl-Callbacks). Beim ersten erfolgreichen Aufruf:
Da es kein eigenes Versions-Feld mehr gibt, wird die Version als statisches
Segment ins Template aufgenommen, z.&nbsp;B. <code>/orders/v1</code> oder
<code>/v1/orders/{uid}</code>. Änderung ohne Breaking Change:


# Die uebergebenen Daten (Query-String oder Request-Body) werden in der Tabelle '''REMOTE_HOOK_URL''' im Feld ''hu_response'' gespeichert.
# Bestehenden Endpunkt unverändert lassen.
# Der Endpunkt wird automatisch aus '''RESTSRV_ENDPOINTS''' geloescht.
# Neuen Endpunkt mit gleichem Ressourcennamen, aber neuem Versions-Segment im Template anlegen.
# Der Server antwortet mit ''{"status": "ok"}''.
# Berechtigungen für neue Konsumenten setzen.
# Alten Endpunkt deaktivieren, wenn die Migration abgeschlossen ist.


Damit lassen sich asynchrone Rueckrufe sauber konsumieren, ohne dass dauerhaft offene Endpunkte zurueckbleiben. Die Anlage und Auswertung eines WebHooks erfolgt typischerweise programmgesteuert ueber die zugehoerigen Helper-Funktionen, nicht ueber die Endpunkt-Maske.
== Zugriffskontrolle ==


==Listen-Funktionen==
Endpunkte sind Server-Profilen zugeordnet; ein Zugang benötigt eine explizite
Berechtigung (Tabelle <code>RESTSRV_ACCESS</code>), um einen Endpunkt nutzen zu
dürfen.


In der Endpunkt-Liste:
{{Achtung|'''Ein Endpunkt besteht aus zwei Zeilen, und beide werden von Hand
gepflegt:''' der Eintrag in <code>RESTSRV_ENDPOINTS</code> und die Berechtigung
in <code>RESTSRV_ACCESS</code>. Fehlt die zweite, antwortet der Endpunkt
'''403''' - und sieht dabei fertig aus: Pfad, Skript und Server-Profil stehen
korrekt da, der Aufruf kommt trotzdem nicht durch. Das ist die häufigste Ursache
für ein „der Endpunkt ist doch angelegt" und kostet jedes Mal eine
Fehlersuche, weil der Statuscode nach einem Rechteproblem des Konsumenten
aussieht und nicht nach einer fehlenden Konfigurationszeile.


* '''Einfg''' - neuer Endpunkt
Beim Anlegen also '''immer beide Zeilen''', und beim Prüfen eines
* '''Return''' - Endpunkt bearbeiten
<code>403</code> zuerst nachsehen, ob die zweite existiert.}} Weil jede Pfad-Form ein eigener Endpunkt-Eintrag ist, lässt sich der
* '''F4''' - Sortierung
Zugriff '''pro Pfad-Form''' granular vergeben (z.&nbsp;B. Lesen von
* '''F6''' - Berechtigungen verwalten (Zugaenge dem Endpunkt zuordnen)
<code>/orders/{uid}</code> erlauben, aber das ändernde
* '''F7''' - Endpunkt-Skript bearbeiten
<code>PUT /orders/{uid}/modules/{code}</code> nicht).
* '''F8''' - Statistik fuer den Endpunkt


==Cache==
== Caching ==


Der REST-Server cached das kompilierte Skript pro Endpunkt. Wird das Skript ueber F7 geaendert, ist die neue Version sofort wirksam - der Server erkennt die Aenderung am ''sys_date'' und compiliert neu.
Die Endpunkt-Definitionen werden pro Server-Profil zwischengespeichert
(TTL, Standard 60&nbsp;s). Neue oder geänderte Endpunkte/Templates werden daher
erst nach Ablauf des Caches (bzw. nach einer Cache-Invalidierung) wirksam – nicht
zwingend sofort.


==Typische Fehlermeldungen==
== HTTP-Fehlercodes ==


{| class="wikitable"
{| class="wikitable"
! Code !! Bedeutung
! Code !! Ursache
|-
| '''404''' || Kein Template passt zum Pfad, Endpunkt inaktiv oder falsches Server-Profil
|-
|-
| 404  || Endpunkt nicht vorhanden / inaktiv / falsches Server-Profil
| '''403''' || Zugang fehlt in der Berechtigungsliste
|-
|-
| 403  || Keine Berechtigung fuer die Endpunkt-Nutzung (Zugang fehlt in der Berechtigungs-Liste)
| '''401''' || API-Key ungültig/fehlend, JWT abgelaufen oder Sitzung gesperrt (''AUTH_EXPIRED'')
|-
|-
| 401  || API-Key fehlt, ungueltig, JWT fehlt oder ist abgelaufen
| '''500''' || Skript- oder Syntax-Fehler (Details in <code>RESTSRV_PROTO</code>)
|-
|-
| 500  || Skript-Fehler oder Syntax-Fehler im Endpunkt-Skript - Detail siehe RESTSRV_PROTO
| '''405''' || Das Endpunkt-Skript hat für die angefragte HTTP-Methode keine Funktion; der Header <code>Allow</code> nennt die vorhandenen
|-
| '''206''' || Teilantwort eines Datei-Downloads auf eine <code>Range</code>-Anfrage; die Antwort enthält <code>Content-Range</code>
|-
| '''416''' || Der angefragte <code>Range</code> liegt hinter dem Dateiende; die Antwort nennt in <code>Content-Range</code> die wirkliche Grösse
|-
| '''415''' || Datei-Upload an einen Endpunkt, der dafür nicht freigeschaltet ist (<code>re_upload = 0</code>); oder ein <code>Content-Encoding</code>, das der Server nicht entpacken kann - unterstützt wird allein <code>gzip</code> (siehe [[OBS/Kostenpflichtige Module/RESTServer|REST-Server]], Abschnitt Kompression)
|-
| '''413''' || Hochgeladene Datei überschreitet die zulässige Maximalgrösse (<code>re_upload_size</code>); oder der Anfragekörper überschreitet 10 MB bzw. - gepackt gesendet - das 400fache der gesendeten Grösse
|-
| '''429''' || Rate-Limit überschritten; die Antwort enthält den Header <code>Retry-After</code>
|-
| '''503''' || Vier Ursachen, unterscheidbar am <code>code</code>: <code>IDEMPOTENCY_IN_PROGRESS</code> (eine Anfrage mit demselben <code>Idempotency-Key</code> wird gerade verarbeitet), <code>SERVICE_UNAVAILABLE</code> bei erreichter Andrangsgrenze des Server-Profils (<code>rsv_max_parallel</code>), bei einer Anmeldung, deren Sitzungszeile nicht geschrieben werden konnte, sowie bei einem fehlgeschlagenen <code>JwtRevoke</code>. Alle vier tragen <code>Retry-After</code>
|-
| '''409''' || Ein früherer Aufruf mit demselben <code>Idempotency-Key</code> ist ohne Ergebnis geblieben (<code>IDEMPOTENCY_UNRESOLVED</code>)
|-
| '''422''' || Derselbe <code>Idempotency-Key</code> wurde mit '''anderem''' Inhalt gesendet (<code>IDEMPOTENCY_KEY_REUSED</code>)
|-
| '''400''' || Ein schreibender Aufruf (POST/PUT/PATCH/DELETE) '''ohne''' <code>Idempotency-Key</code> (<code>IDEMPOTENCY_KEY_MISSING</code>); ein resumable Upload ohne Dateinamen; oder eine Anfrage, die unverschlüsselt an einen TLS-Port geschickt wurde (<code>http</code> statt <code>https</code>) - letztere wird vor Authentifizierung und Endpunkt-Skript abgewiesen
|-
| '''308''' || Zwischenantwort beim resumable Upload: das Teilstück wurde angehängt, der Upload ist noch nicht vollständig. Die Antwort trägt <code>Upload-Id</code>, <code>Upload-Offset</code> und <code>Range</code>; das Skript läuft dabei '''nicht'''
|}
|}
Jede Fehlerantwort besteht aus einem <code>error</code>-Objekt mit
maschinenlesbarem Code - Aufbau siehe
[[OBS/Kostenpflichtige Module/RESTServer|Übersicht]].
Daneben kann ein Endpunkt-Skript den Statuscode selbst setzen (z.B. <code>201</code>, <code>204</code>, <code>409</code>, <code>422</code>) sowie Response-Header wie <code>ETag</code> oder <code>Location</code> - siehe [[OBS/Kostenpflichtige Module/RESTServer/Scripting|Scripting]].
== Idempotenz ==
Bei <code>POST</code>, <code>PUT</code>, <code>PATCH</code> und <code>DELETE</code>
sorgt der Server selbst dafür, dass eine wiederholte Sendung '''keine
Zweitwirkung''' hat. Das gilt für '''jeden''' Endpunkt - es muss weder am Endpunkt
etwas eingestellt noch im Skript etwas programmiert werden.
{{Achtung|'''Der Header <code>Idempotency-Key</code> ist bei diesen vier Verben
Pflicht.''' Fehlt er, antwortet der Server mit '''400'''
<code>IDEMPOTENCY_KEY_MISSING</code> und das Endpunkt-Skript läuft '''nicht''' an.
Bis v1.21 lief eine Anfrage ohne den Header ungeschützt durch; der Schutz gegen
die Doppelbuchung lag damit beim Client. Ausgenommen ist allein der
JWT-Endpunkt (Anmelden, Erneuern, Abmelden) - er greift vor der Adressauflösung
und kennt die Mechanik nicht.}}
{| class="wikitable"
! Situation !! Antwort des Servers
|-
| Kein <code>Idempotency-Key</code> gesendet || '''400''' <code>IDEMPOTENCY_KEY_MISSING</code>. Das Skript läuft nicht
|-
| Erster Aufruf || Endpunkt läuft normal, das Ergebnis wird zum Schlüssel gespeichert
|-
| Wiederholung, gleicher Inhalt || '''Gespeicherte Antwort''' (gleicher Statuscode, gleicher Body, gleiche Header) plus Header <code>Idempotent-Replay: true</code>. Das Skript läuft '''nicht'''
|-
| Wiederholung, anderer Inhalt || '''422''' <code>IDEMPOTENCY_KEY_REUSED</code> - derselbe Schlüssel für einen anderen Inhalt ist ein Client-Fehler
|-
| Erster Aufruf läuft noch || '''503''' <code>IDEMPOTENCY_IN_PROGRESS</code> mit <code>Retry-After</code>. Beim nächsten Versuch liegt die gespeicherte Antwort vor
|-
| Früherer Aufruf ohne Ergebnis || '''409''' <code>IDEMPOTENCY_UNRESOLVED</code>. Nur noch nach einem '''harten Abbruch''' des Dienstes zwischen Verarbeitung und Festschreiben; ob die Buchung stattgefunden hat, ist unbekannt. Der Aufräumlauf gibt solche Einträge nach 24 Stunden frei und protokolliert sie
|}
Wichtig für die Auslegung eines Endpunkts:
* Antwortet das Skript mit '''2xx''', wird die Antwort gespeichert und bei einer Wiederholung erneut ausgeliefert.
* '''Ausnahme Dateiantwort:''' Liefert das Skript mit <code>SendFile</code>/<code>SendTempFile</code> eine '''Datei''' aus, wird '''nichts''' festgeschrieben - eine Dateiantwort liesse sich nicht wiedergeben, gespeichert würde eine leere 200er-Antwort. Der Schlüssel wird stattdessen wieder freigegeben, ein erneuter Abruf derselben Datei ist unschädlich.
* Antwortet das Skript mit '''4xx''' (fachliche Ablehnung), wird der Schlüssel wieder '''freigegeben''' - der Konsument darf ihn nach Korrektur erneut verwenden. Sonst würde ein einziger Validierungsfehler den Schlüssel dauerhaft blockieren.
* Endet die Verarbeitung mit '''5xx''' oder einem Skript-Fehler, wird der Schlüssel '''freigegeben''' und der Fall protokolliert. Der Konsument darf denselben Schlüssel erneut senden.
* Das ist eine bewusste Abwägung (geändert 2026-08-25): Bleibt der Schlüssel nach einem Serverfehler belegt, ist der Vorgang '''unwiederholbar''' - der Client bekommt dauerhaft 503 und müsste die erfasste Arbeit verwerfen. Garantierter Datenverlust wiegt schwerer als ein möglicher Doppelsatz.
* '''Folge für die Auslegung:''' ein schreibender Endpunkt sollte selbst wiederholbar sein, etwa über ein fachliches Merkmal des Clients. Wo reines Anhängen stattfindet (Positionen, Dateien), bleibt sonst ein Restrisiko.
Der Schlüssel gilt je '''Zugang, Methode und Pfad'''. Derselbe
<code>Idempotency-Key</code> an einem anderen Endpunkt ist damit ein eigener
Vorgang - der Konsument muss ihn nicht global eindeutig vergeben, aber pro Vorgang
'''stabil wiederverwenden''' (also nicht bei jedem Wiederholversuch neu erzeugen).
Die Einträge stehen in <code>RESTSRV_IDEMPOTENCY</code>. Abgeschlossene Einträge
werden nach '''30 Tagen''' gelöscht. Einträge '''ohne Ergebnis''' - sie entstehen
nur bei einem harten Abbruch des Dienstes - werden ab '''24 Stunden''' im Protokoll
gemeldet und '''anschliessend ebenfalls gelöscht'''. Das Stehenlassen wäre keine
Vorsicht, sondern eine Sperre: derselbe Vorgang mit demselben Schlüssel wäre sonst
nie wieder durchführbar.
== WebHooks ==
WebHooks sind '''Einmal-Endpunkte''' für asynchrone Callbacks. Beim ersten
erfolgreichen Aufruf wird die Antwort in der Tabelle <code>REMOTE_HOOK_URL</code>
gespeichert und der Endpunkt anschließend automatisch gelöscht.
Typischer Einsatz: OBS stösst einen Vorgang bei einem Fremdsystem an (Bezahldienst,
Versanddienstleister) und gibt diesem eine Rückruf-Adresse mit, die nur '''ein
einziges Mal''' gültig ist. Damit kann die Adresse nicht später erneut - oder von
jemand anderem - benutzt werden.
Ablauf:
# OBS legt einen Eintrag in <code>REMOTE_HOOK_URL</code> an und dazu einen Endpunkt, dessen Feld '''WebHook''' (<code>re_webhook</code>) auf diesen Eintrag zeigt. Ein Skript wird für diesen Endpunkt nicht gepflegt.
# Die Adresse dieses Endpunkts wird dem Fremdsystem als Callback-URL übergeben.
# Das Fremdsystem ruft die Adresse auf. Der Server legt den übergebenen Inhalt in <code>REMOTE_HOOK_URL.hu_response</code> ab, antwortet mit <code>{"status": "ok"}</code> und '''löscht den Endpunkt'''.
# Der weiterverarbeitende OBS-Prozess findet die Rückmeldung in <code>hu_response</code>.
{{Hinweis|Ein zweiter Aufruf derselben Adresse läuft ins Leere: Der Endpunkt
existiert nicht mehr, die Antwort ist 404. Das ist gewollt - ein WebHook ist keine
dauerhafte Schnittstelle. Für eine dauerhaft erreichbare Rückmelde-Adresse einen
normalen Endpunkt mit Skript anlegen.}}

Aktuelle Version vom 25. September 2026, 10:28 Uhr

Endpunkte

Ein Endpunkt stellt eine Adresse bereit, über die der REST-Server konkrete Funktionen anbietet. Jeder Endpunkt wird durch ein OBS-Skript oder einen WebHook realisiert und ist genau einem Server-Profil zugeordnet.

Routing über Pfad-Templates

Das Routing erfolgt über die Spalte Pfad-Template (re_pathtemplate). Ein Template beschreibt den vollständigen Pfad nach dem Host und kann Platzhalter enthalten:

  • Statische Segmente müssen exakt übereinstimmen (Groß-/Kleinschreibung wird ignoriert).
  • Platzhalter in geschweiften Klammern – z. B. {uid} – passen auf einen beliebigen Wert und werden als Pfad-Parameter erfasst.

Beispiele für gültige Templates:

Template Passt auf Pfad-Parameter
/orders /orders –
/orders/{uid} /orders/4711 uid = 4711
/orders/{uid}/modules/{code} /orders/4711/modules/A1 uid = 4711, code = A1
/kalender/v1 /kalender/v1 –

Präzedenz bei mehreren Treffern

Passen mehrere Templates auf denselben Pfad, gewinnt das spezifischste – also das mit den meisten statischen Segmenten. So schlägt /orders/summary das Template /orders/{uid}, während /orders/4711 auf /orders/{uid} matcht.

Hauptfelder eines Endpunkts

Feld Spalte Zweck
Pfad-Template re_pathtemplate Maßgeblich fürs Routing. Vollständiger Pfad mit Platzhaltern, z. B. /orders/{uid}/modules/{code}
Server re_server Zuordnung zum Server-Profil (erforderlich)
Aktiv re_aktiv Statusflag; inaktive Endpunkte liefern 404
Skript re_script DwScript-Quelltext des Handlers (siehe Scripting)
WebHook re_webhook optional: Verweis auf einen Einmal-Endpunkt statt Skript
Info – Dokumentations-Freitext
Datei-Upload re_upload Erlaubt Datei-Uploads an diesem Endpunkt (0 = aus, 1 = an). Ohne Freigabe werden Upload-Anfragen mit 415 abgelehnt.
Max. Upload-Grösse re_upload_size Maximale Dateigrösse in MB. Leer/0 = Standard 25 MB. Überschreitung wird mit 413 abgelehnt.

Pfad-Parameter im Skript

Die aus den Platzhaltern erfassten Werte liest das Endpunkt-Skript über oReader.Path:

procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
var cUid: string;
begin
    cUid := oReader.Path('uid');   // aus /orders/{uid}
    // ...
end;

Diese Werte stammen aus dem Routing und können nicht durch den Client überschrieben werden.

Datei-Uploads

Ein Endpunkt nimmt Datei-Uploads nur entgegen, wenn er dafür freigeschaltet ist (re_upload = 1). Andernfalls werden Upload-Anfragen mit 415 Unsupported Media Type abgelehnt und das Skript wird nicht ausgeführt.

Die maximal zulässige Dateigrösse wird pro Endpunkt über re_upload_size (in MB) festgelegt; ohne Angabe gilt der Standard von 25 MB. Wird das Limit überschritten, antwortet der Server mit 413 Payload Too Large. Für Uploads gilt nicht das JSON-Body-Limit (10 MB), sondern dieses Endpunkt-Limit. Der Originalname (beim resumable Upload über den Header Upload-Metadata) ist Pflicht; fehlt er, wird der Upload mit 400 abgelehnt, ohne dass eine Datei angelegt wird. Das Skript liest Pfad, Name, Content-Type und die SHA-256-Prüfsumme der gespeicherten Datei über oReader.UploadPath(), UploadName(), UploadType() und UploadSha(); bei multipart/form-data die übrigen Formularfelder über oReader.Form('<name>').

Das Übertragungsprotokoll - Header, Statuscodes, Resume-Verhalten - steht auf einer eigenen Seite: Datei-Upload.

Unterstützt werden zwei Übertragungsarten:

  • Einfacher Upload per multipart/form-data (eine Datei pro Request).
  • Resumable/Chunked Upload per Content-Range (grosse Dateien, fortsetzbar nach Abbruch).

Der Server legt die hochgeladene Datei in einem temporären Verzeichnis ab und übergibt dem Endpunkt-Skript den Pfad. Was mit der Datei geschieht (Ablage, DMS-Verknüpfung, Weiterverarbeitung), entscheidet allein das Skript. Details und Beispiele: Scripting.

Datei-Downloads

Ein Endpunkt kann eine Datei statt eines JSON-Körpers zurückgeben - PDF, CSV, eine APK. Dafür ist keine Freischaltung nötig: ein Download entsteht dadurch, dass das Skript ihn mit oWriter.SendFile(...) bzw. oWriter.SendTempFile(...) erzeugt, nicht dadurch, dass ein Client ihn anfragt.

Content-Type, Dateiname, Bereichsanfragen (Range, 206), ETag und das Aufräumen temporärer Dateien übernimmt der Server. Einzelheiten: Datei-Download.

HTTP-Methode

Welche Funktion aufgerufen wird, ergibt sich aus dem HTTP-Verb: Der Server ruft die gleichnamige Skript-Methode auf (Get/Post/Put/Delete/Patch). Ein Template entspricht damit einem Endpunkt-Skript; unterschiedliche Pfad-Formen (z. B. /orders vs. /orders/{uid}) sind eigene Endpunkt-Einträge mit eigenem Skript und eigener Berechtigung.

URL-Struktur

http://[Host][:Port][Pfad-Template]

Beispiele:

Versionierung

Da es kein eigenes Versions-Feld mehr gibt, wird die Version als statisches Segment ins Template aufgenommen, z. B. /orders/v1 oder /v1/orders/{uid}. Änderung ohne Breaking Change:

  1. Bestehenden Endpunkt unverändert lassen.
  2. Neuen Endpunkt mit gleichem Ressourcennamen, aber neuem Versions-Segment im Template anlegen.
  3. Berechtigungen für neue Konsumenten setzen.
  4. Alten Endpunkt deaktivieren, wenn die Migration abgeschlossen ist.

Zugriffskontrolle

Endpunkte sind Server-Profilen zugeordnet; ein Zugang benötigt eine explizite Berechtigung (Tabelle RESTSRV_ACCESS), um einen Endpunkt nutzen zu dürfen.

ACHTUNG: Ein Endpunkt besteht aus zwei Zeilen, und beide werden von Hand

gepflegt: der Eintrag in RESTSRV_ENDPOINTS und die Berechtigung in RESTSRV_ACCESS. Fehlt die zweite, antwortet der Endpunkt 403 - und sieht dabei fertig aus: Pfad, Skript und Server-Profil stehen korrekt da, der Aufruf kommt trotzdem nicht durch. Das ist die häufigste Ursache für ein „der Endpunkt ist doch angelegt" und kostet jedes Mal eine Fehlersuche, weil der Statuscode nach einem Rechteproblem des Konsumenten aussieht und nicht nach einer fehlenden Konfigurationszeile.

Beim Anlegen also immer beide Zeilen, und beim Prüfen eines

403 zuerst nachsehen, ob die zweite existiert.

Weil jede Pfad-Form ein eigener Endpunkt-Eintrag ist, lässt sich der

Zugriff pro Pfad-Form granular vergeben (z. B. Lesen von /orders/{uid} erlauben, aber das ändernde PUT /orders/{uid}/modules/{code} nicht).

Caching

Die Endpunkt-Definitionen werden pro Server-Profil zwischengespeichert (TTL, Standard 60 s). Neue oder geänderte Endpunkte/Templates werden daher erst nach Ablauf des Caches (bzw. nach einer Cache-Invalidierung) wirksam – nicht zwingend sofort.

HTTP-Fehlercodes

Code Ursache
404 Kein Template passt zum Pfad, Endpunkt inaktiv oder falsches Server-Profil
403 Zugang fehlt in der Berechtigungsliste
401 API-Key ungültig/fehlend, JWT abgelaufen oder Sitzung gesperrt (AUTH_EXPIRED)
500 Skript- oder Syntax-Fehler (Details in RESTSRV_PROTO)
405 Das Endpunkt-Skript hat für die angefragte HTTP-Methode keine Funktion; der Header Allow nennt die vorhandenen
206 Teilantwort eines Datei-Downloads auf eine Range-Anfrage; die Antwort enthält Content-Range
416 Der angefragte Range liegt hinter dem Dateiende; die Antwort nennt in Content-Range die wirkliche Grösse
415 Datei-Upload an einen Endpunkt, der dafür nicht freigeschaltet ist (re_upload = 0); oder ein Content-Encoding, das der Server nicht entpacken kann - unterstützt wird allein gzip (siehe REST-Server, Abschnitt Kompression)
413 Hochgeladene Datei überschreitet die zulässige Maximalgrösse (re_upload_size); oder der Anfragekörper überschreitet 10 MB bzw. - gepackt gesendet - das 400fache der gesendeten Grösse
429 Rate-Limit überschritten; die Antwort enthält den Header Retry-After
503 Vier Ursachen, unterscheidbar am code: IDEMPOTENCY_IN_PROGRESS (eine Anfrage mit demselben Idempotency-Key wird gerade verarbeitet), SERVICE_UNAVAILABLE bei erreichter Andrangsgrenze des Server-Profils (rsv_max_parallel), bei einer Anmeldung, deren Sitzungszeile nicht geschrieben werden konnte, sowie bei einem fehlgeschlagenen JwtRevoke. Alle vier tragen Retry-After
409 Ein früherer Aufruf mit demselben Idempotency-Key ist ohne Ergebnis geblieben (IDEMPOTENCY_UNRESOLVED)
422 Derselbe Idempotency-Key wurde mit anderem Inhalt gesendet (IDEMPOTENCY_KEY_REUSED)
400 Ein schreibender Aufruf (POST/PUT/PATCH/DELETE) ohne Idempotency-Key (IDEMPOTENCY_KEY_MISSING); ein resumable Upload ohne Dateinamen; oder eine Anfrage, die unverschlüsselt an einen TLS-Port geschickt wurde (http statt https) - letztere wird vor Authentifizierung und Endpunkt-Skript abgewiesen
308 Zwischenantwort beim resumable Upload: das Teilstück wurde angehängt, der Upload ist noch nicht vollständig. Die Antwort trägt Upload-Id, Upload-Offset und Range; das Skript läuft dabei nicht

Jede Fehlerantwort besteht aus einem error-Objekt mit maschinenlesbarem Code - Aufbau siehe Übersicht.

Daneben kann ein Endpunkt-Skript den Statuscode selbst setzen (z.B. 201, 204, 409, 422) sowie Response-Header wie ETag oder Location - siehe Scripting.

Idempotenz

Bei POST, PUT, PATCH und DELETE sorgt der Server selbst dafür, dass eine wiederholte Sendung keine Zweitwirkung hat. Das gilt für jeden Endpunkt - es muss weder am Endpunkt etwas eingestellt noch im Skript etwas programmiert werden.

ACHTUNG: Der Header Idempotency-Key ist bei diesen vier Verben

Pflicht. Fehlt er, antwortet der Server mit 400 IDEMPOTENCY_KEY_MISSING und das Endpunkt-Skript läuft nicht an.

Bis v1.21 lief eine Anfrage ohne den Header ungeschützt durch; der Schutz gegen die Doppelbuchung lag damit beim Client. Ausgenommen ist allein der JWT-Endpunkt (Anmelden, Erneuern, Abmelden) - er greift vor der Adressauflösung

und kennt die Mechanik nicht.
Situation Antwort des Servers
Kein Idempotency-Key gesendet 400 IDEMPOTENCY_KEY_MISSING. Das Skript läuft nicht
Erster Aufruf Endpunkt läuft normal, das Ergebnis wird zum Schlüssel gespeichert
Wiederholung, gleicher Inhalt Gespeicherte Antwort (gleicher Statuscode, gleicher Body, gleiche Header) plus Header Idempotent-Replay: true. Das Skript läuft nicht
Wiederholung, anderer Inhalt 422 IDEMPOTENCY_KEY_REUSED - derselbe Schlüssel für einen anderen Inhalt ist ein Client-Fehler
Erster Aufruf läuft noch 503 IDEMPOTENCY_IN_PROGRESS mit Retry-After. Beim nächsten Versuch liegt die gespeicherte Antwort vor
Früherer Aufruf ohne Ergebnis 409 IDEMPOTENCY_UNRESOLVED. Nur noch nach einem harten Abbruch des Dienstes zwischen Verarbeitung und Festschreiben; ob die Buchung stattgefunden hat, ist unbekannt. Der Aufräumlauf gibt solche Einträge nach 24 Stunden frei und protokolliert sie

Wichtig für die Auslegung eines Endpunkts:

  • Antwortet das Skript mit 2xx, wird die Antwort gespeichert und bei einer Wiederholung erneut ausgeliefert.
  • Ausnahme Dateiantwort: Liefert das Skript mit SendFile/SendTempFile eine Datei aus, wird nichts festgeschrieben - eine Dateiantwort liesse sich nicht wiedergeben, gespeichert würde eine leere 200er-Antwort. Der Schlüssel wird stattdessen wieder freigegeben, ein erneuter Abruf derselben Datei ist unschädlich.
  • Antwortet das Skript mit 4xx (fachliche Ablehnung), wird der Schlüssel wieder freigegeben - der Konsument darf ihn nach Korrektur erneut verwenden. Sonst würde ein einziger Validierungsfehler den Schlüssel dauerhaft blockieren.
  • Endet die Verarbeitung mit 5xx oder einem Skript-Fehler, wird der Schlüssel freigegeben und der Fall protokolliert. Der Konsument darf denselben Schlüssel erneut senden.
  • Das ist eine bewusste Abwägung (geändert 2026-08-25): Bleibt der Schlüssel nach einem Serverfehler belegt, ist der Vorgang unwiederholbar - der Client bekommt dauerhaft 503 und müsste die erfasste Arbeit verwerfen. Garantierter Datenverlust wiegt schwerer als ein möglicher Doppelsatz.
  • Folge für die Auslegung: ein schreibender Endpunkt sollte selbst wiederholbar sein, etwa über ein fachliches Merkmal des Clients. Wo reines Anhängen stattfindet (Positionen, Dateien), bleibt sonst ein Restrisiko.

Der Schlüssel gilt je Zugang, Methode und Pfad. Derselbe Idempotency-Key an einem anderen Endpunkt ist damit ein eigener Vorgang - der Konsument muss ihn nicht global eindeutig vergeben, aber pro Vorgang stabil wiederverwenden (also nicht bei jedem Wiederholversuch neu erzeugen).

Die Einträge stehen in RESTSRV_IDEMPOTENCY. Abgeschlossene Einträge werden nach 30 Tagen gelöscht. Einträge ohne Ergebnis - sie entstehen nur bei einem harten Abbruch des Dienstes - werden ab 24 Stunden im Protokoll gemeldet und anschliessend ebenfalls gelöscht. Das Stehenlassen wäre keine Vorsicht, sondern eine Sperre: derselbe Vorgang mit demselben Schlüssel wäre sonst nie wieder durchführbar.

WebHooks

WebHooks sind Einmal-Endpunkte für asynchrone Callbacks. Beim ersten erfolgreichen Aufruf wird die Antwort in der Tabelle REMOTE_HOOK_URL gespeichert und der Endpunkt anschließend automatisch gelöscht.

Typischer Einsatz: OBS stösst einen Vorgang bei einem Fremdsystem an (Bezahldienst, Versanddienstleister) und gibt diesem eine Rückruf-Adresse mit, die nur ein einziges Mal gültig ist. Damit kann die Adresse nicht später erneut - oder von jemand anderem - benutzt werden.

Ablauf:

  1. OBS legt einen Eintrag in REMOTE_HOOK_URL an und dazu einen Endpunkt, dessen Feld WebHook (re_webhook) auf diesen Eintrag zeigt. Ein Skript wird für diesen Endpunkt nicht gepflegt.
  2. Die Adresse dieses Endpunkts wird dem Fremdsystem als Callback-URL übergeben.
  3. Das Fremdsystem ruft die Adresse auf. Der Server legt den übergebenen Inhalt in REMOTE_HOOK_URL.hu_response ab, antwortet mit {"status": "ok"} und löscht den Endpunkt.
  4. Der weiterverarbeitende OBS-Prozess findet die Rückmeldung in hu_response.
HINWEIS: Ein zweiter Aufruf derselben Adresse läuft ins Leere: Der Endpunkt

existiert nicht mehr, die Antwort ist 404. Das ist gewollt - ein WebHook ist keine dauerhafte Schnittstelle. Für eine dauerhaft erreichbare Rückmelde-Adresse einen

normalen Endpunkt mit Skript anlegen.