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
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
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; of_get_last_error() dice quale), -6 rifiutato in modalità demo. -2 dice che il client non ha potuto essere creato. Un oggetto creato con create basta per le chiamate sincrone; per gli eventi delle chiamate asincrone, vedere «Dove scrivere gli event», più sotto.
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» |
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; solo l'host e la porta della richiesta vi rispondono: un reindirizzamento verso un altro host prosegue senza di esse |
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; viene presentato solo all'host e alla porta della richiesta, mai dopo un reindirizzamento altrove |
ib_check_revocation | boolean | false | Rifiuta un certificato del server che la sua autorità ha revocato: si consulta la lista di revoca o l'OCSP della catena. Disattivato per impostazione predefinita: quando la verifica non si può fare — l'autorità irraggiungibile da una rete chiusa —, la richiesta fallisce (-4). Letto a ogni richiesta |
is_last_request | string | "" | Ciò che l'ultima richiesta ha chiesto, da leggere quando un'API risponde 400: il metodo e l'URL, poi le intestazioni impostate dal vostro codice — i loro segreti mascherati: lo schema di un Authorization resta (Bearer ***), il valore di un'intestazione il cui nome dice key, token, secret, password o cookie sparisce —, poi la dimensione del corpo. La libreria aggiunge User-Agent, Content-Type e i cookie |
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_get_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; anche un'intestazione rifiutata quando la si imposta (of_set_header) vi dice il perché, e viene svuotata quando una richiesta asincrona parte |
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; «un altro host» vuol dire un'altra origine: un altro host, porta o schema — un URL http:// reindirizzato verso https:// perde il suo token. Un valore viaggia in Latin-1: un carattere oltre (greco, giapponese…) è rifiutato (-5) già quando lo si imposta, of_get_last_error() dice il perché. Lo User-Agent (PBToolboxAI/<versione>) si sostituisce qui |
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à; sostituisce anche un token OAuth (of_oauth_client_credentials) |
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. Ogni altro carattere è permesso, accenti e giapponese compresi: la coppia parte come base64 del suo UTF-8. Il base64 non protegge nulla: in https:// il TLS nasconde la password, in http:// viaggia leggibile |
of_oauth_client_credentials (string as_token_url, string as_client, string as_secret, string as_scope) → long | OAuth2 «client credentials»: chiede subito un token all'endpoint dei token — un POST di modulo con grant_type, client_id, client_secret e scope (vuoto = nessuno) —, poi lo invia come Authorization: Bearer a ogni richiesta. Il token viene richiesto da solo un minuto prima della scadenza (expires_in) e dopo un 401: una richiesta sincrona viene allora ripetuta una volta, una asincrona riceve il suo 401 e la richiesta successiva rinnova il token. La richiesta del token non porta mai l'autenticazione dell'oggetto. Una sola autenticazione alla volta: of_set_bearer, of_set_basic, of_clear_headers e of_reset la fermano. Restituisce 0 una volta impostato il token, -5 per un URL o un client vuoti, -4 quando l'endpoint rifiuta o non risponde — of_get_last_error() dice il perché, con l'error_description del server —, -2 se il client non ha potuto essere creato. Un fallimento conserva le impostazioni: la richiesta successiva richiede di nuovo |
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. -5 anche per credenziali scritte nell'URL (https://user:pwd@…) — usate of_set_basic —, -2 se il client non ha potuto essere creato. La query dell'URL parte così come è scritta: costruitela con of_url appena un valore viene da un input |
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_post_form (string as_url, string as_names[], string as_values[]) → long | POST di un modulo HTML (application/x-www-form-urlencoded), ogni nome e ogni valore codificati: ciò che si aspetta un endpoint dei token OAuth2, un'API datata o un modulo web. Un nome vuoto viene saltato, un valore mancante è vuoto. Restituisce lo stato HTTP, o -5 / -4 / -6 / -2 come of_post |
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 {, string as_headers}) → 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. as_headers: intestazioni per questa sola richiesta — righe Nome: valore unite da ~r~n (un Idempotency-Key, un If-Match, un altro token); sostituiscono un'intestazione con lo stesso nome, un valore vuoto ne esclude una, nulla è conservato per la richiesta successiva; una riga non valida restituisce -5 e nulla parte. Un metodo malformato (GE T) restituisce -5 |
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_retrieve (datastore ads_target, string as_url {, string as_path}) → long | GET di un corpo JSON e l'array di oggetti trovato in as_path messo in un DataStore ("" = il corpo è l'array, "data/items" = un array al suo interno) — come Retrieve: il DataStore viene prima svuotato, poi una riga per oggetto, ogni membro nella colonna con lo stesso nome (maiuscole ignorate), convertito nel suo tipo (n_pbt_json.of_to_datastore); le righe arrivano non modificate. Gli stessi overload esistono per un datawindow e un datawindowchild (un elenco a discesa riempito da un'API). Restituisce il numero di righe, o -4 quando la risposta non è un 2xx, non è JSON — in modalità demo un corpo è tagliato a 4096 caratteri — o non contiene alcun array in quel punto (il DataStore conserva allora le sue righe), -5 per un DataStore o un URL non validi, e i codici di of_get; of_get_last_error() dice il perché |
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; un'intestazione inviata più volte dà tutti i suoi valori, uniti da , — Set-Cookie da un a capo (una data di cookie contiene una virgola) |
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_url (string as_path, string as_names[], string as_values[]) → string | Un URL con la sua query, ogni nome e ogni valore codificati: con q = "C# & R&D" e page = "2", of_url("/items", …) restituisce /items?q=C%23%20%26%20R%26D&page=2. Un percorso che ha già una query riceve le coppie dopo un &, un frammento (#top) resta in fondo, un nome vuoto viene saltato. Nulla è conservato: sicuro tra due richieste asincrone. La libreria non codifica mai la query di un URL che scrivete voi stessi — un # la taglia, un & aggiunge un parametro, un + diventa uno spazio |
of_url_encode (string as_text) → string | Un valore reso sicuro per un URL (RFC 3986, UTF-8): lettere, cifre, -, ., _ e ~ restano, tutto il resto diventa %XX — "C# & co" restituisce C%23%20%26%20co. Vuoto per un testo vuoto o NULL |
of_reset ( ) → long | 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 = ""); ib_check_revocation torna a false, un token OAuth viene dimenticato e is_last_request svuotato Rende 0 una volta fatto. |
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.of_get_last_error())
end if
Un'API che risponde con un errore #
// 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
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.of_get_last_error())
end if
// The client is no longer needed
destroy lnv_rest
Una ricerca digitata dall'utente #
// 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))
Un'API protetta da 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
Un elenco in 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())
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 (of_get_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 (of_get_last_error() dice perché) |
of_close ( ) → long | 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 Rende 0 una volta fatto. |
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 (of_get_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_post_form_async (string as_url, string as_names[], string as_values[]) → long | Lo stesso modulo di of_post_form, inviato senza attendere. 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 {, string as_headers}) → 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. as_headers: intestazioni per questa sola richiesta, come per of_request |
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); i valori ripetuti uniti come in of_response_header(as_name) |
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). Da leggere in ue_response; letta prima della risposta (in ue_progress) restituisce vuoto, e la risposta viene letta comunque quando arriva |
| 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. Si scrive sull'oggetto client posto nella finestra, o in un discendente (vedere «Dove scrivere gli event»). Chiudere da esso la finestra che porta il client è permesso: le risposte ancora in attesa non vengono consegnate |
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 una richiesta asincrona: il corpo della risposta man mano che arriva (qualsiasi richiesta, un grande JSON compreso), il file di un download (of_download_async) o di un upload (of_upload). al_done byte su al_total (0 quando il server non ha annunciato la dimensione); l'ultimo, subito prima di ue_response, dice al_done = al_total. Oltre 2 GB i byte non stanno più in un long |
Dove scrivere gli event #
Un oggetto creato con create n_pbt_restclient serve benissimo le chiamate sincrone — è ciò che fa l'avvio rapido —, ma i suoi eventi non hanno script: PowerBuilder non permette di scrivere ue_response su un oggetto creato in uno script. Per le chiamate asincrone, due strade:
- l'oggetto posto nella finestra (o nell'oggetto utente): nel painter, inserite un oggetto non visuale
n_pbt_restcliente chiamateloinv_rest; i suoi eventiue_response,ue_failedeue_progresssi scrivono allora come quelli di un controllo, e nasce e muore con la finestra — ciò che fa l'applicazione dimostrativa; - un discendente: ereditate da
n_pbt_restclient(n_my_api), scriveteue_responsein quel discendente e createlo concreate n_my_api.
Chiudere da uno dei suoi eventi la finestra che porta il client è permesso: le risposte ancora in attesa non vengono consegnate.
// 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)
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 —of_get_last_error()dice 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'altra origine non porta nulla di vostro: nessuna delle intestazioni impostate con
of_set_header(token, chiave API) lo segue — resta soloContent-Type. Un'origine è un host, una porta e uno schema: un'APIhttp://reindirizzata versohttps://perde il suo token, scrivete quindihttps://inis_base_url. Le credenziali Windows (ib_windows_auth) e il certificato client sono dati solo all'host e alla porta della richiesta. 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, o un carattere oltre il Latin-1 (un'intestazione viaggia in Latin-1; non perof_set_basic, la cui coppia parte in base64), 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. - Codificate ciò che l'utente ha digitato: la query di un URL parte così come è scritta — un
#la taglia, un&aggiunge un parametro, un+diventa uno spazio. Costruitela conof_url, o codificate un valore conof_url_encode. - Mai credenziali nell'URL (
https://user:pwd@…): la richiesta è rifiutata (-5); usateof_set_basic. - Lo
User-AgentvalePBToolboxAI/<versione>;of_set_header("User-Agent", …)lo sostituisce quando un'API esige il vostro.