PBToolboxAI v5 ← Site

restclient — n_pbt_restclient #

← Référence des composants · Sommaire du guide

Un client HTTPS pour PowerBuilder, la version 10 comprise : GET, POST, PUT, PATCH, DELETE, les en-têtes, un corps JSON, le téléchargement d'un fichier. Natif — le TLS, le proxy et la décompression du système, et aucun mur CORS, parce que la requête ne part pas d'une page.

▶ Le voir en vrai — Application de démonstration, tuile REST client : les réponses, le code qui les obtient et cette page, côte à côte (connexion Internet requise).


En bref #

Objet non visueln_pbt_restclient
Sert àAppeler une API REST depuis une application PowerBuilder : lire, créer, mettre à jour, télécharger
PrincipeUne requête est un appel qui rend le statut HTTP ; la réponse attend dans l'objet, lue par of_response_text ou of_json_value
DépendanceWinHTTP, dans la DLL de la bibliothèque — aucune page, aucun runtime supplémentaire

Démarrage rapide #

// 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

Chaque méthode HTTP rend le statut que le serveur a choisi — un 404 est une réponse, pas une erreur — ou un code négatif quand la requête n'a pas abouti : -5 URL ou en-tête invalide, certificat client introuvable, -4 échec (pas de réseau, nom inconnu, délai dépassé, TLS, corps de texte de plus de 64 Mo — 16 Mo dans un processus 32 bits ; of_get_last_error() dit lequel), -6 refusé en mode démo. -2 dit que le client n'a pas pu être créé. Un objet créé par create suffit pour le synchrone ; pour les événements de l'asynchrone, voyez « Où écrire les événements », plus bas.


Pourquoi natif, et pas la page #

Un fetch() lancé depuis une page de la bibliothèque est soumis à CORS : toute API qui n'envoie pas Access-Control-Allow-Origin lui est fermée, et les API d'entreprise ne l'envoient pas. La requête part donc de la DLL, par WinHTTP : le TLS du système et ses certificats, le proxy configuré, gzip. Ce que vous perdez : rien ; ce que vous gagnez : toutes les API.


Propriétés #

PropriétéTypeDéfautRôle
is_base_urlstring""Mis devant une URL relative : is_base_url = "https://api.example.com" puis of_get("/orders/4152"). Une URL absolue est prise telle quelle
il_timeout_mslong30000Délai de chaque étape d'une requête : résolution du nom, connexion, envoi, puis attente de chaque bloc de la réponse. Un gros téléchargement qui avance n'est jamais coupé ; un serveur muet pendant ce délai l'est : -4 et « timed out ». 0 ou moins vaut le défaut, 30 s : il n'existe pas de « pas de délai »
il_statuslong0Le statut HTTP de la dernière requête synchrone, ou son code négatif ; la valeur que la requête a rendue. Une réponse asynchrone a le sien : of_status(id)
ib_keep_cookiesbooleantrueGarde les cookies d'une requête à l'autre — synchrones et asynchrones, qui partagent une même session : une API qui pose un cookie à la connexion vous garde connecté. À false, plus aucun cookie n'est envoyé ni gardé, et ceux déjà gardés sont oubliés ; appliqué tout de suite
il_max_retrieslong0Combien de fois une requête asynchrone est rejouée sur 429, 503 ou une panne réseau. Un POST dont la réponse s'est perdue n'est jamais rejoué : il a pu être traité. Un échec qui se reproduirait à l'identique n'est jamais rejoué : trop de redirections, redirection invalide, redirection HTTPS → HTTP refusée, échec TLS, fichier impossible à écrire
il_retry_backoff_mslong500L'attente avant la première tentative, en millisecondes, doublée à chaque essai et plafonnée à 60 s. Un en-tête Retry-After du serveur (secondes ou date) passe devant
ib_windows_authbooleanfalseAuthentification Windows intégrée (Negotiate / NTLM) : quand un serveur la demande — une API IIS de l'intranet —, les identifiants de la session Windows répondent, sans rien saisir. Désactivée par défaut : sans elle, ils ne partent jamais, vers aucun serveur. Lue à chaque requête ; seuls l'hôte et le port de la requête y répondent : une redirection vers un autre hôte repart sans eux
is_client_certificatestring""Un certificat client (TLS mutuel) : l'empreinte SHA-1 d'un certificat du magasin personnel — celui de l'utilisateur, puis celui de la machine —, telle que le gestionnaire de certificats l'affiche ; espaces et deux-points ignorés. Vide = aucun. Une empreinte introuvable fait rendre -5 à toute requête (« client certificate not found »). Lue à chaque requête. Il n'existe pas d'option pour ignorer une erreur de certificat du serveur ; il n'est présenté qu'à l'hôte et au port de la requête, jamais après une redirection ailleurs
ib_check_revocationbooleanfalseRefuse un certificat de serveur que son autorité a révoqué : la liste de révocation ou l'OCSP de la chaîne est consulté. Désactivé par défaut : quand la vérification ne peut pas se faire — l'autorité injoignable depuis un réseau fermé —, la requête échoue (-4). Lu à chaque requête
is_last_requeststring""Ce que la dernière requête a demandé, à lire quand une API répond 400 : la méthode et l'URL, puis les en-têtes posés par votre code — leurs secrets masqués : le schéma d'un Authorization reste (Bearer ***), la valeur d'un en-tête dont le nom dit key, token, secret, password ou cookie disparaît —, puis la taille du corps. La bibliothèque ajoute User-Agent, Content-Type et les cookies
ipo_ownerpowerobjectnullL'objet visuel pour lequel ce client travaille : la licence se vérifie sur sa classe. Nécessaire seulement dans l'application de démonstration ; une clé de développement ou d'exécution débride le client sans lui. Non débridé, le client est en mode démo — corps coupés à 4096 caractères, ni téléchargement ni téléversement

