Zum Inhalt

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 Grundmustern sendReceive/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 setzt contentType selbst 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):

var r = http.get("https://api.example.com/v1/me", {
  auth:  "bearer",
  token: "eyJhbGciOi…"
});

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.

var c = http.connect("partner-api");

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 timeout setzen (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 http und https sind zulässige Schemata.
  • Öffentliche Ziele sind ohne weiteres erlaubt.
  • Interne Ziele (Loopback wie localhost, private Netze wie 10.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 gesetzter timeout wird gekappt.
  • Download-Größe — kcforms.http.max-download-bytes (Standard 500 MB) deckelt, wie viel http.download schreibt.
  • Redirects — kcforms.http.max-redirects (Standard 10) begrenzt die Zahl der Weiterleitungen zusätzlich zu deinem maxRedirects.

Zu insecureTLS siehe die Warnung oben.

Verwandte Themen

  • ws-WebService-Client — SOAP + REST/JSON mit Konnektor-Komfort; teilt sich Allowlist und Konnektoren mit http.
  • fs-Sandbox — Ziel für http.download, Quelle für filePath- und Multipart-Uploads.
  • Lifecycle-Hooks — in welchem Hook du http sinnvoll aufrufst (und wann ein Fehler das Speichern verhindert).
  • view — Rückmeldungen ans Formular (view.Message) statt harter Abbrüche.