restclient — n_pbt_restclient #
← Komponentenreferenz · Inhalt des Handbuchs
Ein HTTPS-Client für PowerBuilder, Version 10 eingeschlossen: GET, POST, PUT, PATCH, DELETE, Header, ein JSON-Körper, ein Datei-Download. Nativ — TLS, Proxy und Dekompression des Systems, und keine CORS-Mauer, weil die Anfrage nicht von einer Seite ausgeht.
▶ Live ansehen — Demoanwendung, Kachel REST client: die Antworten, der Code, der sie holt, und diese Seite nebeneinander (Internetverbindung erforderlich).
Kurzüberblick #
| Nichtvisuelles Objekt | n_pbt_restclient |
| Wofür | Eine REST-API aus einer PowerBuilder-Anwendung aufrufen: lesen, anlegen, aktualisieren, herunterladen |
| Prinzip | Eine Anfrage ist ein Aufruf, der den HTTP-Status liefert; die Antwort wartet im Objekt, gelesen per of_response_text oder of_json_value |
| Abhängigkeit | WinHTTP in der DLL der Bibliothek — keine Seite, keine zusätzliche Runtime |
Schnellstart #
// Local variables
n_pbt_restclient lnv_rest
n_pbt_json lnv_j
string ls_token, ls_customer
long ll_status
// One client for the API : its address and the token it sends (here read from the settings)
lnv_rest = create n_pbt_restclient
lnv_rest.is_base_url = "https://api.example.com"
ls_token = ProfileString("app.ini", "api", "token", "")
lnv_rest.of_set_bearer(/*token*/ ls_token)
// Read : the status comes back, the body waits in the object
ll_status = lnv_rest.of_get(/*url*/ "/orders/4152")
if ll_status = 200 then ls_customer = lnv_rest.of_json_value(/*path*/ "customer")
// Create : the body is built with n_pbt_json, never by hand
lnv_j.of_set_number(/*path*/ "order", /*value*/ 4152)
lnv_j.of_set_string(/*path*/ "status", /*value*/ "shipped")
ll_status = lnv_rest.of_post(/*url*/ "/shipments", /*body*/ lnv_j.of_text())
if ll_status < 0 then MessageBox("API", lnv_rest.of_get_last_error())
// The client is no longer needed
destroy lnv_rest
Jede HTTP-Methode liefert den Status, den der Server gewählt hat — ein 404 ist eine Antwort, kein Fehler — oder einen negativen Code, wenn die Anfrage nicht durchging: -5 ungültige URL oder ungültiger Header, Client-Zertifikat nicht gefunden, -4 Fehlschlag (kein Netz, unbekannter Name, Zeitüberschreitung, TLS, ein Textkörper über 64 MB — 16 MB in einem 32-Bit-Prozess; of_get_last_error() sagt, welcher), -6 im Demomodus abgelehnt. -2 bedeutet, dass der Client nicht angelegt werden konnte. Ein mit create erzeugtes Objekt genügt für synchrone Aufrufe; für die Ereignisse der asynchronen Aufrufe siehe unten „Wo die Events geschrieben werden“.
Warum nativ, und nicht die Seite #
Ein fetch() aus einer Seite der Bibliothek unterliegt CORS: jede API, die kein Access-Control-Allow-Origin sendet, ist ihm verschlossen — und Unternehmens-APIs senden es nicht. Die Anfrage geht daher von der DLL aus, über WinHTTP: das TLS des Systems mit seinen Zertifikaten, der konfigurierte Proxy, gzip. Was Sie verlieren: nichts; was Sie gewinnen: jede API.
Eigenschaften #
| Eigenschaft | Typ | Standard | Rolle |
|---|---|---|---|
is_base_url | string | "" | Vor eine relative URL gesetzt: is_base_url = "https://api.example.com", dann of_get("/orders/4152"). Eine absolute URL wird unverändert verwendet |
il_timeout_ms | long | 30000 | Wie lange jeder Schritt einer Anfrage dauern darf: Namensauflösung, Verbindung, Senden, dann das Warten auf jeden Block der Antwort. Ein großer Download, der vorankommt, wird nie abgebrochen; ein Server, der so lange schweigt, schon: -4 und „timed out“. 0 oder weniger bedeutet den Standard, 30 s: ein „kein Zeitlimit“ gibt es nicht |
il_status | long | 0 | Der HTTP-Status der letzten synchronen Anfrage oder ihr negativer Code; der Wert, den die Anfrage lieferte. Eine asynchrone Antwort hat ihren eigenen: of_status(id) |
ib_keep_cookies | boolean | true | Behält die Cookies zwischen Anfragen — synchronen und asynchronen, die eine Sitzung teilen: eine API, die beim Anmelden ein Cookie setzt, hält Sie angemeldet. Bei false wird kein Cookie mehr gesendet oder behalten, und die bisher behaltenen werden vergessen; sofort wirksam |
il_max_retries | long | 0 | Wie oft eine asynchrone Anfrage bei 429, 503 oder einem Netzfehler wiederholt wird. Ein POST, dessen Antwort verloren ging, wird nie wiederholt: er könnte verarbeitet worden sein. Ein Fehlschlag, der sich genauso wiederholen würde, wird nie wiederholt: zu viele Umleitungen, ungültige Umleitung, abgelehnte Umleitung HTTPS → HTTP, TLS-Fehler, nicht schreibbare Datei |
il_retry_backoff_ms | long | 500 | Die Wartezeit vor dem ersten Wiederholungsversuch in Millisekunden, bei jedem Versuch verdoppelt und auf 60 s begrenzt. Ein Retry-After-Header des Servers (Sekunden oder Datum) hat Vorrang |
ib_windows_auth | boolean | false | Integrierte Windows-Authentifizierung (Negotiate / NTLM): verlangt ein Server sie — eine IIS-API im Intranet —, antworten die Anmeldedaten der Windows-Sitzung, ohne Eingabe. Standardmäßig aus: ohne sie werden sie nie gesendet, an keinen Server. Bei jeder Anfrage gelesen; nur Host und Port der Anfrage antworten damit: eine Umleitung auf einen anderen Host geht ohne sie weiter |
is_client_certificate | string | "" | Ein Client-Zertifikat (gegenseitiges TLS): der SHA-1-Fingerabdruck eines Zertifikats aus dem persönlichen Speicher — dem des Benutzers, dann dem des Computers —, wie die Zertifikatverwaltung ihn anzeigt; Leerzeichen und Doppelpunkte werden ignoriert. Leer = keines. Ein nicht gefundener Fingerabdruck lässt jede Anfrage -5 liefern („client certificate not found“). Bei jeder Anfrage gelesen. Eine Option, einen Zertifikatfehler des Servers zu ignorieren, gibt es nicht; es wird nur Host und Port der Anfrage vorgelegt, nie nach einer Umleitung anderswohin |
ib_check_revocation | boolean | false | Lehnt ein Serverzertifikat ab, das seine Stelle widerrufen hat: Sperrliste oder OCSP der Kette werden geprüft. Standardmäßig aus: lässt sich die Prüfung nicht durchführen — die Stelle ist aus einem geschlossenen Netz unerreichbar —, schlägt die Anfrage fehl (-4). Bei jeder Anfrage gelesen |
is_last_request | string | "" | Was die letzte Anfrage verlangt hat, zu lesen, wenn eine API 400 antwortet: Methode und URL, dann die von Ihrem Code gesetzten Header — ihre Geheimnisse maskiert: das Schema eines Authorization bleibt (Bearer ***), der Wert eines Headers, dessen Name key, token, secret, password oder cookie enthält, verschwindet —, dann die Größe des Körpers. Die Bibliothek fügt User-Agent, Content-Type und die Cookies hinzu |
ipo_owner | powerobject | null | Das visuelle Objekt, für das dieser Client arbeitet: die Lizenz wird an seiner Klasse geprüft. Nur in der Demoanwendung nötig; ein Entwicklungs- oder Laufzeitschlüssel schaltet den Client ohne es frei. Nicht freigeschaltet läuft der Client im Demomodus — Körper bei 4096 Zeichen abgeschnitten, weder Download noch Upload |
Konstanten: METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE für of_request.
Methoden #
| Methode | Rolle |
|---|---|
of_get_last_error ( ) → string | Warum die letzte synchrone Anfrage — oder ein sofort abgelehnter asynchroner Aufruf — einen negativen Code lieferte. Leer nach einer Anfrage, die den Server erreicht hat — ein 404 ist kein Fehler. Ein asynchroner Fehlschlag nennt seinen Grund in ue_failed; ein beim Setzen abgelehnter Header (of_set_header) nennt dort ebenfalls den Grund, und sie wird geleert, sobald eine asynchrone Anfrage abgeschickt wird |
of_set_header (string as_name, string as_value) → long | Ein Header, der ab jetzt mit jeder Anfrage gesendet wird; ein bereits gesetzter Name wird ersetzt, ein leerer Wert entfernt den Header. Content-Type wird von selbst ergänzt, wenn ein Körper ohne ihn gesendet wird (JSON, UTF-8); bei einem Upload wird der hier gesetzte ignoriert (multipart/form-data setzt die Bibliothek). Keiner dieser Header folgt einer Umleitung auf einen anderen Host. Liefert 0, oder -5 — nichts ändert sich — für einen leeren Namen, einen Namen mit Leerzeichen, Doppelpunkt oder Steuerzeichen, oder einen Wert mit Zeilenumbruch; „ein anderer Host“ heißt ein anderer Ursprung: ein anderer Host, Port oder ein anderes Schema — eine auf https:// umgeleitete http://-URL verliert ihr Token. Ein Wert wird in Latin-1 gesendet: ein Zeichen darüber hinaus (Griechisch, Japanisch…) wird schon beim Setzen abgelehnt (-5), of_get_last_error() nennt den Grund. Der User-Agent (PBToolboxAI/<Version>) wird hier ersetzt |
of_remove_header (string as_name) | Vergisst einen Header, nach Name (Groß-/Kleinschreibung egal) |
of_clear_headers ( ) | Vergisst alle Header, Authentifizierung eingeschlossen |
of_set_bearer (string as_token) → long | Authorization: Bearer bei jeder Anfrage. Eine Authentifizierung zur Zeit: es ersetzt eine Basic-Authentifizierung; ein leeres Token entfernt die Authentifizierung. Liefert 0, oder -5 für ein Token mit Zeilenumbruch — dann bleibt keine Authentifizierung, nie eine halbe; es ersetzt auch ein OAuth-Token (of_oauth_client_credentials) |
of_set_basic (string as_user, string as_password) → long | Basic-Authentifizierung; die DLL kodiert das Base64 selbst — PowerBuilder 10 hat keines. Sie ersetzt ein Bearer-Token; ein leerer Benutzer entfernt die Authentifizierung. Liefert 0, oder -5 für einen Benutzer oder ein Kennwort mit Zeilenumbruch — dann bleibt keine Authentifizierung. Jedes andere Zeichen ist erlaubt, Akzente und Japanisch eingeschlossen: das Paar geht als Base64 seines UTF-8 ab. Base64 schützt nichts: über https:// verbirgt TLS das Passwort, über http:// geht es lesbar |
of_oauth_client_credentials (string as_token_url, string as_client, string as_secret, string as_scope) → long | OAuth2 „Client Credentials“: fordert jetzt beim Token-Endpunkt ein Token an — ein Formular-POST mit grant_type, client_id, client_secret und scope (leer = keiner) —, und sendet es dann als Authorization: Bearer bei jeder Anfrage. Das Token wird eine Minute vor Ablauf (expires_in) und nach einem 401 von selbst neu angefordert: eine synchrone Anfrage wird dann einmal wiederholt, eine asynchrone erhält ihren 401, und die nächste Anfrage erneuert das Token. Die Token-Anfrage trägt nie die eigene Authentifizierung des Objekts. Eine Authentifizierung zugleich: of_set_bearer, of_set_basic, of_clear_headers und of_reset beenden sie. Liefert 0, sobald das Token gesetzt ist, -5 für eine leere URL oder einen leeren Client, -4, wenn der Endpunkt ablehnt oder nicht antwortet — of_get_last_error() nennt den Grund mit der error_description des Servers —, -2, wenn der Client nicht angelegt werden konnte. Ein Fehlschlag behält die Einstellungen: die nächste Anfrage fordert erneut an |
of_get (string as_url) → long | GET. Liefert den HTTP-Status, oder -5 ungültige URL oder ungültiger Header, Client-Zertifikat nicht gefunden, -4 Fehlschlag, -6 in der Demo abgelehnt. Das Fragment #… einer URL wird nie an den Server gesendet. -5 auch für in die URL geschriebene Anmeldedaten (https://user:pwd@…) — verwenden Sie of_set_basic —, -2, wenn der Client nicht angelegt werden konnte. Die Abfrage der URL geht so, wie sie geschrieben ist: bauen Sie sie mit of_url, sobald ein Wert aus einer Eingabe kommt |
of_post (string as_url, string as_body) → long | POST mit einem Körper (standardmäßig JSON, mit n_pbt_json gebaut). Liefert den HTTP-Status oder -5 / -4 / -6 |
of_post_form (string as_url, string as_names[], string as_values[]) → long | POST eines HTML-Formulars (application/x-www-form-urlencoded), jeder Name und jeder Wert kodiert: was ein OAuth2-Token-Endpunkt, eine ältere API oder ein Webformular erwartet. Ein leerer Name wird übersprungen, ein fehlender Wert ist leer. Liefert den HTTP-Status oder -5 / -4 / -6 / -2 wie of_post |
of_put (string as_url, string as_body) → long | PUT mit einem Körper. Liefert den HTTP-Status oder -5 / -4 / -6 |
of_patch (string as_url, string as_body) → long | PATCH mit einem Körper. Liefert den HTTP-Status oder -5 / -4 / -6 |
of_delete (string as_url) → long | DELETE. Liefert den HTTP-Status oder -5 / -4 / -6 |
of_request (string as_method, string as_url, string as_body {, string as_headers}) → long | Jede Methode (METHOD_* oder eine eigene), jeder Körper als UTF-8: was die fünf Kurzformen aufrufen. Liefert den HTTP-Status oder -5 / -4 / -6. as_headers: Header nur für diese Anfrage — Zeilen Name: Wert, verbunden durch ~r~n (ein Idempotency-Key, ein If-Match, ein anderes Token); sie ersetzen einen gleichnamigen Header, ein leerer Wert lässt einen weg, nichts wird für die nächste Anfrage behalten; eine ungültige Zeile liefert -5, und nichts geht ab. Eine fehlerhafte Methode (GE T) liefert -5 |
of_download (string as_url, string as_path) → long | GET direkt in eine Datei, Bytes unverändert, unabhängig von der Größe. Liefert den HTTP-Status oder -5 URL oder Pfad leer, -4 Fehlschlag, -6 in der Demo verweigert. Ein relativer Pfad wird beim Aufruf im aktuellen Ordner der Anwendung beim Laden der Bibliothek aufgelöst — nie dort, wohin ein DirList oder ein Dateidialog ihn seitdem verschoben hat |
of_retrieve (datastore ads_target, string as_url {, string as_path}) → long | GET eines JSON-Körpers und das bei as_path gefundene Array von Objekten in einen DataStore ("" = der Körper ist das Array, "data/items" = ein Array darin) — wie Retrieve: der DataStore wird zuerst geleert, dann eine Zeile je Objekt, jedes Mitglied in der Spalte gleichen Namens (Groß-/Kleinschreibung egal), in deren Typ umgewandelt (n_pbt_json.of_to_datastore); die Zeilen kommen unverändert an. Dieselben Überladungen gibt es für ein datawindow und ein datawindowchild (eine aus einer API gefüllte Dropdown-Liste). Liefert die Zeilenzahl oder -4, wenn die Antwort kein 2xx ist, kein JSON ist — im Demo-Modus wird ein Körper bei 4096 Zeichen abgeschnitten — oder dort kein Array enthält (der DataStore behält dann seine Zeilen), -5 für einen ungültigen DataStore oder eine ungültige URL, und die Codes von of_get; of_get_last_error() nennt den Grund |
of_response_text ( ) → string | Der Körper der letzten synchronen Antwort dieses Objekts — ein anderer Client überschreibt ihn nicht —, als Text: dekodiert nach dem vom Server angekündigten charset (UTF-8 und die älteren Zeichensätze: windows-125x, iso-8859-x, shift_jis, gbk/gb2312, big5, euc-kr, koi8-r…), standardmäßig UTF-8 (ein ungültiges Byte wird zum Ersatzzeichen). Leer nach einem Fehlschlag und nach of_download: dieser Körper steht in der Datei. Ein Textkörper ist auf 64 MB begrenzt, 16 MB in einem 32-Bit-Prozess: darüber -4 „body too large“ — verwenden Sie of_download |
of_response_headers ( ) → string | Alle Header der letzten synchronen Antwort, einer pro Zeile |
of_response_header (string as_name) → string | Ein Header der letzten synchronen Antwort, nach Namen; leer, wenn er nicht gesendet wurde; ein mehrfach gesendeter Header liefert alle seine Werte, verbunden durch , — Set-Cookie durch einen Zeilenumbruch (ein Cookie-Datum enthält ein Komma) |
of_json_value (string as_path) → string | Ein Wert des JSON-Körpers nach seinem Pfad — Schlüssel durch / verbunden, Array-Positionen ab 1, die Schreibweise von n_pbt_json: "customer", "json/customer", "items/1/qty". Eine Zeichenkette kommt dekodiert zurück, eine Zahl oder true/false wie geschrieben ("4152"), null leer, ein Objekt oder Array als JSON; leer, wenn der Pfad nicht existiert. Für mehr als einen Wert laden Sie of_response_text in ein n_pbt_json. Der Körper wird pro Antwort nur einmal analysiert: zehn Werte zu lesen kostet eine einzige Analyse |
of_url (string as_path, string as_names[], string as_values[]) → string | Eine URL mit ihrer Abfrage, jeder Name und jeder Wert kodiert: mit q = "C# & R&D" und page = "2" liefert of_url("/items", …) /items?q=C%23%20%26%20R%26D&page=2. Ein Pfad, der schon eine Abfrage hat, erhält die Paare nach einem &, ein Fragment (#top) bleibt am Ende, ein leerer Name wird übersprungen. Nichts wird behalten: sicher zwischen zwei asynchronen Anfragen. Die Bibliothek kodiert die Abfrage einer selbst geschriebenen URL nie — ein # schneidet sie ab, ein & fügt einen Parameter hinzu, ein + wird zum Leerzeichen |
of_url_encode (string as_text) → string | Ein Wert, sicher für eine URL gemacht (RFC 3986, UTF-8): Buchstaben, Ziffern, -, ., _ und ~ bleiben, alles andere wird zu %XX — "C# & co" liefert C%23%20%26%20co. Leer für einen leeren oder NULL-Text |
of_reset ( ) → long | Zurück zu den Standardwerten: kein Header, keine Basis-URL, die Standard-Zeitüberschreitung, keine Wiederholung, Cookies behalten; der Client wird geschlossen — seine Cookies gehen, seine laufenden Anfragen werden ohne Ereignis abgebrochen — und die letzte Antwort wird vergessen. Die Windows-Authentifizierung wird ausgeschaltet (ib_windows_auth = false) und das Client-Zertifikat vergessen (is_client_certificate = ""); ib_check_revocation geht auf false zurück, ein OAuth-Token wird vergessen und is_last_request geleert Liefert 0, sobald erledigt. |
Beispiele #
Eine Liste lesen und durchgehen #
// Local variables
string ls_body, ls_id
n_pbt_json lnv_list
long ll_i
// One value : of_json_value("items/1/id"). The whole list : load the body into an
// n_pbt_json and walk it by path.
if lnv_rest.of_get(/*url*/ "/orders?status=open") = 200 then
ls_body = lnv_rest.of_response_text()
lnv_list.of_load(/*json*/ ls_body)
for ll_i = 1 to lnv_list.of_count(/*path*/ "items")
ls_id = lnv_list.of_get_string(/*path*/ "items/" + String(ll_i) + "/id")
next
end if
Ein Dokument herunterladen #
// Local variables
long ll_status
// Download the invoice to a file, then open it
ll_status = lnv_rest.of_download(/*url*/ "/orders/4152/invoice.pdf", /*path*/ "C:\temp\invoice-4152.pdf")
if ll_status = 200 then
Run("C:\temp\invoice-4152.pdf")
elseif ll_status < 0 then
MessageBox("Download", lnv_rest.of_get_last_error())
end if
Eine API, die mit einem Fehler antwortet #
// Local variables
n_pbt_json lnv_j
// The status decides : 200 or 201 = created, 400 = the request was refused (is_last_request
// tells what it asked for, its secrets masked), 401 = the token expired, below 0 = the network failed
lnv_j.of_set_number(/*path*/ "order", /*value*/ 4152)
choose case lnv_rest.of_post(/*url*/ "/shipments", /*body*/ lnv_j.of_text())
case 200, 201
case 400
MessageBox("API", lnv_rest.of_json_value(/*path*/ "message") + "~r~n~r~n" + lnv_rest.is_last_request)
case 401
MessageBox("API", "Token expired : " + lnv_rest.of_json_value(/*path*/ "message"))
case is < 0
MessageBox("Network", lnv_rest.of_get_last_error())
end choose
Eine Intranet-API mit Windows-Authentifizierung #
// Local variables
n_pbt_restclient lnv_rest
long ll_status
// An IIS API of the intranet : the Windows session answers the server's challenge,
// nothing is typed. Off by default : switch it on for this client only.
lnv_rest = create n_pbt_restclient
lnv_rest.is_base_url = "https://erp.intranet.local/api"
lnv_rest.ib_windows_auth = true
// A header refused (a line break in the value) returns -5 and is not set
if lnv_rest.of_set_header(/*name*/ "X-Client", /*value*/ "PowerBuilder") < 0 then return
// Read an order : 401 when the server refused the Windows account
ll_status = lnv_rest.of_get(/*url*/ "/orders/4152")
if ll_status = 401 then
MessageBox("ERP", "Access denied")
elseif ll_status < 0 then
MessageBox("ERP", lnv_rest.of_get_last_error())
end if
// The client is no longer needed
destroy lnv_rest
Eine vom Benutzer eingegebene Suche #
// Local variables
string ls_names[], ls_values[]
long ll_status
// What the user typed may hold # & + : of_url encodes every name and every value
ls_names[1] = "q"
ls_values[1] = sle_search.text
ls_names[2] = "page"
ls_values[2] = "2"
ll_status = lnv_rest.of_get(/*url*/ lnv_rest.of_url(/*path*/ "/items", /*names*/ ls_names, /*values*/ ls_values))
Eine durch OAuth2 geschützte API #
// Local variables
n_pbt_restclient lnv_api
// One client for an API protected by OAuth2 (Azure AD, Keycloak, Salesforce...) : the token
// is asked once, then renewed by the client itself a minute before it expires
lnv_api = create n_pbt_restclient
lnv_api.is_base_url = "https://api.example.com"
if lnv_api.of_oauth_client_credentials(/*token_url*/ "https://login.example.com/oauth2/token", /*client*/ "my-app", /*secret*/ ProfileString("app.ini", "api", "secret", ""), /*scope*/ "orders.read") < 0 then
MessageBox("API", lnv_api.of_get_last_error())
return
end if
// Every request now carries the token
lnv_api.of_get(/*url*/ "/orders/4152")
destroy lnv_api
Eine Liste in einen DataStore #
// Local variables
datastore lds_orders
long ll_rows
// The open orders of the API straight into a DataStore, as Retrieve does from a database :
// one row per object, each member in the column of its name, the rows not modified
lds_orders = create datastore
lds_orders.DataObject = "d_orders"
ll_rows = lnv_rest.of_retrieve(/*target*/ lds_orders, /*url*/ "/orders?status=open", /*path*/ "items")
if ll_rows < 0 then MessageBox("API", lnv_rest.of_get_last_error())
Asynchron (ohne die Oberfläche einzufrieren) #
Während eines synchronen Aufrufs ist die Oberfläche bis zur Antwort eingefroren: das Fenster zeichnet sich nicht mehr neu und reagiert nicht mehr auf Klicks. Bei einem kurzen Aufruf ist das folgenlos; für jeden Aufruf, der dauern kann — ein langsamer Server, ein großer Körper, ein unsicheres Netz —, nehmen Sie die asynchrone API: die Anfrage läuft in einem Arbeitsthread, und ihre Antwort kommt später als Ereignis zurück. Synchron und asynchron teilen sich dieselbe Sitzung: das Cookie einer synchronen Anmeldung dient den folgenden asynchronen Aufrufen und umgekehrt; ein synchroner Aufruf wartet nie hinter den asynchronen Anfragen desselben Objekts.
Einrichtung — nichts zu verdrahten: die Komponente liefert die Antwort selbst in ue_response. of_open / of_close öffnen und schließen den Client (optional: die erste Anfrage öffnet ihn), of_clear_cookies vergisst die zwischen Anfragen behaltenen Cookies. Intern ruft eine Pumpe der Komponente of_process_events auf, um die Antworten abzuholen — Sie rufen sie nie auf.
Senden — of_request_async(method, url, body) oder die Kurzformen of_get_async, of_post_async, of_put_async, of_patch_async, of_delete_async liefern sofort eine Anfrage-ID — oder -5 sofort, ohne nachfolgendes Ereignis, für eine ungültige URL oder einen ungültigen Header (of_get_last_error() sagt warum, zum Beispiel „invalid URL: …“). Höchstens sechs Anfragen laufen gleichzeitig; die weiteren warten in einer Warteschlange. of_download_async(url, path) lädt in eine Datei herunter und of_upload(url, field, path) lädt eine Datei als multipart/form-data hoch — beide mit Fortschritt. of_cancel(id) stoppt eine Anfrage. Wiederholungen (il_max_retries, il_retry_backoff_ms) greifen bei 429/503/Netzausfall.
Empfangen — ue_response(al_id, al_status) (den Körper mit of_response_text(al_id) / of_status(al_id) IM Handler lesen), ue_failed(al_id, al_code, as_error), ue_progress(al_id, al_done, al_total). Eine ID gilt nur für das Objekt, das die Anfrage gesendet hat. Ein Laufzeitfehler in einem dieser Ereignisse wird nicht verschluckt: er erreicht das Ereignis SystemError der Anwendung wie jeder Skriptfehler, und die folgenden Antworten werden trotzdem geliefert.
| Methode | Rolle |
|---|---|
of_open ( ) → long | Öffnet den Client: seine Sitzung (Cookies, die synchrone und asynchrone Aufrufe teilen) und seinen nativen Ereigniskanal (kein WebView, kein CORS). Optional: die erste Anfrage öffnet ihn. Liefert die Client-ID (> 0) oder -2, wenn er nicht erzeugt werden konnte (of_get_last_error() sagt, warum) |
of_close ( ) → long | Schließt den Client: seine Cookies (die Sitzung) gehen mit, und seine noch laufenden Anfragen werden ohne jedes Ereignis abgebrochen — kein ue_response, kein ue_failed. Die nächste Anfrage öffnet ihn wieder. Wird für Sie von of_reset und beim Zerstören erledigt. Die letzte synchrone Antwort des Objekts wird mit ihm vergessen Liefert 0, sobald erledigt. |
of_process_events ( ) | Leert die asynchronen Antworten und löst ue_response / ue_failed / ue_progress aus. NUR öffentlich, weil der interne Pump der Komponente sie auf der PowerBuilder-Schleife aufruft, alle paar Millisekunden, solange eine Anfrage läuft — Sie rufen sie nie auf und verdrahten weder Empfänger noch Timer. Ein eigener Aufruf ist harmlos: sie leert nur, was bereits wartet |
of_clear_cookies ( ) | Vergisst die zwischen Anfragen behaltenen Cookies (eine Abmeldung). Ob danach Cookies behalten werden, bleibt Sache von ib_keep_cookies |
of_get_async (string as_url) → long | Asynchrones GET. Liefert sofort eine Anfrage-id; die Antwort kommt in ue_response(id, status). Liefert sofort -5 — und kein Ereignis folgt — für eine ungültige URL oder einen ungültigen Header (of_get_last_error() sagt warum), -2, wenn sich der Client nicht öffnen lässt |
of_post_async (string as_url, string as_body) → long | Asynchrones POST mit Körper (standardmäßig JSON, mit n_pbt_json gebaut). Liefert sofort eine Anfrage-id; die Antwort kommt in ue_response. Liefert -5 / -2 wie of_get_async |
of_post_form_async (string as_url, string as_names[], string as_values[]) → long | Dasselbe Formular wie of_post_form, ohne Warten gesendet. Liefert sofort eine Anfrage-id; die Antwort kommt in ue_response. Liefert -5 / -2 wie of_get_async |
of_put_async (string as_url, string as_body) → long | Asynchrones PUT mit Körper. Liefert sofort eine Anfrage-id; die Antwort kommt in ue_response. Liefert -5 / -2 wie of_get_async |
of_patch_async (string as_url, string as_body) → long | Asynchrones PATCH mit Körper. Liefert sofort eine Anfrage-id; die Antwort kommt in ue_response. Liefert -5 / -2 wie of_get_async |
of_delete_async (string as_url) → long | Asynchrones DELETE. Liefert sofort eine Anfrage-id; die Antwort kommt in ue_response. Liefert -5 / -2 wie of_get_async |
of_request_async (string as_method, string as_url, string as_body {, string as_headers}) → long | Asynchrone Anfrage mit der HTTP-Methode Ihrer Wahl (of_get_async und seine Geschwister rufen sie auf). Liefert sofort eine Anfrage-id; die Antwort kommt in ue_response, ein Fehlschlag in ue_failed. Höchstens sechs Anfragen gleichzeitig, die weiteren warten. Liefert -2, wenn der Client sich nicht öffnen lässt, -4, wenn die Anfrage nicht eingereiht werden kann, sofort -5 — ohne Ereignis — für eine ungültige URL oder einen ungültigen Header. as_headers: Header nur für diese Anfrage, wie bei of_request |
of_download_async (string as_url, string as_path) → long | Asynchrones GET direkt in eine DATEI, mit ue_progress unterwegs. Liefert eine Anfrage-id, -5 bei leerem Pfad, -6 im Demomodus sofort abgelehnt, -2, wenn der Client sich nicht öffnen lässt. Ein relativer Pfad wird beim Aufruf aufgelöst, wie bei of_download |
of_upload (string as_url, string as_field, string as_path) → long | Lädt eine DATEI als multipart/form-data hoch (Formularfeld as_field), asynchron, mit ue_progress — immer ein POST mit genau diesem Feld. Liefert eine Anfrage-id, -5 bei leerem Pfad, -6 im Demomodus sofort abgelehnt, -2, wenn der Client sich nicht öffnen lässt. Eine relative Datei wird im aktuellen Ordner gesucht, dann im Ordner der Anwendung beim Laden der Bibliothek, dann neben der EXE. Der per of_set_header gesetzte Content-Type wird ignoriert: multipart/form-data setzt die Bibliothek |
of_cancel (long al_id) → long | Fordert eine Anfrage zum Anhalten auf; sie endet mit ue_failed(id, -4, "cancelled"), sofort, wenn sie noch auf ihren Platz wartete. Liefert 0, -5, wenn die ID unbekannt ist oder zu einem anderen Objekt gehört — nichts wird abgebrochen —, -4, wenn es zu spät ist: die Antwort ist da und kommt in ue_response |
of_status (long al_id) → long | Der HTTP-Status einer asynchronen Antwort, per Anfrage-ID — IM ue_response zu lesen. Liefert den Status (200, 404…); 0, solange die Anfrage läuft, für eine fehlgeschlagene Anfrage (in ue_failed), für eine unbekannte, bereits vergessene oder zu einem anderen Objekt gehörende ID |
of_response_text (long al_id) → string | Der Körper einer asynchronen Antwort, nach Anfrage-ID — IN ue_response zu lesen: die Anfrage wird vergessen, sobald das Ereignis zurückkehrt; leer, solange sie läuft; ebenfalls leer für die ID eines anderen Objekts |
of_response_headers (long al_id) → string | Alle Header einer asynchronen Antwort, einer pro Zeile |
of_response_header (long al_id, string as_name) → string | Ein Header einer asynchronen Antwort, nach Namen (Groß-/Kleinschreibung egal); wiederholte Werte verbunden wie bei of_response_header(as_name) |
of_json_value (long al_id, string as_path) → string | Ein Wert des JSON-Körpers einer asynchronen Antwort, nach Pfad — dieselbe Schreibweise wie of_json_value(as_path). In ue_response lesen; vor der Antwort gelesen (in ue_progress) liefert sie leer, und die Antwort wird trotzdem gelesen, wenn sie kommt |
| Ereignis | Rolle |
|---|---|
ue_response (long al_id, long al_status) | Eine asynchrone Anfrage ist beendet: al_id ist die beim Senden gelieferte id, al_status der HTTP-Status. Den Rumpf mit of_response_text(al_id) INNERHALB des Handlers lesen: die Anfrage ist nach dem Ereignis vergessen. Es wird am in das Fenster eingefügten Client-Objekt oder in einem Nachkommen geschrieben (siehe „Wo die Events geschrieben werden“). Daraus das Fenster zu schließen, das den Client trägt, ist erlaubt: die noch wartenden Antworten werden nicht zugestellt |
ue_failed (long al_id, long al_code, string as_error) | Eine asynchrone Anfrage ist fehlgeschlagen. al_code nennt die Art: -4 die Anfrage schlug fehl (kein Netz, Zeitüberschreitung, TLS, Körper zu groß, oder per of_cancel abgebrochen — as_error lautet dann cancelled), -5 ungültige Datei oder Client-Zertifikat nicht gefunden, -6 im Demomodus abgelehnt (eine Range-Anfrage); as_error sagt warum. of_status(al_id) liefert dort 0 |
ue_progress (long al_id, long al_done, long al_total) | Fortschritt einer asynchronen Anfrage: der Antwortkörper, während er eintrifft (jede Anfrage, ein großes JSON eingeschlossen), die Datei eines Downloads (of_download_async) oder eines Uploads (of_upload). al_done Bytes von al_total (0, wenn der Server die Größe nicht angekündigt hat); das letzte, unmittelbar vor ue_response, meldet al_done = al_total. Über 2 GB passen die Byte-Zahlen nicht mehr in ein long |
Wo die Events geschrieben werden #
Ein mit create n_pbt_restclient erzeugtes Objekt dient den synchronen Aufrufen sehr gut — das tut der Schnellstart —, aber seine Ereignisse haben kein Skript: PowerBuilder kann ue_response nicht auf ein in einem Skript erzeugtes Objekt schreiben. Für die asynchronen Aufrufe gibt es zwei Wege:
- das in das Fenster eingefügte Objekt (oder das Benutzerobjekt): fügen Sie im Painter ein nichtvisuelles Objekt
n_pbt_restclientein und nennen Sie esinv_rest; seine Ereignisseue_response,ue_failedundue_progresswerden dann wie die eines Steuerelements geskriptet, und es entsteht und vergeht mit dem Fenster — so macht es die Demoanwendung; - ein Nachkomme: erben Sie von
n_pbt_restclient(n_my_api), schreiben Sieue_responsein diesem Nachkommen und erzeugen Sie ihn mitcreate n_my_api.
Das Fenster, das den Client trägt, aus einem seiner Ereignisse zu schließen, ist erlaubt: die noch wartenden Antworten werden nicht zugestellt.
// Local variables
long ll_id
// Async : the UI never freezes, and there is nothing to wire. inv_rest is the restclient
// placed in the window ; the request id comes back at once
ll_id = inv_rest.of_get_async(/*url*/ "https://api.example.com/orders/4152")
// Local variables
string ls_body
// Event ue_response of inv_rest : the answer arrives on its own, and it is read here
if al_status >= 200 and al_status < 300 then ls_body = this.of_response_text(/*id*/ al_id)
Bewährte Praxis #
- Ein Client pro API, mit seinem
is_base_urlund seinen einmal gesetzten Headern: der Aufrufcode trägt nur noch Pfad und Körper. - Der Körper wird mit
n_pbt_jsongebaut, nie durch Verkettung: ein Anführungszeichen in einem Kundennamen zerstört nichts. - Prüfen Sie den Status, dann den negativen Code:
>= 200 und < 300ist gut,>= 400der Server hat abgelehnt,< 0die Anfrage ist nicht abgegangen —of_get_last_error()sagt warum. - Das Zeitlimit: standardmäßig 30 Sekunden; ein schwerer Bericht verdient mehr, eine Erreichbarkeitsprüfung weniger.
- Was dauern kann, läuft asynchron: ein synchroner Aufruf friert die Oberfläche bis zur Antwort ein.
- Eine Umleitung behält Ihre Anfrage: ein 301 oder 302 schreibt nur ein POST in ein GET um — ein PUT, PATCH oder DELETE wird unverändert erneut gesendet, Körper inklusive; ein 303 geht als GET ohne Körper weiter. Eine relative
Location(?page=2,../) wird aufgelöst, wie ein Browser es täte. - Eine Umleitung auf einen anderen Ursprung nimmt nichts von Ihnen mit: keiner der mit
of_set_headergesetzten Header (Token, API-Schlüssel) folgt ihr — nurContent-Typebleibt. Ein Ursprung ist ein Host, ein Port und ein Schema: eine aufhttps://umgeleitetehttp://-API verliert ihr Token, schreiben Sie alsohttps://inis_base_url. Die Windows-Anmeldedaten (ib_windows_auth) und das Client-Zertifikat erhält nur Host und Port der Anfrage. Eine Umleitung HTTPS → HTTP wird abgelehnt. - Prüfen Sie die Rückgabe von
of_set_header,of_set_bearerundof_set_basic, wenn der Wert aus einer Eingabe oder einer Datei stammt: ein Zeilenumbruch oder ein Zeichen jenseits von Latin-1 (ein Header reist in Latin-1; nicht beiof_set_basic, dessen Paar als Base64 abgeht) wird dort abgelehnt (-5), und nichts wird gesetzt. Ein abgelehnter Header geht nie halb fertig ab. - Windows-Anmeldedaten gehen nur ab, wenn Sie es verlangen:
ib_windows_auth = truefür eine Intranet-API mit Windows-Authentifizierung, nie bei einem Client, der mit dem Internet spricht. - Ein Textkörper ist auf 64 MB begrenzt, 16 MB in einer 32-Bit-Anwendung: ein großer Export wird mit
of_download/of_download_asyncgelesen, die ohne Grenze in eine Datei schreiben. - Kodieren Sie, was der Benutzer eingegeben hat: die Abfrage einer URL geht so ab, wie sie geschrieben ist — ein
#schneidet sie ab, ein&fügt einen Parameter hinzu, ein+wird zum Leerzeichen. Bauen Sie sie mitof_url, oder kodieren Sie einen Wert mitof_url_encode. - Nie Anmeldedaten in der URL (
https://user:pwd@…): die Anfrage wird abgelehnt (-5); nehmen Sieof_set_basic. - Der
User-AgentlautetPBToolboxAI/<Version>;of_set_header("User-Agent", …)ersetzt ihn, wenn eine API Ihren eigenen verlangt.