Constantes : METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE pour of_request.


Méthodes #

MéthodeRôle
of_get_last_error ( ) → stringPourquoi la dernière requête synchrone — ou un appel asynchrone refusé sur-le-champ — a rendu un code négatif. Vide après une requête arrivée au serveur — un 404 n'est pas une erreur. Un échec asynchrone dit sa raison dans ue_failed ; un en-tête refusé à la pose (of_set_header) y dit aussi pourquoi, et elle est vidée quand une requête asynchrone part
of_set_header (string as_name, string as_value) → longUn en-tête envoyé avec chaque requête désormais ; un nom déjà posé est remplacé, une valeur vide retire l'en-tête. Content-Type est ajouté seul quand un corps part sans lui (JSON, UTF-8) ; pour un téléversement, celui posé ici est ignoré (multipart/form-data est posé par la bibliothèque). Aucun de ces en-têtes ne suit une redirection vers un autre hôte. Rend 0, ou -5 — rien ne change — pour un nom vide, un nom qui contient une espace, un deux-points ou un caractère de contrôle, ou une valeur qui contient un saut de ligne ; « un autre hôte » veut dire une autre origine : un autre hôte, un autre port ou un autre schéma — une URL http:// redirigée vers https:// perd son jeton. Une valeur part en Latin-1 : un caractère au-delà (grec, japonais…) est refusé (-5) dès la pose, of_get_last_error() dit pourquoi. Le User-Agent (PBToolboxAI/<version>) se remplace ici
of_remove_header (string as_name)Oublie un en-tête, par nom (la casse est indifférente)
of_clear_headers ( )Oublie tous les en-têtes, l'authentification comprise
of_set_bearer (string as_token) → longAuthorization: Bearer sur chaque requête. Une seule authentification à la fois : il remplace une authentification Basic ; un jeton vide retire l'authentification. Rend 0, ou -5 pour un jeton qui contient un saut de ligne — aucune authentification ne reste alors, jamais une moitié ; il remplace aussi un jeton OAuth (of_oauth_client_credentials)
of_set_basic (string as_user, string as_password) → longAuthentification Basic ; la DLL encode elle-même le base64 — PowerBuilder 10 n'en a pas. Elle remplace un jeton Bearer ; un utilisateur vide retire l'authentification. Rend 0, ou -5 pour un utilisateur ou un mot de passe qui contient un saut de ligne — aucune authentification ne reste alors. Tout autre caractère est permis, accents et japonais compris : la paire part en base64 de son UTF-8. Le base64 ne protège rien : en https:// le TLS cache le mot de passe, en http:// il part lisible
of_oauth_client_credentials (string as_token_url, string as_client, string as_secret, string as_scope) → longOAuth2 « client credentials » : demande maintenant un jeton au point de jeton — un POST en formulaire de grant_type, client_id, client_secret et scope (vide = aucun) —, puis l'envoie en Authorization: Bearer sur chaque requête. Le jeton est redemandé de lui-même une minute avant son expiration (expires_in) et après un 401 : une requête synchrone est alors rejouée une fois, une asynchrone reçoit son 401 et la suivante renouvelle le jeton. La demande de jeton ne porte jamais l'authentification de l'objet. Une seule authentification à la fois : of_set_bearer, of_set_basic, of_clear_headers et of_reset l'arrêtent. Rend 0 une fois le jeton posé, -5 pour une URL ou un client vides, -4 quand le point de jeton refuse ou ne répond pas — of_get_last_error() dit pourquoi, avec l'error_description du serveur —, -2 si le client n'a pas pu être créé. Un échec garde les réglages : la requête suivante redemande
of_get (string as_url) → longGET. Rend le statut HTTP, ou -5 URL ou en-tête invalide, certificat client introuvable, -4 échec, -6 refusé en démo. Le fragment #… d'une URL n'est jamais envoyé au serveur. -5 aussi pour des identifiants écrits dans l'URL (https://user:mdp@…) — utilisez of_set_basic —, -2 si le client n'a pas pu être créé. La requête de l'URL part telle qu'écrite : construisez-la avec of_url dès qu'une valeur vient d'une saisie
of_post (string as_url, string as_body) → longPOST avec un corps (JSON par défaut, construit avec n_pbt_json). Rend le statut HTTP, ou -5 / -4 / -6
of_post_form (string as_url, string as_names[], string as_values[]) → longPOST d'un formulaire HTML (application/x-www-form-urlencoded), chaque nom et chaque valeur encodés : ce qu'attend un point de jeton OAuth2, une API ancienne ou un formulaire web. Un nom vide est sauté, une valeur manquante est vide. Rend le statut HTTP, ou -5 / -4 / -6 / -2 comme of_post
of_put (string as_url, string as_body) → longPUT avec un corps. Rend le statut HTTP, ou -5 / -4 / -6
of_patch (string as_url, string as_body) → longPATCH avec un corps. Rend le statut HTTP, ou -5 / -4 / -6
of_delete (string as_url) → longDELETE. Rend le statut HTTP, ou -5 / -4 / -6
of_request (string as_method, string as_url, string as_body {, string as_headers}) → longN'importe quelle méthode (METHOD_*, ou la vôtre), n'importe quel corps envoyé en UTF-8 : ce que les cinq raccourcis appellent. Rend le statut HTTP, ou -5 / -4 / -6. as_headers : des en-têtes pour cette requête seule — des lignes Nom: valeur jointes par ~r~n (un Idempotency-Key, un If-Match, un autre jeton) ; ils remplacent un en-tête de même nom, une valeur vide en écarte un, rien n'est gardé pour la requête suivante ; une ligne invalide rend -5 et rien ne part. Une méthode mal formée (GE T) rend -5
of_download (string as_url, string as_path) → longGET directement dans un fichier, octets intacts, quelle que soit la taille. Rend le statut HTTP, ou -5 URL ou chemin vide, -4 échec, -6 refusé en démo. Un chemin relatif est résolu à l'appel dans le dossier courant de l'application au chargement de la bibliothèque — jamais là où un DirList ou une boîte de fichiers l'a déplacé depuis
of_retrieve (datastore ads_target, string as_url {, string as_path}) → longGET d'un corps JSON et le tableau d'objets trouvé à as_path dans un DataStore ("" = le corps est le tableau, "data/items" = un tableau à l'intérieur) — comme Retrieve : le DataStore est vidé d'abord, puis une ligne par objet, chaque membre dans la colonne du même nom (casse ignorée), converti à son type (n_pbt_json.of_to_datastore) ; les lignes arrivent non modifiées. Les mêmes surcharges existent pour un datawindow et un datawindowchild (une liste déroulante remplie depuis une API). Rend le nombre de lignes, ou -4 quand la réponse n'est pas un 2xx, n'est pas du JSON — en mode démo un corps est coupé à 4096 caractères — ou n'a pas de tableau à cet endroit (le DataStore garde alors ses lignes), -5 pour un DataStore ou une URL invalides, et les codes de of_get ; of_get_last_error() dit pourquoi
of_response_text ( ) → stringLe corps de la dernière réponse synchrone de cet objet — un autre client ne l'écrase pas —, en texte : décodé selon le charset annoncé par le serveur (UTF-8, et les jeux historiques : windows-125x, iso-8859-x, shift_jis, gbk/gb2312, big5, euc-kr, koi8-r…), UTF-8 par défaut (un octet invalide devient un caractère de remplacement). Vide après un échec, et après of_download : ce corps-là est dans le fichier. Un corps de texte est plafonné à 64 Mo, 16 Mo dans un processus 32 bits : au-delà, -4 « body too large » — utilisez of_download
of_response_headers ( ) → stringTous les en-têtes de la dernière réponse synchrone, un par ligne
of_response_header (string as_name) → stringUn en-tête de la dernière réponse synchrone, par nom ; vide s'il n'a pas été envoyé ; un en-tête envoyé plusieurs fois rend toutes ses valeurs, jointes par , — Set-Cookie par un saut de ligne (une date de cookie contient une virgule)
of_json_value (string as_path) → stringUne valeur du corps JSON par son chemin — clés jointes par /, positions de tableau à partir de 1, la notation de n_pbt_json : "customer", "json/customer", "items/1/qty". Une chaîne revient décodée, un nombre ou true/false tels qu'écrits ("4152"), null vide, un objet ou un tableau en JSON ; vide si le chemin n'existe pas. Pour plus d'une valeur, chargez of_response_text dans un n_pbt_json. Le corps n'est analysé qu'une fois par réponse : lire dix valeurs coûte une seule analyse
of_url (string as_path, string as_names[], string as_values[]) → stringUne URL avec sa requête, chaque nom et chaque valeur encodés : avec q = "C# & R&D" et page = "2", of_url("/items", …) rend /items?q=C%23%20%26%20R%26D&page=2. Un chemin qui a déjà une requête reçoit les paires après un &, un fragment (#top) reste au bout, un nom vide est sauté. Rien n'est gardé : sûr entre deux requêtes asynchrones. La bibliothèque n'encode jamais la requête d'une URL que vous écrivez vous-même — un # la coupe, un & ajoute un paramètre, un + devient une espace
of_url_encode (string as_text) → stringUne valeur rendue sûre pour une URL (RFC 3986, UTF-8) : lettres, chiffres, -, ., _ et ~ restent, tout le reste devient %XX — "C# & co" rend C%23%20%26%20co. Vide pour un texte vide ou NULL
of_reset ( ) → longRetour aux valeurs par défaut : plus d'en-tête, plus d'URL de base, le délai par défaut, aucune tentative, cookies gardés ; le client est fermé — ses cookies partent, ses requêtes en vol sont annulées sans événement — et la dernière réponse est oubliée. L'authentification Windows est coupée (ib_windows_auth = false) et le certificat client oublié (is_client_certificate = "") ; ib_check_revocation revient à false, un jeton OAuth est oublié et is_last_request vidé Rend 0 une fois fait.

Exemples #

Lire une liste et la parcourir #

// 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

Télécharger un document #

// 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

Une API qui répond en erreur #

// 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

Une API d'intranet en authentification Windows #

// 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

Une recherche saisie par l'utilisateur #

// 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))

Une API protégée par OAuth2 #

// 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

Une liste dans un 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())

Asynchrone (sans figer l'interface) #

Pendant un appel synchrone, l'interface est figée jusqu'à la réponse : la fenêtre ne se redessine plus et ne répond plus aux clics. C'est sans conséquence pour un appel court ; pour tout appel qui peut durer — un serveur lent, un gros corps, un réseau incertain —, prenez l'API asynchrone : la requête part sur un fil de travail et sa réponse revient plus tard en événement. Synchrone et asynchrone partagent la même session : le cookie posé par une connexion synchrone sert aux appels asynchrones qui suivent, et inversement ; un appel synchrone n'attend jamais derrière les requêtes asynchrones du même objet.

Mise en place — rien à câbler : le composant livre la réponse tout seul dans ue_response. of_open / of_close ouvrent et ferment le client (facultatif : la première requête l'ouvre), of_clear_cookies oublie les cookies gardés d'une requête à l'autre. En interne, un pump du composant appelle of_process_events pour drainer les réponses — vous ne l'appelez jamais.

Envoyer — of_request_async(method, url, body), ou les raccourcis of_get_async, of_post_async, of_put_async, of_patch_async, of_delete_async, rendent aussitôt un id de requête — ou -5 tout de suite, sans aucun événement ensuite, pour une URL ou un en-tête invalide (of_get_last_error() dit pourquoi, par exemple « invalid URL: … »). Six requêtes au plus partent en même temps ; les suivantes attendent leur tour dans une file. of_download_async(url, path) télécharge vers un fichier et of_upload(url, field, path) téléverse un fichier en multipart/form-data — les deux avec progression. of_cancel(id) arrête une requête. Les tentatives (il_max_retries, il_retry_backoff_ms) rejouent sur 429/503/panne réseau.

Recevoir — ue_response(al_id, al_status) (lire le corps avec of_response_text(al_id) / of_status(al_id) DANS le gestionnaire), ue_failed(al_id, al_code, as_error), ue_progress(al_id, al_done, al_total). Un id ne vaut que pour l'objet qui a envoyé la requête. Une erreur d'exécution dans l'un de ces événements n'est pas avalée : elle remonte à l'événement SystemError de l'application comme toute erreur de script, et les réponses suivantes sont livrées quand même.

MéthodeRôle
of_open ( ) → longOuvre le client : sa session (cookies partagés par les appels synchrones et asynchrones) et son canal d'événements natif (pas de WebView, pas de CORS). Facultatif : la première requête l'ouvre. Rend l'identifiant du client (> 0), ou -2 s'il n'a pas pu être créé (of_get_last_error() dit pourquoi)
of_close ( ) → longFerme le client : ses cookies (la session) partent avec lui, et ses requêtes encore en vol sont annulées sans aucun événement — ni ue_response, ni ue_failed. La prochaine requête le rouvre. Fait pour vous par of_reset et à la destruction. La dernière réponse synchrone de l'objet est oubliée avec lui Rend 0 une fois fait.
of_process_events ( )Draine les réponses asynchrones et lève ue_response / ue_failed / ue_progress. Publique UNIQUEMENT parce que le pump interne du composant l'appelle sur la boucle PowerBuilder, toutes les quelques millisecondes tant qu'une requête est en vol — vous ne l'appelez jamais, et vous ne câblez ni récepteur ni timer. L'appeler vous-même est sans danger : elle ne vide que ce qui attend déjà
of_clear_cookies ( )Oublie les cookies gardés d'une requête à l'autre (une déconnexion). Garder ou non les cookies ensuite reste l'affaire de ib_keep_cookies
of_get_async (string as_url) → longGET asynchrone. Rend aussitôt un id de requête ; la réponse arrive dans ue_response(id, status). Rend -5 tout de suite — et aucun événement ne suit — sur une URL ou un en-tête invalide (of_get_last_error() dit pourquoi), -2 si le client ne peut pas s'ouvrir
of_post_async (string as_url, string as_body) → longPOST asynchrone avec un corps (JSON par défaut, construit avec n_pbt_json). Rend aussitôt un id de requête ; la réponse arrive dans ue_response. Rend -5 / -2 comme of_get_async
of_post_form_async (string as_url, string as_names[], string as_values[]) → longLe même formulaire que of_post_form, envoyé sans attendre. Rend aussitôt un id de requête ; la réponse arrive dans ue_response. Rend -5 / -2 comme of_get_async
of_put_async (string as_url, string as_body) → longPUT asynchrone avec un corps. Rend aussitôt un id de requête ; la réponse arrive dans ue_response. Rend -5 / -2 comme of_get_async
of_patch_async (string as_url, string as_body) → longPATCH asynchrone avec un corps. Rend aussitôt un id de requête ; la réponse arrive dans ue_response. Rend -5 / -2 comme of_get_async
of_delete_async (string as_url) → longDELETE asynchrone. Rend aussitôt un id de requête ; la réponse arrive dans ue_response. Rend -5 / -2 comme of_get_async
of_request_async (string as_method, string as_url, string as_body {, string as_headers}) → longEnvoi asynchrone avec la méthode HTTP de votre choix (of_get_async et ses frères l'appellent). Rend aussitôt un id de requête ; la réponse arrive dans ue_response, l'échec dans ue_failed. Six requêtes au plus en même temps, les suivantes attendent leur tour. Rend -2 si le client ne s'ouvre pas, -4 si la requête ne peut pas être mise en file, -5 tout de suite — sans événement — pour une URL ou un en-tête invalide. as_headers : des en-têtes pour cette requête seule, comme pour of_request
of_download_async (string as_url, string as_path) → longGET asynchrone directement dans un FICHIER, ue_progress en chemin. Rend un id de requête, -5 sur un chemin vide, -6 refusé sur-le-champ en mode démo, -2 si le client ne s'ouvre pas. Un chemin relatif est résolu à l'appel, comme pour of_download
of_upload (string as_url, string as_field, string as_path) → longEnvoie un FICHIER en multipart/form-data (champ de formulaire as_field), en asynchrone, avec ue_progress — toujours un POST, avec ce seul champ. Rend un id de requête, -5 sur un chemin vide, -6 refusé sur-le-champ en mode démo, -2 si le client ne s'ouvre pas. Un fichier relatif est cherché dans le dossier courant, puis dans celui de l'application au chargement de la bibliothèque, puis à côté de l'exe. Le Content-Type posé par of_set_header est ignoré : multipart/form-data est posé par la bibliothèque
of_cancel (long al_id) → longDemande à une requête de s'arrêter ; elle se termine par ue_failed(id, -4, "cancelled"), tout de suite si elle attendait encore son tour. Rend 0, -5 si l'id est inconnu ou appartient à un autre objet — rien n'est annulé —, -4 s'il est trop tard : la réponse est arrivée et vient dans ue_response
of_status (long al_id) → longLe statut HTTP d'une réponse asynchrone, par id de requête — à lire DANS ue_response. Rend le statut (200, 404…) ; 0 tant que la requête court, pour une requête échouée (dans ue_failed), pour un id inconnu, déjà oublié ou d'un autre objet
of_response_text (long al_id) → stringLe corps d'une réponse asynchrone, par id de requête — à lire DANS ue_response : la requête est oubliée au retour de l'événement ; vide tant qu'elle court ; vide aussi pour l'id d'un autre objet
of_response_headers (long al_id) → stringTous les en-têtes d'une réponse asynchrone, un par ligne
of_response_header (long al_id, string as_name) → stringUn en-tête d'une réponse asynchrone, par nom (la casse est indifférente) ; les valeurs répétées jointes comme of_response_header(as_name)
of_json_value (long al_id, string as_path) → stringUne valeur du corps JSON d'une réponse asynchrone, par chemin — la même notation que of_json_value(as_path). À lire dans ue_response ; lue avant la réponse (dans ue_progress), elle rend vide, et la réponse est quand même lue quand elle arrive
ÉvénementRôle
ue_response (long al_id, long al_status)Une requête asynchrone a abouti : al_id est l'id rendu à l'envoi, al_status le statut HTTP. Lire le corps avec of_response_text(al_id) DANS le gestionnaire : la requête est oubliée à la sortie de l'événement. Il s'écrit sur l'objet client posé dans la fenêtre, ou dans un descendant (voir « Où écrire les événements »). Fermer depuis lui la fenêtre qui porte le client est permis : les réponses qui attendaient encore ne sont pas livrées
ue_failed (long al_id, long al_code, string as_error)Une requête asynchrone a échoué. al_code dit la nature : -4 la requête a échoué (pas de réseau, délai dépassé, TLS, corps trop gros, ou annulée par of_cancel — as_error vaut alors cancelled), -5 fichier invalide ou certificat client introuvable, -6 refusé en mode démo (une requête de plage Range) ; as_error dit pourquoi. of_status(al_id) y rend 0
ue_progress (long al_id, long al_done, long al_total)Avancement d'une requête asynchrone : le corps de la réponse au fil de son arrivée (toute requête, un gros JSON compris), le fichier d'un téléchargement (of_download_async) ou d'un envoi (of_upload). al_done octets sur al_total (0 quand le serveur n'a pas annoncé la taille) ; le dernier, juste avant ue_response, dit al_done = al_total. Au-delà de 2 Go, les octets ne tiennent plus dans un long

Où écrire les événements #

Un objet créé par create n_pbt_restclient sert très bien le synchrone — c'est ce que fait le démarrage rapide —, mais ses événements n'ont aucun script : PowerBuilder ne permet pas d'écrire ue_response sur un objet créé dans un script. Pour l'asynchrone, deux voies :

Fermer la fenêtre qui porte le client depuis l'un de ses événements est permis : les réponses qui attendaient encore ne sont pas livrées.

// 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)

Bonnes pratiques #

← Référence des composants · Sommaire du guide