restclient — n_pbt_restclient #
← Riferimento dei componenti · Sommario della guida
Un client HTTPS per PowerBuilder, versione 10 compresa: GET, POST, PUT, PATCH, DELETE, intestazioni, un corpo JSON, il download di un file. Nativo — TLS, proxy e decompressione del sistema, e nessun muro CORS, perché la richiesta non parte da una pagina.
▶ Vederlo dal vivo — Applicazione dimostrativa, riquadro REST client: le risposte, il codice che le ottiene e questa pagina, fianco a fianco (connessione Internet richiesta).
In breve #
| Oggetto non visuale | n_pbt_restclient |
| Serve a | Chiamare un'API REST da un'applicazione PowerBuilder: leggere, creare, aggiornare, scaricare |
| Principio | Una richiesta è una chiamata che restituisce lo stato HTTP; la risposta attende nell'oggetto, letta con of_response_text o of_json_value |
| Dipendenza | WinHTTP, nella DLL della libreria — nessuna pagina, nessun runtime aggiuntivo |
Avvio rapido #
// Local variables
n_pbt_restclient lnv_rest
n_pbt_json lnv_j
long ll_status
// One client for the API : its address and the token it sends
lnv_rest = create n_pbt_restclient
lnv_rest.is_base_url = "https://api.example.com"
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.is_last_error)
// The client is no longer needed
destroy lnv_rest
Ogni metodo HTTP restituisce lo stato scelto dal server — un 404 è una risposta, non un errore — oppure un codice negativo quando la richiesta non è andata a buon fine: -5 URL o intestazione non validi, certificato client non trovato, -4 fallimento (nessuna rete, nome sconosciuto, tempo scaduto, TLS, un corpo di testo oltre 64 MB — 16 MB in un processo a 32 bit; is_last_error dice quale), -6 rifiutato in modalità demo.
Perché nativo, e non la pagina #
Un fetch() lanciato da una pagina della libreria è soggetto a CORS: ogni API che non invia Access-Control-Allow-Origin gli è chiusa, e le API aziendali non lo inviano. La richiesta parte quindi dalla DLL, tramite WinHTTP: il TLS del sistema con i suoi certificati, il proxy configurato, gzip. Ciò che perdi: nulla; ciò che guadagni: tutte le API.
Proprietà #
| Proprietà | Tipo | Predefinito | Ruolo |
|---|---|---|---|
is_base_url | string | "" | Messo davanti a un URL relativo: is_base_url = "https://api.example.com" poi of_get("/orders/4152"). Un URL assoluto è usato così com'è |
il_timeout_ms | long | 30000 | Quanto può durare ogni fase di una richiesta: risoluzione del nome, connessione, invio, poi attesa di ogni blocco della risposta. Un grande download che avanza non viene mai interrotto; un server muto per quel tempo sì: -4 e «timed out». 0 o meno vale il predefinito, 30 s: non esiste «nessun limite di tempo» |
is_last_error | string | "" | Perché l'ultima richiesta sincrona — o una chiamata asincrona rifiutata subito — ha restituito un codice negativo. Vuoto dopo una richiesta arrivata al server — un 404 non è un errore. Un fallimento asincrono dice il motivo in ue_failed |
il_status | long | 0 | Lo stato HTTP dell'ultima richiesta sincrona, o il suo codice negativo; il valore restituito dalla richiesta. Una risposta asincrona ha il suo: of_status(id) |
ib_keep_cookies | boolean | true | Conserva i cookie da una richiesta all'altra — sincrone e asincrone, che condividono una stessa sessione: un'API che imposta un cookie al login vi mantiene connessi. A false, nessun cookie viene più inviato né conservato, e quelli già conservati vengono dimenticati; applicato subito |
il_max_retries | long | 0 | Quante volte una richiesta asincrona viene ripetuta su 429, 503 o un guasto di rete. Un POST la cui risposta si è persa non viene mai ripetuto: potrebbe essere stato elaborato. Un fallimento che si ripeterebbe identico non viene mai ripetuto: troppi reindirizzamenti, reindirizzamento non valido, reindirizzamento HTTPS → HTTP rifiutato, errore TLS, file impossibile da scrivere |
il_retry_backoff_ms | long | 500 | L'attesa prima del primo nuovo tentativo, in millisecondi, raddoppiata a ogni prova e limitata a 60 s. Un'intestazione Retry-After del server (secondi o data) ha la precedenza |
ib_windows_auth | boolean | false | Autenticazione integrata di Windows (Negotiate / NTLM): quando un server la chiede — un'API IIS dell'intranet —, rispondono le credenziali della sessione Windows, senza digitare nulla. Disattivata per impostazione predefinita: senza di essa non partono mai, verso nessun server. Letta a ogni richiesta |
is_client_certificate | string | "" | Un certificato client (TLS reciproco): l'impronta SHA-1 di un certificato dell'archivio personale — quello dell'utente, poi quello del computer —, come la mostra il gestore dei certificati; spazi e due punti ignorati. Vuoto = nessuno. Un'impronta non trovata fa restituire -5 a ogni richiesta («client certificate not found»). Letta a ogni richiesta. Non esiste un'opzione per ignorare un errore di certificato del server |
ipo_owner | powerobject | null | L'oggetto visuale per cui lavora questo client: la licenza si verifica sulla sua classe. Necessario solo nell'applicazione dimostrativa; una chiave di sviluppo o di runtime sblocca il client senza di esso. Non sbloccato, il client è in modalità demo — corpi tagliati a 4096 caratteri, né download né upload |
Costanti: METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE per of_request.
Metodi #
| Metodo | Ruolo |
|---|---|
of_set_header (string as_name, string as_value) → long | Un'intestazione inviata con ogni richiesta d'ora in poi; un nome già impostato viene sostituito, un valore vuoto rimuove l'intestazione. Content-Type viene aggiunto da solo quando un corpo parte senza (JSON, UTF-8); per un upload, quello impostato qui è ignorato (multipart/form-data lo imposta la libreria). Nessuna di queste intestazioni segue un reindirizzamento verso un altro host. Restituisce 0, oppure -5 — nulla cambia — per un nome vuoto, un nome che contiene uno spazio, due punti o un carattere di controllo, o un valore che contiene un a capo |
of_remove_header (string as_name) | Dimentica un'intestazione, per nome (maiuscole indifferenti) |
of_clear_headers ( ) | Dimentica tutte le intestazioni, autenticazione compresa |
of_set_bearer (string as_token) → long | Authorization: Bearer su ogni richiesta. Una sola autenticazione alla volta: sostituisce un'autenticazione Basic; un token vuoto rimuove l'autenticazione. Restituisce 0, oppure -5 per un token che contiene un a capo — non resta allora alcuna autenticazione, mai una a metà |
of_set_basic (string as_user, string as_password) → long | Autenticazione Basic; la DLL codifica da sé il base64 — PowerBuilder 10 non ce l'ha. Sostituisce un token Bearer; un utente vuoto rimuove l'autenticazione. Restituisce 0, oppure -5 per un utente o una password che contiene un a capo — non resta allora alcuna autenticazione |
of_get (string as_url) → long | GET. Restituisce lo stato HTTP, oppure -5 URL o intestazione non validi, certificato client non trovato, -4 fallimento, -6 rifiutato in demo. Il frammento #… di un URL non viene mai inviato al server |
of_post (string as_url, string as_body) → long | POST con un corpo (JSON per impostazione predefinita, costruito con n_pbt_json). Restituisce lo stato HTTP, o -5 / -4 / -6 |
of_put (string as_url, string as_body) → long | PUT con un corpo. Restituisce lo stato HTTP, o -5 / -4 / -6 |
of_patch (string as_url, string as_body) → long | PATCH con un corpo. Restituisce lo stato HTTP, o -5 / -4 / -6 |
of_delete (string as_url) → long | DELETE. Restituisce lo stato HTTP, o -5 / -4 / -6 |
of_request (string as_method, string as_url, string as_body) → long | Qualsiasi metodo (METHOD_*, o il tuo), qualsiasi corpo inviato in UTF-8: ciò che le cinque scorciatoie chiamano. Restituisce lo stato HTTP, o -5 / -4 / -6 |
of_download (string as_url, string as_path) → long | GET direttamente in un file, byte intatti, di qualsiasi dimensione. Restituisce lo stato HTTP, o -5 URL o percorso vuoto, -4 fallimento, -6 rifiutato in demo. Un percorso relativo è risolto alla chiamata nella cartella corrente dell'applicazione al caricamento della libreria — mai dove un DirList o una finestra di file l'ha spostata da allora |
of_response_text ( ) → string | Il corpo dell'ultima risposta sincrona di questo oggetto — un altro client non lo sovrascrive —, come testo: decodificato secondo il charset annunciato dal server (UTF-8, e i set storici: windows-125x, iso-8859-x, shift_jis, gbk/gb2312, big5, euc-kr, koi8-r…), UTF-8 per impostazione predefinita (un byte non valido diventa un carattere di sostituzione). Vuoto dopo un fallimento, e dopo of_download: quel corpo è nel file. Un corpo di testo è limitato a 64 MB, 16 MB in un processo a 32 bit: oltre, -4 «body too large» — usa of_download |
of_response_headers ( ) → string | Tutte le intestazioni dell'ultima risposta sincrona, una per riga |
of_response_header (string as_name) → string | Un'intestazione dell'ultima risposta sincrona, per nome; vuota se non è stata inviata |
of_json_value (string as_path) → string | Un valore del corpo JSON tramite il suo percorso — chiavi unite da /, posizioni di array da 1, la notazione di n_pbt_json: "customer", "json/customer", "items/1/qty". Una stringa torna decodificata, un numero o true/false come scritti ("4152"), null vuoto, un oggetto o un array come JSON; vuoto se il percorso non esiste. Per più di un valore, caricate of_response_text in un n_pbt_json. Il corpo viene analizzato una sola volta per risposta: leggere dieci valori costa una sola analisi |
of_reset ( ) | Ritorno ai valori predefiniti: nessuna intestazione, nessun URL di base, il tempo predefinito, nessun nuovo tentativo, cookie conservati; il client viene chiuso — i suoi cookie se ne vanno, le sue richieste in corso vengono annullate senza alcun evento — e l'ultima risposta viene dimenticata. L'autenticazione Windows viene disattivata (ib_windows_auth = false) e il certificato client dimenticato (is_client_certificate = "") |
Esempi #
Leggere un elenco e scorrerlo #
// 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
Scaricare un documento #
// 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.is_last_error)
end if
Un'API che risponde con un errore #
// The status decides : 200 or 201 = created, 401 = the token expired,
// below 0 = the network failed
choose case lnv_rest.of_post(/*url*/ "/shipments", /*body*/ ls_body)
case 200, 201
case 401
MessageBox("API", "Token expired : " + lnv_rest.of_json_value(/*path*/ "message"))
case is < 0
MessageBox("Network", lnv_rest.is_last_error)
end choose
Un'API dell'intranet con autenticazione 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.is_last_error)
end if
// The client is no longer needed
destroy lnv_rest
Asincrono (senza bloccare l'interfaccia) #
Durante una chiamata sincrona l'interfaccia è bloccata fino alla risposta: la finestra non si ridisegna più e non risponde più ai clic. Per una chiamata breve non ha conseguenze; per ogni chiamata che può durare — un server lento, un corpo grande, una rete incerta —, usa l'API asincrona: la richiesta parte su un thread di lavoro e la sua risposta torna più tardi come evento. Sincrono e asincrono condividono la stessa sessione: il cookie impostato da un login sincrono serve alle chiamate asincrone che seguono, e viceversa; una chiamata sincrona non attende mai dietro le richieste asincrone dello stesso oggetto.
Preparazione — niente da collegare: il componente consegna da solo la risposta in ue_response. of_open / of_close aprono e chiudono il client (facoltativo: la prima richiesta lo apre), of_clear_cookies dimentica i cookie conservati tra una richiesta e l'altra. Internamente, una pompa del componente chiama of_process_events per drenare le risposte — non la chiami mai.
Inviare — of_request_async(method, url, body), o le scorciatoie of_get_async, of_post_async, of_put_async, of_patch_async, of_delete_async, restituiscono subito un id di richiesta — oppure -5 subito, senza alcun evento dopo, per un URL o un'intestazione non validi (is_last_error dice perché, per esempio «invalid URL: …»). Al massimo sei richieste partono insieme; le successive attendono il loro turno in una coda. of_download_async(url, path) scarica in un file e of_upload(url, field, path) invia un file in multipart/form-data — entrambi con avanzamento. of_cancel(id) ferma una richiesta. I tentativi (il_max_retries, il_retry_backoff_ms) ripetono su 429/503/guasto di rete.
Ricevere — ue_response(al_id, al_status) (leggere il corpo con of_response_text(al_id) / of_status(al_id) DENTRO il gestore), ue_failed(al_id, al_code, as_error), ue_progress(al_id, al_done, al_total). Un id vale solo per l'oggetto che ha inviato la richiesta. Un errore di esecuzione in uno di questi eventi non viene inghiottito: risale all'evento SystemError dell'applicazione come ogni errore di script, e le risposte successive vengono consegnate comunque.
| Metodo | Ruolo |
|---|---|
of_open ( ) → long | Apre il client: la sua sessione (cookie condivisi dalle chiamate sincrone e asincrone) e il suo canale di eventi nativo (nessun WebView, nessun CORS). Facoltativo: la prima richiesta lo apre. Restituisce l'id del client (> 0), o -2 se non è stato possibile crearlo (is_last_error dice perché) |
of_close ( ) | Chiude il client: i suoi cookie (la sessione) se ne vanno con lui, e le sue richieste ancora in corso vengono annullate senza alcun evento — né ue_response, né ue_failed. La richiesta successiva lo riapre. Fatto per voi da of_reset e alla distruzione. L'ultima risposta sincrona dell'oggetto viene dimenticata con esso |
of_process_events ( ) | Distribuisce le risposte asincrone e solleva ue_response / ue_failed / ue_progress. Pubblica SOLO perché il pump interno del componente la chiama sul ciclo PowerBuilder, ogni pochi millisecondi finché una richiesta è in volo — non la chiami mai, e non colleghi né ricevitore né timer. Chiamarla tu stesso è innocuo: svuota solo ciò che già attende |
of_clear_cookies ( ) | Dimentica i cookie conservati da una richiesta all'altra (un logout). Se conservare o no i cookie in seguito resta compito di ib_keep_cookies |
of_get_async (string as_url) → long | GET asincrono. Restituisce subito un id di richiesta; la risposta arriva in ue_response(id, status). Restituisce subito -5 — e nessun evento segue — per un URL o un'intestazione non validi (is_last_error dice perché), -2 se il client non può aprirsi |
of_post_async (string as_url, string as_body) → long | POST asincrono con un corpo (JSON per impostazione predefinita, costruito con n_pbt_json). Restituisce subito un id di richiesta; la risposta arriva in ue_response. Restituisce -5 / -2 come of_get_async |
of_put_async (string as_url, string as_body) → long | PUT asincrono con un corpo. Restituisce subito un id di richiesta; la risposta arriva in ue_response. Restituisce -5 / -2 come of_get_async |
of_patch_async (string as_url, string as_body) → long | PATCH asincrono con un corpo. Restituisce subito un id di richiesta; la risposta arriva in ue_response. Restituisce -5 / -2 come of_get_async |
of_delete_async (string as_url) → long | DELETE asincrono. Restituisce subito un id di richiesta; la risposta arriva in ue_response. Restituisce -5 / -2 come of_get_async |
of_request_async (string as_method, string as_url, string as_body) → long | Richiesta asincrona con il metodo HTTP di vostra scelta (of_get_async e i suoi fratelli la chiamano). Restituisce subito un id di richiesta; la risposta arriva in ue_response, il fallimento in ue_failed. Al massimo sei richieste insieme, le successive attendono il loro turno. Restituisce -2 se il client non si apre, -4 se la richiesta non può essere messa in coda, -5 subito — senza evento — per un URL o un'intestazione non validi |
of_download_async (string as_url, string as_path) → long | GET asincrono direttamente in un FILE, con ue_progress lungo il percorso. Restituisce un id di richiesta, -5 su un percorso vuoto, -6 rifiutato subito in modalità demo, -2 se il client non si apre. Un percorso relativo è risolto alla chiamata, come per of_download |
of_upload (string as_url, string as_field, string as_path) → long | Invia un FILE in multipart/form-data (campo di modulo as_field), in asincrono, con ue_progress — sempre un POST, con quel solo campo. Restituisce un id di richiesta, -5 su un percorso vuoto, -6 rifiutato subito in modalità demo, -2 se il client non si apre. Un file relativo è cercato nella cartella corrente, poi in quella dell'applicazione al caricamento della libreria, poi accanto all'exe. Il Content-Type impostato da of_set_header è ignorato: multipart/form-data lo imposta la libreria |
of_cancel (long al_id) → long | Chiede a una richiesta di fermarsi; termina con ue_failed(id, -4, "cancelled"), subito se attendeva ancora il suo turno. Restituisce 0, -5 se l'id è sconosciuto o appartiene a un altro oggetto — nulla viene annullato —, -4 se è troppo tardi: la risposta è arrivata e viene in ue_response |
of_status (long al_id) → long | Lo stato HTTP di una risposta asincrona, per id di richiesta — da leggere DENTRO ue_response. Restituisce lo stato (200, 404…); 0 finché la richiesta è in corso, per una richiesta fallita (in ue_failed), per un id sconosciuto, già dimenticato o di un altro oggetto |
of_response_text (long al_id) → string | Il corpo di una risposta asincrona, per id di richiesta — da leggere IN ue_response: la richiesta viene dimenticata al ritorno dall'evento; vuoto finché è in corso; vuoto anche per l'id di un altro oggetto |
of_response_headers (long al_id) → string | Tutte le intestazioni di una risposta asincrona, una per riga |
of_response_header (long al_id, string as_name) → string | Un'intestazione di una risposta asincrona, per nome (maiuscole indifferenti) |
of_json_value (long al_id, string as_path) → string | Un valore del corpo JSON di una risposta asincrona, per percorso — la stessa notazione di of_json_value(as_path) |
| Evento | Ruolo |
|---|---|
ue_response (long al_id, long al_status) | Una richiesta asincrona è terminata: al_id è l'id restituito all'invio, al_status lo stato HTTP. Leggere il corpo con of_response_text(al_id) DENTRO il gestore: la richiesta è dimenticata all'uscita dell'evento |
ue_failed (long al_id, long al_code, string as_error) | Una richiesta asincrona è fallita. al_code dice la natura: -4 la richiesta è fallita (nessuna rete, tempo scaduto, TLS, corpo troppo grande, o annullata da of_cancel — as_error vale allora cancelled), -5 file non valido o certificato client non trovato, -6 rifiutato in modalità demo (una richiesta Range); as_error dice perché. of_status(al_id) vi restituisce 0 |
ue_progress (long al_id, long al_done, long al_total) | Avanzamento di un download (of_download_async) o di un invio (of_upload) asincrono: al_done byte su al_total (0 quando il server non ha annunciato la dimensione). Oltre 2 GB i byte non stanno più in un long |
// Async : the UI never freezes, and there is nothing to wire.
ll_id = uo_rest.of_get_async(/*url*/ "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(/*id*/ al_id)
END IF
Buone pratiche #
- Un client per API, con il suo
is_base_urle le sue intestazioni impostati una volta: il codice delle chiamate porta solo il percorso e il corpo. - Il corpo si costruisce con
n_pbt_json, mai per concatenazione: una virgoletta nel nome di un cliente non rompe nulla. - Verifica lo stato, poi il codice negativo:
>= 200 e < 300va bene,>= 400il server ha detto no,< 0la richiesta non è partita —is_last_errordice perché. - Il tempo limite: 30 secondi per impostazione predefinita; un report pesante ne merita di più, un controllo di presenza di meno.
- Ciò che può durare va in asincrono: una chiamata sincrona blocca l'interfaccia fino alla risposta.
- Un reindirizzamento conserva la tua richiesta: un 301 o un 302 riscrive in GET solo un POST — un PUT, un PATCH o un DELETE viene rifatto così com'è, corpo compreso; un 303 riparte in GET senza corpo. Una
Locationrelativa (?page=2,../) è risolta come farebbe un browser. - Un reindirizzamento verso un altro host non porta nulla di tuo: nessuna delle intestazioni impostate da
of_set_header(token, chiave API) lo segue — resta soloContent-Type. Un reindirizzamento HTTPS → HTTP è rifiutato. - Verifica il ritorno di
of_set_header,of_set_bearereof_set_basicquando il valore viene da un input o da un file: un a capo vi è rifiutato (-5) e nulla viene impostato. Un'intestazione rifiutata non parte mai a metà. - Le credenziali Windows partono solo se lo chiedi:
ib_windows_auth = trueper un'API dell'intranet con autenticazione Windows, mai su un client che parla con Internet. - Un corpo di testo è limitato a 64 MB, 16 MB in un'applicazione a 32 bit: un export voluminoso si legge con
of_download/of_download_async, che scrivono un file senza limiti.