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 visuel | n_pbt_restclient |
| Sert à | Appeler une API REST depuis une application PowerBuilder : lire, créer, mettre à jour, télécharger |
| Principe | Une 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épendance | WinHTTP, dans la DLL de la bibliothèque — aucune page, aucun runtime supplémentaire |
Démarrage rapide #
n_pbt_restclient lnv_rest
n_pbt_json lnv_j
long ll_status
lnv_rest = create n_pbt_restclient
lnv_rest.is_base_url = "https://api.example.com"
lnv_rest.of_set_bearer(ls_token)
// Read : the status comes back, the body waits in the object
ll_status = lnv_rest.of_get("/orders/4152")
if ll_status = 200 then ls_customer = lnv_rest.of_json_value("customer")
// Create : the body is built with n_pbt_json, never by hand
lnv_j.of_set_number("order", 4152)
lnv_j.of_set_string("status", "shipped")
ll_status = lnv_rest.of_post("/shipments", lnv_j.of_text())
if ll_status < 0 then MessageBox("API", lnv_rest.is_last_error)
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 invalide, -4 échec (pas de réseau, nom inconnu, délai dépassé, TLS ; is_last_error dit lequel), -6 refusé en mode démo.
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é | Type | Défaut | Rôle |
|---|---|---|---|
is_base_url | string | "" | 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_ms | long | 30000 | Durée maximale d'une requête, connexion et réponse comprises. Au-delà : -4 et « timed out » |
is_last_error | string | "" | Pourquoi la dernière requête a rendu un code négatif. Vide après une requête arrivée au serveur — un 404 n'est pas une erreur |
il_status | long | 0 | Le statut HTTP de la dernière requête, ou son code négatif ; la valeur que la requête a rendue |
Constantes : METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE pour of_request.
Méthodes #
| Méthode | Rôle |
|---|---|
of_set_header (string as_name, string as_value) | Un en-tête envoyé avec chaque requête désormais ; un nom déjà posé est remplacé. Content-Type est ajouté seul quand un corps part sans lui (JSON, UTF-8) |
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) | Authorization: Bearer sur chaque requête ; un jeton vide le retire |
of_set_basic (string as_user, string as_password) | Authentification Basic ; la DLL encode elle-même le base64 — PowerBuilder 10 n'en a pas |
of_get (string as_url) → long | GET. Rend le statut HTTP, ou -5 URL invalide, -4 échec, -6 refusé en démo |
of_post (string as_url, string as_body) → long | POST avec un corps (JSON par défaut, construit avec n_pbt_json). Rend le statut HTTP, ou -5 / -4 / -6 |
of_put (string as_url, string as_body) → long | PUT avec un corps. Rend le statut HTTP, ou -5 / -4 / -6 |
of_patch (string as_url, string as_body) → long | PATCH avec un corps. Rend le statut HTTP, ou -5 / -4 / -6 |
of_delete (string as_url) → long | DELETE. Rend le statut HTTP, ou -5 / -4 / -6 |
of_request (string as_method, string as_url, string as_body) → long | N'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 |
of_download (string as_url, string as_path) → long | GET 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 |
of_response_text ( ) → string | Le corps de la dernière réponse, en texte (UTF-8 décodé) |
of_response_headers ( ) → string | Tous les en-têtes de la dernière réponse, un par ligne |
of_response_header (string as_name) → string | Un en-tête de la dernière réponse, par nom ; vide s'il n'a pas été envoyé |
of_json_value (string as_key) → string | Une valeur du corps JSON par sa clé, la première occurrence à n'importe quelle profondeur. Pour plus d'une valeur, lisez of_response_text et n_pbt_utils.of_json_get_* |
of_reset ( ) | Retour aux valeurs par défaut : plus d'en-tête, plus d'URL de base, le délai par défaut, la dernière réponse oubliée |
Exemples #
Lire une liste et la parcourir #
string ls_body
if lnv_rest.of_get("/orders?status=open") = 200 then
ls_body = lnv_rest.of_response_text()
// One value : of_json_value. The whole list : n_pbt_utils.of_json_get_payload and
// of_json_get_str on each object, or your own parser on ls_body.
end if
Télécharger un document #
long ll_status
ll_status = lnv_rest.of_download("/orders/4152/invoice.pdf", "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.is_last_error)
end if
Une API qui répond en erreur #
choose case lnv_rest.of_post("/shipments", ls_body)
case 200, 201
// created
case 401
MessageBox("API", "Token expired : " + lnv_rest.of_json_value("message"))
case is < 0
MessageBox("Network", lnv_rest.is_last_error)
end choose
Asynchrone (sans gel de l'interface) #
Les appels synchrones ci-dessus bloquent le script (l'interface reste vivante mais la ligne attend). L'API asynchrone ne bloque même plus le script : la requête part sur un fil de travail et sa réponse revient plus tard en événement.
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 : le premier appel async 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. 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 en vol. 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, as_error), ue_progress(al_id, al_done, al_total).
| Méthode | Rôle |
|---|---|
of_open ( ) → long | Ouvre le client asynchrone, un canal d'événements natif (pas de WebView, pas de CORS). Facultatif : le premier appel async l'ouvre. Rend l'identifiant du client (> 0), ou un code négatif |
of_close ( ) | Ferme le client asynchrone : ses cookies (la session) partent avec lui. Le prochain appel async le rouvre. Fait pour vous par of_reset et à la destruction |
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). Le réglage ib_keep_cookies ne change pas |
of_get_async (string as_url) → long | GET asynchrone. Rend aussitôt un id de requête ; la réponse arrive dans ue_response(id, status). Rend -5 sur une URL invalide, -2 si le client ne peut pas s'ouvrir |
of_post_async (string as_url, string as_body) → long | POST 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_put_async (string as_url, string as_body) → long | PUT 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) → long | PATCH 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) → long | DELETE 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) → long | Envoi 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. Rend -2 si le client ne s'ouvre pas |
of_download_async (string as_url, string as_path) → long | GET asynchrone directement dans un FICHIER, ue_progress en chemin. Rend un id de requête, -5 sur un chemin vide, -2 si le client ne s'ouvre pas |
of_upload (string as_url, string as_field, string as_path) → long | Envoie un FICHIER en multipart/form-data (champ de formulaire as_field), en asynchrone, avec ue_progress. Rend un id de requête, -5 sur un chemin vide, -2 si le client ne s'ouvre pas, -6 en mode démo |
of_cancel (long al_id) → long | Demande à une requête en vol de s'arrêter ; elle se termine par ue_failed(id, "cancelled"). Rend 0, ou -1 si l'id est inconnu |
of_status (long al_id) → long | Le statut HTTP d'une réponse asynchrone, par id de requête - à lire DANS ue_response. Rend le statut (200, 404...), 0 si la requête est inconnue ou déjà oubliée |
| Événement | Rô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 |
ue_failed (long al_id, string as_error) | Une requête asynchrone a échoué : pas de réseau, délai dépassé, TLS, ou annulée par of_cancel (as_error vaut alors cancelled) |
ue_progress (long al_id, long al_done, long al_total) | Avancement d'un téléchargement (of_download_async) ou d'un envoi (of_upload) asynchrone : al_done octets sur al_total |
// Async : the UI never freezes, and there is nothing to wire.
ll_id = uo_rest.of_get_async("https://api.example.com/orders/4152")
// The answer arrives on its own in the ue_response event of uo_rest :
IF al_status >= 200 AND al_status < 300 THEN
ls_body = uo_rest.of_response_text(al_id)
END IF
Bonnes pratiques #
- Un client par API, avec son
is_base_urlet ses en-têtes posés une fois : le code des appels ne porte plus que le chemin et le corps. - Le corps se construit avec
n_pbt_json, jamais par concaténation : un guillemet dans un nom de client ne casse rien. - Testez le statut, puis le code négatif :
>= 200 et < 300c'est bon,>= 400le serveur a répondu non,< 0la requête n'est pas partie —is_last_errordit pourquoi. - Le délai : 30 secondes par défaut ; un rapport lourd en mérite plus, un contrôle de présence moins.