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 #
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
Ogni metodo HTTP restituisce lo stato scelto dal server — un 404 è una risposta, non un errore — o un codice negativo quando la richiesta non è andata a buon fine: -5 URL non valido, -4 fallimento (niente rete, nome sconosciuto, timeout, TLS; 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 | Durata massima di una richiesta, connessione e risposta comprese. Oltre: -4 e «timed out» |
is_last_error | string | "" | Perché l'ultima richiesta ha restituito un codice negativo. Vuoto dopo una richiesta arrivata al server — un 404 non è un errore |
il_status | long | 0 | Lo stato HTTP dell'ultima richiesta, o il suo codice negativo; il valore restituito dalla richiesta |
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) | Un'intestazione inviata con ogni richiesta d'ora in poi; un nome già impostato è sostituito. Content-Type è aggiunto da solo quando un corpo parte senza (JSON, UTF-8) |
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) | Authorization: Bearer su ogni richiesta; un token vuoto lo rimuove |
of_set_basic (string as_user, string as_password) | Autenticazione Basic; la DLL codifica da sola il base64 — PowerBuilder 10 non ce l'ha |
of_get (string as_url) → long | GET. Restituisce lo stato HTTP, o -5 URL non valido, -4 fallimento, -6 rifiutato in demo |
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 |
of_response_text ( ) → string | Il corpo dell'ultima risposta, come testo (UTF-8 decodificato) |
of_response_headers ( ) → string | Tutte le intestazioni dell'ultima risposta, una per riga |
of_response_header (string as_name) → string | Un'intestazione dell'ultima risposta, per nome; vuota se non è stata inviata |
of_json_value (string as_key) → string | Un valore del corpo JSON per la sua chiave, la prima occorrenza a qualsiasi profondità. Per più valori, leggi of_response_text e n_pbt_utils.of_json_get_* |
of_reset ( ) | Ritorno ai valori predefiniti: nessuna intestazione, nessun URL di base, il timeout predefinito, l'ultima risposta dimenticata |
Esempi #
Leggere un elenco e scorrerlo #
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
Scaricare un documento #
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
Un'API che risponde con un errore #
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
Asincrono (nessun blocco dell'interfaccia) #
Le chiamate sincrone qui sopra bloccano lo script (l'interfaccia resta viva ma la riga attende). L'API asincrona non blocca nemmeno lo script: la richiesta parte su un thread di lavoro e la risposta torna più tardi come evento.
Impostazione — niente da collegare: il componente consegna la risposta da solo in ue_response. of_open / of_close aprono e chiudono il client (facoltativo: la prima chiamata async lo apre), of_clear_cookies dimentica i cookie tenuti tra le richieste. Internamente il pump del componente chiama of_process_events per distribuire le risposte — non lo 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. of_download_async(url, path) scarica in un file e of_upload(url, field, path) carica un file come multipart/form-data — entrambi con progresso. of_cancel(id) ferma una richiesta in volo. I tentativi (il_max_retries, il_retry_backoff_ms) rigiocano su 429/503/errore 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, as_error), ue_progress(al_id, al_done, al_total).
| Metodo | Ruolo |
|---|---|
of_open ( ) → long | Apre il client asincrono, un canale di eventi nativo (niente WebView, niente CORS). Facoltativo: la prima chiamata async lo apre. Restituisce l'id del client (> 0), o un codice negativo |
of_close ( ) | Chiude il client asincrono: i suoi cookie (la sessione) se ne vanno con lui. La prossima chiamata async lo riapre. Fatto per te da of_reset e alla distruzione |
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 tenuti tra le richieste (un logout). L'impostazione ib_keep_cookies non cambia |
of_get_async (string as_url) → long | GET asincrono. Restituisce subito un id di richiesta; la risposta arriva in ue_response(id, status). Restituisce -5 su URL non valido, -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, un errore in ue_failed. Restituisce -2 se il client non si apre |
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, -2 se il client non si apre |
of_upload (string as_url, string as_field, string as_path) → long | Invia un FILE come multipart/form-data (campo del modulo as_field), in modo asincrono, con ue_progress. Restituisce un id di richiesta, -5 su un percorso vuoto, -2 se il client non si apre, -6 in modalità demo |
of_cancel (long al_id) → long | Chiede a una richiesta in corso di fermarsi; termina con ue_failed(id, "cancelled"). Restituisce 0, oppure -1 se l'id è sconosciuto |
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 se la richiesta è sconosciuta o già dimenticata |
| 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, string as_error) | Una richiesta asincrona è fallita: nessuna rete, timeout, TLS, o annullata da of_cancel (as_error vale allora cancelled) |
ue_progress (long al_id, long al_done, long al_total) | Avanzamento di un download (of_download_async) o di un upload (of_upload) asincrono: al_done byte su 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
Buone pratiche #
- Un client per API, con il suo
is_base_urle le sue intestazioni impostate 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 in un nome di cliente non rompe nulla. - Controlla lo stato, poi il codice negativo:
>= 200 e < 300va bene,>= 400il server ha detto no,< 0la richiesta non è partita —is_last_errordice perché. - Il timeout: 30 secondi per impostazione predefinita; un report pesante ne merita di più, un controllo di presenza di meno.