HTTP-Client (http)¶
Das Objekt http ist ein allgemeiner HTTP-Client. Damit sprichst du
beliebige Web-Dienste an — offene Daten-APIs, Datei-Downloads, Anmeldungen
mit Cookies, OAuth2-geschützte Endpunkte. Es steht in allen Skripten bereit:
Formular-Hooks (siehe Lifecycle), Schaltflächen,
Skript-Konsole, Jobs und
REST-Services.
http vs. ws — wann was?
Es gibt zwei Clients, und sie ergänzen sich:
ws— der Konnektor-Komfort-Client für SOAP und REST/JSON mit den GrundmusternsendReceive/fireAndForget. Ideal für die Anbindung von Fachsystemen (z. B. SAP), wo Endpunkt und Zugangsdaten zentral im Konnektor liegen.http— der allgemeine Client: volle Kontrolle über Methode, Header, Body-Kodierung (JSON, Form, Multipart, Bytes, Datei), Redirects, Retries, Cookies/Sessions, Downloads, gzip und OAuth2. Für alles, was kein klassischer Web-Service ist.
Beide teilen sich die SSRF-Allowlist und die
benannten Konnektoren — ein http.connect("name")
nutzt genau dieselbe Konnektor-Definition wie ws.connect("name").
Kurzreferenz¶
| Aufruf | Zweck |
|---|---|
http.get(url[, opts]) |
GET, Response-Objekt |
http.del(url[, opts]) |
DELETE |
http.head(url[, opts]) |
HEAD |
http.options(url[, opts]) |
OPTIONS |
http.post(url, body[, opts]) |
POST (Body → JSON/roh) |
http.put(url, body[, opts]) |
PUT |
http.patch(url, body[, opts]) |
PATCH |
http.request(opts) |
generisch — alles in opts ({ method, url, … }) |
http.session([opts]) |
Session mit eigenem Cookie-Jar (Login-Flows) |
http.connect(name[, overrides]) |
benannten Konnektor öffnen (liefert Session) |
http.download(url, "/pfad"[, opts]) |
direkt in die fs-Sandbox streamen |
http.oauth2ClientCredentials(opts) |
OAuth2-Token per Client-Credentials holen |
Einfache Aufrufe¶
Für jede HTTP-Methode gibt es eine Kurzform. Alle geben ein Response-Objekt zurück:
var r = http.get("https://api.example.com/v1/status");
var r = http.head("https://api.example.com/v1/large.bin");
var r = http.options("https://api.example.com/v1/orders");
var r = http.del("https://api.example.com/v1/orders/42");
var r = http.post("https://api.example.com/v1/orders", { id: 42 });
var r = http.put("https://api.example.com/v1/orders/42", { id: 42, done: false });
var r = http.patch("https://api.example.com/v1/orders/42", { done: true });
Der positionale body¶
Bei post/put/patch ist das zweite Argument der Body:
- Ein JS-Objekt oder -Array wird automatisch als JSON gesendet
(
Content-Type: application/json). - Ein String geht roh raus (
Content-Type: text/plain, außer du setztcontentTypeselbst oder eine andere Body-Option).
var url = "http://localhost:8099/api/demo/http/echo"; // Echo der App
// Objekt → JSON
http.post(url, { orderId: 42, items: ["A", "B"] });
// String → roh, mit eigenem Content-Type
http.post(url, "<xml/>", { contentType: "application/xml" });
Für alles darüber hinaus (Query, Header, Auth, Form, Multipart, …) nimmst du die
opts.
opts — alle Felder optional¶
Jeder Aufruf nimmt als letztes Argument ein Options-Objekt. http.request(opts)
packt alles dort hinein (inkl. method und url).
| Feld | Bedeutung |
|---|---|
method |
"GET", "POST", … (nur bei http.request) |
url |
Ziel-URL (nur bei http.request) |
query |
{ k: v } → an die URL angehängt (?k=v, URL-kodiert) |
headers |
{ Name: "Wert" } zusätzliche HTTP-Header |
json |
Objekt/Array → als JSON-Body (application/json) |
form |
{ k: v } → application/x-www-form-urlencoded |
multipart |
Array von Teilen → multipart/form-data |
text |
roher String-Body |
bytes |
Base64-String → Binär-Body |
filePath |
Pfad in der fs-Sandbox → Datei-Inhalt als Body |
body |
positionaler Body — auch hier in opts setzbar |
contentType |
Content-Type des Requests überschreiben |
auth |
"basic" | "bearer" | "none" |
username, password |
für auth:"basic" |
token |
für auth:"bearer" |
timeout |
Timeout in ms (Standard und Obergrenze 60000, siehe Zeitlimit) |
followRedirects |
Default true; jeder Hop läuft erneut durch den SSRF-Guard |
maxRedirects |
Obergrenze der Weiterleitungen (Default 10) |
gzip |
Default true — sendet Accept-Encoding: gzip, dekomprimiert automatisch |
insecureTLS |
true = TLS-Zertifikatsprüfung für diesen Aufruf aussetzen (self-signed) — mit Vorsicht! |
throwOnError |
true = wirft, wenn der Status nicht 2xx ist (statt r.ok zu prüfen) |
retries |
Zahl oder Retry-Objekt |
Body-Optionen schließen einander aus
json, form, multipart, text, bytes, filePath und der positionale
body sind verschiedene Wege, denselben Request-Body zu setzen. Nimm pro
Aufruf genau einen. Setzt du mehrere, gewinnt die spezifischere
Body-Option vor dem positionalen body.
Multipart-Teile¶
Ein Eintrag im multipart-Array ist entweder ein Textfeld oder eine Datei:
| Form | Bedeutung |
|---|---|
{ name, value } |
einfaches Textfeld |
{ name, filePath:"/pfad", filename?, contentType? } |
Datei aus der fs-Sandbox |
{ name, bytes:"<base64>", filename?, contentType? } |
Datei aus Base64-Bytes im Skript |
Retries¶
retries kannst du als Zahl (dann Standard-Verhalten mit dieser Anzahl
Wiederholungen) oder als Objekt angeben:
| Feld | Bedeutung |
|---|---|
count |
Anzahl der Wiederholungen |
delayMs |
Basis-Wartezeit vor der ersten Wiederholung |
maxDelayMs |
Obergrenze der Wartezeit (Backoff wächst nicht darüber) |
jitter |
zufällige Streuung der Wartezeit (Standard true) |
onStatus |
[…] überschreibt die Status-Liste, bei der wiederholt wird |
onIoError |
bei Netz-/IO-Fehlern wiederholen (Standard true) |
Standardmäßig wiederholt der Client automatisch bei 429 (Rate-Limit) und
5xx (Server-Fehler) sowie bei Netzfehlern, mit Exponential-Backoff +
Jitter. onStatus:[…] ersetzt diese Status-Liste durch deine eigene.
Das Response-Objekt¶
Alle Aufrufe (außer download) liefern
ein Objekt mit diesen Feldern und Methoden:
| Feld / Methode | Typ | Bedeutung |
|---|---|---|
status |
int | HTTP-Statuscode (z. B. 200, 404) |
statusText |
String | Statustext (z. B. "Not Found") |
ok |
boolean | true bei 2xx |
contentType |
String | Content-Type der Antwort |
url |
String | finale URL nach Redirects |
headers |
Objekt | Response-Header { name: "wert" } |
text |
String | Antwort-Body als Text |
json() |
Funktion | parst text als JSON → JS-Objekt (wirft bei ungültigem JSON) |
xml() |
Funktion | parst text als XML (siehe XML lesen und bauen) |
bytes() |
Funktion | Antwort-Body als Base64-String |
saveTo(pfad) |
Funktion | schreibt den Body in die fs-Sandbox, gibt die Byte-Anzahl zurück |
Zur XML-Navigation von r.xml() (.name, .text, .children,
.attr("name"), .find("tag"), .findDeep("tag") …) siehe
XML lesen und bauen.
ok prüfen — HTTP-Fehler werfen nicht automatisch
Ein 404 oder 500 ist eine gültige Antwort: r.status ist gesetzt,
r.ok ist false — es fliegt keine Exception. Nur echte
Kommunikationsfehler (DNS, Timeout, TLS, SSRF-Sperre) werfen. Prüfe also
immer r.ok, und fange den Rest mit try/catch. Willst du HTTP-Fehler
lieber als Exception behandeln, setze throwOnError:true — dann wirft der
Client selbst, sobald der Status nicht 2xx ist.
Beispiele¶
1 — GET mit Query, json() und Fehlerbehandlung¶
// onFieldChange: Kundendaten zu einer eingegebenen Nummer laden
try {
var r = http.get("https://api.example.com/v1/customers", {
query: { number: $kundennummer, fields: "name,city" },
timeout: 10000
});
if (!r.ok) {
view.add(new view.Message("Service antwortete mit HTTP " + r.status));
} else {
var data = r.json(); // JSON → JS-Objekt (wirft bei kaputtem JSON)
$kundenname = data.name;
$kundenort = data.city;
}
} catch (e) {
view.add(new view.Message("Service nicht erreichbar: " + e.message));
}
2 — POST als JSON¶
// onSubmit: Vorgang an ein Fremdsystem melden
var r = http.post("https://api.example.com/v1/orders", {
orderId: $orderId,
customer: $kundennummer,
items: [
{ sku: $artikel1, qty: $menge1 },
{ sku: $artikel2, qty: $menge2 }
]
});
if (r.ok) {
$status = r.json().status;
} else {
throw new Error("Übertragung fehlgeschlagen (HTTP " + r.status + "): " + r.text);
}
Ein Fehler in onSubmit macht das Speichern nicht rückgängig
onSubmit läuft nach dem Speichern: Der Datensatz ist dann schon in
der Datenbank, eine geworfene Exception zeigt nur die Meldung an. Soll ein
fehlgeschlagener Aufruf das Speichern verhindern, gehört er in
onBeforeSave (siehe Lifecycle). Willst du
nur informieren, nutze view.add(new view.Message(...)).
3 — POST als form-urlencoded¶
Viele klassische Web-Formulare und Token-Endpunkte erwarten
application/x-www-form-urlencoded statt JSON. Dafür die form-Option:
var r = http.post("https://api.example.com/v1/lookup", null, {
form: { plz: $plz, land: "DE" }
});
// gesendeter Body: plz=<wert>&land=DE (URL-kodiert)
4 — Multipart-Upload einer fs-Datei¶
Eine Datei aus der fs-Sandbox plus ein paar Textfelder hochladen:
// onSubmit: erzeugte PDF an einen Upload-Endpunkt schicken
var r = http.post("https://api.example.com/v1/documents", null, {
multipart: [
{ name: "vorgang", value: String(ctx.instanceId) },
{ name: "typ", value: "rechnung" },
{ name: "file", filePath: "/exports/" + ctx.instanceId + ".pdf",
filename: "rechnung.pdf", contentType: "application/pdf" }
]
});
if (!r.ok) throw new Error("Upload fehlgeschlagen (HTTP " + r.status + ")");
Alternativ eine Datei direkt aus Skript-Bytes (Base64) statt aus der Sandbox:
multipart: [
{ name: "file", bytes: base64String, filename: "daten.bin",
contentType: "application/octet-stream" }
]
5 — Download in die fs-Sandbox streamen¶
http.download lädt die Antwort direkt in eine Datei — es entsteht kein
großer Blob im JS-Heap, taugt also auch für große Dateien. Rückgabe ist ein
kompaktes Objekt (kein Body):
var d = http.download(
"https://cdn.example.com/kataloge/2026.pdf",
"/downloads/katalog-2026.pdf", // Ziel in der fs-Sandbox
{ timeout: 120000 }
);
log.info("Geladen: " + d.bytes + " Byte nach " + d.path +
" (HTTP " + d.status + ", " + d.contentType + ")");
Rückgabe: { status, ok, bytes, path, contentType, url }. Die opts sind
dieselben wie sonst (Auth, Header, insecureTLS, followRedirects …).
6 — Response-Body speichern mit r.saveTo(...)¶
Wenn du den Body erst prüfen und dann ablegen willst, nimmst du einen
normalen Aufruf und r.saveTo:
var r = http.get("https://api.example.com/v1/report.csv");
if (r.ok) {
var n = r.saveTo("/exports/report-" + ctx.instanceId + ".csv");
log.info(n + " Byte gespeichert");
}
download vs. saveTo
http.download streamt direkt auf die Platte (speicher-schonend, ideal
für große Dateien). r.saveTo schreibt einen bereits geladenen Body —
praktisch, wenn du die Antwort ohnehin vorher auswertest. Für Downloads im
zweistelligen MB-Bereich und darüber immer download.
7 — Session-Login-Flow (Cookies)¶
Für Dienste, die per Cookie-Session authentifizieren, gibt dir
http.session() einen eigenen Cookie-Jar. Vom Server gesetzte Cookies
werden automatisch beim nächsten Aufruf mitgeschickt — der klassische Login:
var s = http.session({ baseUrl: "https://portal.example.com" });
// 1) Anmelden — der Server setzt ein Session-Cookie
var login = s.post("/login", null, {
form: { user: "service", pass: "geheim" } // besser aus einem Konnektor
});
if (!login.ok) throw new Error("Login fehlgeschlagen (HTTP " + login.status + ")");
// 2) Geschützte Ressource — Cookie wird automatisch mitgeschickt
var profil = s.get("/profil");
$anzeigename = profil.json().displayName;
log.debug("Cookies im Jar: " + s.cookieCount());
Eine Session bietet dieselben Methoden wie http selbst
(s.get/post/put/patch/del/head/options/request); relative Pfade werden
gegen baseUrl aufgelöst. Die opts von http.session(opts) — baseUrl,
headers, auth/username/password/token, timeout, insecureTLS —
gelten für alle Aufrufe der Session.
Jede Session-Methode nimmt wie auf http zusätzlich ein opts-Objekt für
diesen einen Aufruf, z. B. s.patch(url, body, { headers: {...} }).
| Session-Methode | Bedeutung |
|---|---|
s.clearCookies() |
Cookie-Jar leeren |
s.cookieCount() |
Anzahl gespeicherter Cookies |
s.close() |
tut nichts; nur vorhanden, damit übernommene Skripte laufen |
8 — Retries (Backoff/Jitter, 429/5xx)¶
Bei flatterigen oder Rate-limitierten Diensten übernimmt der Client das Wiederholen. Die Kurzform ist eine Zahl:
// bis zu 3 Wiederholungen bei 429/5xx/Netzfehler, mit Exponential-Backoff
var r = http.get("https://api.example.com/v1/quote", { retries: 3 });
Feiner steuerbar über das Objekt:
var r = http.get("https://api.example.com/v1/quote", {
retries: {
count: 5,
delayMs: 500, // erste Wartezeit
maxDelayMs: 8000, // Backoff-Obergrenze
jitter: true, // zufällige Streuung gegen Thundering-Herd
onStatus: [429, 503] // nur bei diesen Codes wiederholen
}
});
Standard ohne onStatus: wiederholt bei 429 und 5xx sowie bei
Netzfehlern. Die Wartezeit wächst exponentiell (delayMs, dann doppelt,
… bis maxDelayMs) und wird per Jitter leicht gestreut, damit nicht alle
Clients gleichzeitig erneut anklopfen.
9 — Bearer-Token & benannter Konnektor¶
Direkt mit Token (nur zur Illustration — Credentials gehören in einen Konnektor):
Sauberer über einen benannten Konnektor — Endpunkt und Auth bleiben zentral:
var c = http.connect("partner-api"); // baseUrl + Auth aus dem Konnektor
var r = c.get("/v1/me"); // relativer Pfad gegen baseUrl
$name = r.json().name;
10 — OAuth2 Client-Credentials → Bearer¶
Für Dienste mit OAuth2 Client-Credentials-Flow holt dir
http.oauth2ClientCredentials in einem Schritt den Token und liefert den
access_token-String zurück:
var token = http.oauth2ClientCredentials({
tokenUrl: "https://auth.example.com/oauth/token",
clientId: "kcforms",
clientSecret: "geheim", // besser aus einem Konnektor
scope: "orders.read"
});
// danach als Bearer verwenden
var r = http.get("https://api.example.com/v1/orders", {
auth: "bearer",
token: token
});
Standardmäßig geht die Client-Auth per HTTP-Basic (client_id/secret im
Authorization-Header). Verlangt der Token-Endpunkt die Credentials im Body,
setzt du authInBody:true. Brauchst du das ganze Token-Objekt (inkl.
expires_in, token_type …), setzt du full:true:
var tok = http.oauth2ClientCredentials({
tokenUrl: "https://auth.example.com/oauth/token",
clientId: "kcforms",
clientSecret: "geheim",
authInBody: true, // client_id/secret in den Body statt Basic-Header
full: true // ganzes Token-Objekt statt nur access_token
});
log.info("Token gültig für " + tok.expires_in + " s");
11 — insecureTLS nur bei internen self-signed Endpunkten¶
insecureTLS:true schaltet für diesen einen Aufruf die TLS-Zertifikats-
und Hostname-Prüfung ab. Nur für self-signed interne Endpunkte:
// intern gehostetes Tool mit selbst-signiertem Zertifikat
var r = http.get("https://interner-dienst.firma.local/status", {
insecureTLS: true
});
insecureTLS hebt den TLS-Schutz auf
Mit insecureTLS:true akzeptiert der Client jedes Zertifikat und prüft
den Hostnamen nicht — Man-in-the-Middle wird damit möglich. Nutze es
ausschließlich für vertrauenswürdige interne / self-signed Endpunkte,
niemals für öffentliche Dienste im Internet. Besser als jeder
Einzel-Aufruf: das Flag im Konnektor hinterlegen und
den Host gezielt auf die Allowlist setzen.
12 — gzip (automatisch)¶
Standardmäßig sendet der Client Accept-Encoding: gzip und dekomprimiert die
Antwort automatisch — r.text/r.json() liefern immer Klartext. Du musst
nichts tun; nur wenn ein Server mit gzip Probleme macht, kannst du es mit
gzip:false abschalten:
var r = http.get("https://api.example.com/v1/big.json"); // gzip an, transparent
var r = http.get("https://zickiger-dienst.example.com/x", { gzip: false });
In-App-Demo¶
Zum Ausprobieren bringt KCForms unter /api/demo/http/ mehrere Endpunkte
mit, die typische HTTP-Situationen nachstellen. Aus einem Skript erreichst du
sie über http://localhost:8099/api/demo/http/… — das Skript läuft im
Backend, localhost ist dort das Backend selbst:
| Endpunkt | Zweck |
|---|---|
.../echo |
spiegelt Methode, Body, Query und Header als JSON |
.../status/{code} |
antwortet mit dem angegebenen HTTP-Statuscode |
.../redirect |
leitet weiter (Redirect-Handling testen) |
.../gzip |
liefert eine gzip-komprimierte Antwort |
.../cookie |
setzt ein Cookie / prüft es beim nächsten Aufruf |
.../download |
liefert eine Datei zum Download |
.../upload |
nimmt einen Multipart-Upload entgegen |
.../token |
Mini-OAuth2-Token-Endpunkt |
Vorher localhost auf die Allowlist setzen
localhost ist ein internes Ziel; ohne Eintrag sperrt der
SSRF-Schutz den Aufruf. Trage localhost unter
Erlaubte Ziel-Hosts (Allowlist) ein (siehe Sicherheit) —
auf Test- und Entwicklungssystemen.
// echo — Request zurückgespiegelt
var r = http.post("http://localhost:8099/api/demo/http/echo",
{ gruss: "hallo" }, { query: { n: 42 } });
log.debug(JSON.stringify(r.json())); // { method:"POST", body:{gruss:"hallo"}, query:{n:"42"}, … }
// status — beliebigen Code provozieren
var r = http.get("http://localhost:8099/api/demo/http/status/503");
log.info("ok? " + r.ok + " status " + r.status); // ok? false status 503
// redirect — followRedirects greift automatisch
var r = http.get("http://localhost:8099/api/demo/http/redirect");
log.info("finale URL: " + r.url);
// gzip — transparent dekomprimiert
var r = http.get("http://localhost:8099/api/demo/http/gzip");
log.info(r.json().note); // "Diese Antwort war gzip-komprimiert."
// cookie — Session merkt sich das Cookie
var s = http.session({ baseUrl: "http://localhost:8099" });
s.get("/api/demo/http/cookie"); // Server setzt Cookie
var r2 = s.get("/api/demo/http/cookie"); // Cookie wird mitgeschickt
log.info(r2.text);
// download — direkt in die fs-Sandbox
var d = http.download("http://localhost:8099/api/demo/http/download", "/downloads/demo.bin");
log.info(d.bytes + " Byte");
// upload — Multipart entgegen
var u = http.post("http://localhost:8099/api/demo/http/upload", null, {
multipart: [{ name: "file", filePath: "/downloads/demo.bin", filename: "demo.bin" }]
});
log.info(u.json().parts.length + " Teile empfangen"); // parts: [{ name, filename, size, contentType }]
// token — OAuth2 Client-Credentials
var tok = http.oauth2ClientCredentials({
tokenUrl: "http://localhost:8099/api/demo/http/token",
clientId: "demo",
clientSecret: "demo"
});
log.info("access_token: " + tok);
Öffentliche Daten-Dienste anbinden¶
Das häufigste Praxis-Muster: eine offene REST-API abfragen, ggf. mit einem API-Key im Header, über mehrere Seiten paginieren und Rate-Limits abfedern.
Key im Header + json() auswerten¶
var r = http.get("https://opendata.example.org/api/v1/titel", {
headers: { "X-Api-Key": $apiKey },
query: { isbn: $isbn }
});
if (r.ok) {
var t = r.json();
$titel = t.title;
$verlag = t.publisher;
}
Paginierung per Schleife über query:{page:n}¶
Viele Dienste liefern seitenweise. Du läufst so lange, bis eine Seite leer
zurückkommt (oder ein hasMore-Flag false wird). Viele Seiten brauchen
Zeit — so etwas gehört in einen Job, nicht
in einen Formular-Hook (siehe Zeitlimit):
var alle = [];
for (var page = 1; page <= 50; page++) { // Obergrenze als Sicherheitsnetz
var r = http.get("https://opendata.example.org/api/v1/liste", {
headers: { "X-Api-Key": $apiKey },
query: { page: page, pageSize: 100 },
retries: { count: 3 } // Rate-Limits abfedern, s. u.
});
if (!r.ok) throw new Error("Abbruch auf Seite " + page + " (HTTP " + r.status + ")");
var stapel = r.json().items || [];
if (stapel.length === 0) break; // fertig
alle = alle.concat(stapel);
}
log.info("Insgesamt " + alle.length + " Datensätze geladen");
Rate-Limits mit retries abfedern¶
Öffentliche APIs antworten bei Überlast gern mit 429 Too Many Requests.
retries fängt das automatisch ab — der Client wartet mit Backoff + Jitter
und versucht es erneut, statt dass dein Skript hart abbricht:
var r = http.get("https://opendata.example.org/api/v1/quote", {
headers: { "X-Api-Key": $apiKey },
retries: { count: 4, delayMs: 1000, maxDelayMs: 15000, jitter: true }
});
http in Button-/Custom-Hooks¶
http steht in jedem Hook zur Verfügung, auch hinter einem kc:button. So
verdrahtest du z. B. einen „Jetzt hochladen"-Button (Details zum Hook-Modell in
Lifecycle):
// Custom-Hook "ladeHoch", an einem kc:button verdrahtet
var r = http.post("https://api.example.com/v1/documents", null, {
multipart: [
{ name: "vorgang", value: String(ctx.instanceId) },
{ name: "file", filePath: "/exports/" + ctx.instanceId + ".pdf",
filename: "beleg.pdf", contentType: "application/pdf" }
]
});
if (r.ok) {
view.add(new view.Message("Hochgeladen: " + r.json().id));
} else {
view.add(new view.Message("Fehlgeschlagen (HTTP " + r.status + ")"));
}
Benannte Konnektoren¶
Ein Konnektor bündelt Basis-URL, Auth, Default-Header und Timeout unter einem
Namen (System-Administration → Schnittstellen & Verteilung →
HTTP-/WebService-Konnektoren). http.connect("name") nutzt
dieselben Konnektoren wie ws.connect —
Endpunkt und Zugangsdaten bleiben zentral und aus dem Formular-Schema heraus.
http.connect liefert eine Session (mit eigenem Cookie-Jar) — du hast also
die volle http-API plus Cookie-Handling, und
relative Pfade lösen gegen die Konnektor-baseUrl auf:
var c = http.connect("partner-api"); // baseUrl = https://api.example.com, Auth hinterlegt
var r = c.get("/v1/orders", { query: { since: $stichtag } });
Einzelne Konnektor-Werte lassen sich über overrides (2. Argument von
http.connect) überschreiben: baseUrl, auth, username, password,
token, timeout, headers und insecureTLS. Ein deaktivierter Konnektor
wirft beim connect einen Fehler.
// gleicher Konnektor, aber mit einem Einmal-Token statt der hinterlegten Auth
var c = http.connect("partner-api", { auth: "bearer", token: einmalToken });
Credentials nicht ins Skript schreiben
username/password/token gehören in einen Konnektor — im Skript stehen
sie sonst im Klartext, und jeder mit Zugriff auf das Formular im Formular-Editor
kann sie lesen. Der Konnektor erlaubt außerdem zentrale Wartung
(URL-Wechsel, Passwort-Rotation).
Zeitlimit¶
Ein HTTP-Aufruf blockiert das Skript, bis die Antwort da ist. Dabei gilt das
Zeitlimit des Skripts (Formular-Hooks: 6 Sekunden, siehe
Sandbox) zusätzlich zum timeout des
Aufrufs: Braucht der Dienst länger, wird das Skript danach abgebrochen — auch
wenn der Aufruf selbst noch im timeout lag.
- In Formular-Hooks deshalb einen knappen
timeoutsetzen (z. B.3000) und langsame Dienste nicht im Vordergrund aufrufen. - Lange Abrufe und große Downloads gehören in einen Job; dort legst du das Zeitlimit je Job fest.
Sicherheit¶
Ausgehende Aufrufe laufen — gemeinsam mit ws — durch einen
SSRF-Schutz (Server-Side Request Forgery), damit ein Skript nicht
ungefiltert ins interne Netz greifen kann:
- Nur
httpundhttpssind zulässige Schemata. - Öffentliche Ziele sind ohne weiteres erlaubt.
- Interne Ziele (Loopback wie
localhost, private Netze wie10.x,192.168.x) sind gesperrt, solange ihr Host nicht auf der Allowlist steht. - Link-local-Adressen, darunter die Cloud-Metadaten-Adresse
169.254.169.254, sind immer gesperrt — auch über die Allowlist nicht freigebbar. - Jeder Redirect-Hop wird erneut geprüft; eine Weiterleitung kann den Schutz nicht umgehen.
Die Allowlist pflegst du unter System-Administration → Schnittstellen &
Verteilung → HTTP-/WebService-Konnektoren, Abschnitt Erlaubte
Ziel-Hosts (Allowlist) — ein Muster je Zeile oder durch Komma getrennt:
api.intern.firma.de, *.intern.firma.de (Subdomains und die Domain
selbst), localhost. Sie ist anfangs leer.
Alle internen Ziele freigeben
Mit kcforms.webservice.allow-private-networks=true (Umgebungsvariable
KCFORMS_WS_ALLOW_PRIVATE=true) sind alle internen Ziele erlaubt; nur
link-local bleibt gesperrt. Das ist bequem, schwächt aber den Schutz —
die Allowlist ist die gezieltere Lösung.
Server-seitige Obergrenzen
- Timeout —
kcforms.webservice.max-timeout-millis(Standard 60000 ms); ein größer gesetztertimeoutwird gekappt. - Download-Größe —
kcforms.http.max-download-bytes(Standard 500 MB) deckelt, wie vielhttp.downloadschreibt. - Redirects —
kcforms.http.max-redirects(Standard 10) begrenzt die Zahl der Weiterleitungen zusätzlich zu deinemmaxRedirects.
Zu insecureTLS siehe die
Warnung oben.
Verwandte Themen¶
ws-WebService-Client — SOAP + REST/JSON mit Konnektor-Komfort; teilt sich Allowlist und Konnektoren mithttp.fs-Sandbox — Ziel fürhttp.download, Quelle fürfilePath- und Multipart-Uploads.- Lifecycle-Hooks — in welchem Hook du
httpsinnvoll aufrufst (und wann ein Fehler das Speichern verhindert). view— Rückmeldungen ans Formular (view.Message) statt harter Abbrüche.