restclient — n_pbt_restclient #
← Referencia de componentes · Índice de la guía
Un cliente HTTPS para PowerBuilder, versión 10 incluida: GET, POST, PUT, PATCH, DELETE, cabeceras, un cuerpo JSON, la descarga de un archivo. Nativo — el TLS, el proxy y la descompresión del sistema, y ningún muro CORS, porque la petición no sale de una página.
▶ Verlo en vivo — Aplicación de demostración, mosaico REST client: las respuestas, el código que las obtiene y esta página, lado a lado (se requiere conexión a Internet).
En resumen #
| Objeto no visual | n_pbt_restclient |
| Sirve para | Llamar a una API REST desde una aplicación PowerBuilder: leer, crear, actualizar, descargar |
| Principio | Una petición es una llamada que devuelve el estado HTTP; la respuesta espera en el objeto, leída con of_response_text u of_json_value |
| Dependencia | WinHTTP, en la DLL de la biblioteca — sin página, sin runtime adicional |
Inicio rápido #
// 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
Cada método HTTP devuelve el estado que el servidor eligió — un 404 es una respuesta, no un error — o un código negativo cuando la petición no llegó a buen puerto: -5 URL o cabecera no válida, certificado de cliente no encontrado, -4 fallo (sin red, nombre desconocido, tiempo agotado, TLS, un cuerpo de texto de más de 64 MB — 16 MB en un proceso de 32 bits; of_get_last_error() dice cuál), -6 rechazado en modo demo. -2 dice que el cliente no pudo crearse. Un objeto creado con create basta para las llamadas síncronas; para los eventos de las asíncronas, vea «Dónde escribir los eventos», más abajo.
Por qué nativo, y no la página #
Un fetch() lanzado desde una página de la biblioteca está sujeto a CORS: toda API que no envíe Access-Control-Allow-Origin le queda cerrada, y las API de empresa no lo envían. La petición sale por tanto de la DLL, por WinHTTP: el TLS del sistema con sus certificados, el proxy configurado, gzip. Lo que pierde: nada; lo que gana: todas las API.
Propiedades #
| Propiedad | Tipo | Predeterminado | Función |
|---|---|---|---|
is_base_url | string | "" | Puesto delante de una URL relativa: is_base_url = "https://api.example.com" y luego of_get("/orders/4152"). Una URL absoluta se usa tal cual |
il_timeout_ms | long | 30000 | Cuánto puede durar cada etapa de una petición: resolución del nombre, conexión, envío y espera de cada bloque de la respuesta. Una descarga grande que avanza nunca se corta; un servidor mudo durante ese tiempo, sí: -4 y «timed out». 0 o menos vale el valor predeterminado, 30 s: no existe «sin límite de tiempo» |
il_status | long | 0 | El estado HTTP de la última petición síncrona, o su código negativo; el valor que devolvió la petición. Una respuesta asíncrona tiene el suyo: of_status(id) |
ib_keep_cookies | boolean | true | Conserva las cookies de una petición a otra — síncronas y asíncronas, que comparten una misma sesión: una API que pone una cookie al iniciar sesión le mantiene conectado. A false, ya no se envía ni se guarda ninguna cookie, y las ya guardadas se olvidan; se aplica de inmediato |
il_max_retries | long | 0 | Cuántas veces se repite una petición asíncrona ante un 429, un 503 o un fallo de red. Un POST cuya respuesta se perdió nunca se repite: puede haberse procesado. Un fallo que se repetiría de forma idéntica nunca se reintenta: demasiadas redirecciones, redirección no válida, redirección HTTPS → HTTP rechazada, fallo TLS, archivo imposible de escribir |
il_retry_backoff_ms | long | 500 | La espera antes del primer reintento, en milisegundos, duplicada en cada intento y limitada a 60 s. Una cabecera Retry-After del servidor (segundos o fecha) tiene prioridad |
ib_windows_auth | boolean | false | Autenticación integrada de Windows (Negotiate / NTLM): cuando un servidor la pide — una API IIS de la intranet —, responden las credenciales de la sesión de Windows, sin teclear nada. Desactivada por defecto: sin ella nunca se envían, a ningún servidor. Se lee en cada petición; solo el host y el puerto de la petición responden con ellas: una redirección hacia otro host sigue sin ellas |
is_client_certificate | string | "" | Un certificado de cliente (TLS mutuo): la huella SHA-1 de un certificado del almacén personal — el del usuario, luego el de la máquina —, tal como la muestra el administrador de certificados; espacios y dos puntos ignorados. Vacío = ninguno. Una huella no encontrada hace que toda petición devuelva -5 («client certificate not found»). Se lee en cada petición. No existe opción para ignorar un error de certificado del servidor; solo se presenta al host y al puerto de la petición, nunca tras una redirección a otro sitio |
ib_check_revocation | boolean | false | Rechaza un certificado de servidor que su autoridad ha revocado: se consulta la lista de revocación o el OCSP de la cadena. Desactivado por defecto: cuando la comprobación no puede hacerse — la autoridad inalcanzable desde una red cerrada —, la petición falla (-4). Se lee en cada petición |
is_last_request | string | "" | Lo que la última petición pidió, para leer cuando una API responde 400: el método y la URL, luego las cabeceras puestas por su código — sus secretos ocultos: el esquema de un Authorization se queda (Bearer ***), el valor de una cabecera cuyo nombre dice key, token, secret, password o cookie desaparece —, luego el tamaño del cuerpo. La biblioteca añade User-Agent, Content-Type y las cookies |
ipo_owner | powerobject | null | El objeto visual para el que trabaja este cliente: la licencia se comprueba en su clase. Necesario solo en la aplicación de demostración; una clave de desarrollo o de ejecución desbloquea el cliente sin él. Sin desbloquear, el cliente está en modo demo — cuerpos cortados a 4096 caracteres, ni descarga ni subida |
Constantes: METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE para of_request.
Métodos #
| Método | Función |
|---|---|
of_get_last_error ( ) → string | Por qué la última petición síncrona — o una llamada asíncrona rechazada en el acto — devolvió un código negativo. Vacío tras una petición que llegó al servidor — un 404 no es un error. Un fallo asíncrono dice su motivo en ue_failed; una cabecera rechazada al ponerla (of_set_header) también dice aquí por qué, y se vacía cuando sale una petición asíncrona |
of_set_header (string as_name, string as_value) → long | Una cabecera enviada con cada petición a partir de ahora; un nombre ya puesto se reemplaza, un valor vacío retira la cabecera. Content-Type se añade solo cuando un cuerpo sale sin él (JSON, UTF-8); para una subida, el puesto aquí se ignora (multipart/form-data lo pone la biblioteca). Ninguna de estas cabeceras sigue una redirección hacia otro host. Devuelve 0, o -5 — nada cambia — para un nombre vacío, un nombre con un espacio, dos puntos o un carácter de control, o un valor con un salto de línea; «otro host» quiere decir otro origen: otro host, puerto o esquema — una URL http:// redirigida a https:// pierde su token. Un valor viaja en Latin-1: un carácter más allá (griego, japonés…) se rechaza (-5) al ponerlo, of_get_last_error() dice por qué. El User-Agent (PBToolboxAI/<versión>) se sustituye aquí |
of_remove_header (string as_name) | Olvida una cabecera, por nombre (sin distinguir mayúsculas) |
of_clear_headers ( ) | Olvida todas las cabeceras, la autenticación incluida |
of_set_bearer (string as_token) → long | Authorization: Bearer en cada petición. Una sola autenticación a la vez: reemplaza una autenticación Basic; un token vacío retira la autenticación. Devuelve 0, o -5 para un token con un salto de línea — no queda entonces ninguna autenticación, nunca media; también sustituye un token OAuth (of_oauth_client_credentials) |
of_set_basic (string as_user, string as_password) → long | Autenticación Basic; la DLL codifica ella misma el base64 — PowerBuilder 10 no lo tiene. Reemplaza un token Bearer; un usuario vacío retira la autenticación. Devuelve 0, o -5 para un usuario o una contraseña con un salto de línea — no queda entonces ninguna autenticación. Cualquier otro carácter está permitido, acentos y japonés incluidos: el par sale como base64 de su UTF-8. El base64 no protege nada: en https:// el TLS oculta la contraseña, en http:// viaja legible |
of_oauth_client_credentials (string as_token_url, string as_client, string as_secret, string as_scope) → long | OAuth2 «client credentials»: pide ahora un token al punto de emisión — un POST de formulario con grant_type, client_id, client_secret y scope (vacío = ninguno) —, y luego lo envía como Authorization: Bearer en cada petición. El token se vuelve a pedir solo un minuto antes de caducar (expires_in) y tras un 401: una petición síncrona se repite entonces una vez, una asíncrona recibe su 401 y la petición siguiente renueva el token. La petición del token nunca lleva la autenticación del objeto. Una sola autenticación a la vez: of_set_bearer, of_set_basic, of_clear_headers y of_reset la detienen. Devuelve 0 una vez puesto el token, -5 para una URL o un cliente vacíos, -4 cuando el punto de emisión rechaza o no responde — of_get_last_error() dice por qué, con el error_description del servidor —, -2 si el cliente no pudo crearse. Un fallo conserva los ajustes: la petición siguiente vuelve a pedirlo |
of_get (string as_url) → long | GET. Devuelve el estado HTTP, o -5 URL o cabecera no válida, certificado de cliente no encontrado, -4 fallo, -6 rechazado en demo. El fragmento #… de una URL nunca se envía al servidor. -5 también para credenciales escritas en la URL (https://user:pwd@…) — use of_set_basic —, -2 si el cliente no pudo crearse. La consulta de la URL sale tal como está escrita: constrúyala con of_url en cuanto un valor venga de una entrada |
of_post (string as_url, string as_body) → long | POST con un cuerpo (JSON por defecto, construido con n_pbt_json). Devuelve el estado HTTP, o -5 / -4 / -6 |
of_post_form (string as_url, string as_names[], string as_values[]) → long | POST de un formulario HTML (application/x-www-form-urlencoded), cada nombre y cada valor codificados: lo que espera un punto de emisión de tokens OAuth2, una API antigua o un formulario web. Un nombre vacío se omite, un valor que falta queda vacío. Devuelve el estado HTTP, o -5 / -4 / -6 / -2 como of_post |
of_put (string as_url, string as_body) → long | PUT con un cuerpo. Devuelve el estado HTTP, o -5 / -4 / -6 |
of_patch (string as_url, string as_body) → long | PATCH con un cuerpo. Devuelve el estado HTTP, o -5 / -4 / -6 |
of_delete (string as_url) → long | DELETE. Devuelve el estado HTTP, o -5 / -4 / -6 |
of_request (string as_method, string as_url, string as_body {, string as_headers}) → long | Cualquier método (METHOD_*, o el suyo), cualquier cuerpo enviado en UTF-8: lo que llaman los cinco atajos. Devuelve el estado HTTP, o -5 / -4 / -6. as_headers: cabeceras para esta petición sola — líneas Nombre: valor unidas por ~r~n (un Idempotency-Key, un If-Match, otro token); sustituyen una cabecera del mismo nombre, un valor vacío deja una fuera, nada se guarda para la petición siguiente; una línea no válida devuelve -5 y nada sale. Un método mal formado (GE T) devuelve -5 |
of_download (string as_url, string as_path) → long | GET directamente a un archivo, bytes intactos, sea cual sea el tamaño. Devuelve el estado HTTP, o -5 URL o ruta vacía, -4 fallo, -6 rechazado en demo. Una ruta relativa se resuelve en la llamada en la carpeta actual de la aplicación al cargar la biblioteca — nunca donde un DirList o un cuadro de archivos la haya movido desde entonces |
of_retrieve (datastore ads_target, string as_url {, string as_path}) → long | GET de un cuerpo JSON y el array de objetos encontrado en as_path puesto en un DataStore ("" = el cuerpo es el array, "data/items" = un array dentro) — como Retrieve: el DataStore se vacía primero, luego una fila por objeto, cada miembro en la columna del mismo nombre (mayúsculas ignoradas), convertido a su tipo (n_pbt_json.of_to_datastore); las filas llegan sin modificar. Las mismas sobrecargas existen para un datawindow y un datawindowchild (una lista desplegable llenada desde una API). Devuelve el número de filas, o -4 cuando la respuesta no es un 2xx, no es JSON — en modo demo un cuerpo se corta a 4096 caracteres — o no tiene array en ese lugar (el DataStore conserva entonces sus filas), -5 para un DataStore o una URL no válidos, y los códigos de of_get; of_get_last_error() dice por qué |
of_response_text ( ) → string | El cuerpo de la última respuesta síncrona de este objeto — otro cliente no lo sobrescribe —, en texto: decodificado según el charset anunciado por el servidor (UTF-8, y los juegos históricos: windows-125x, iso-8859-x, shift_jis, gbk/gb2312, big5, euc-kr, koi8-r…), UTF-8 por defecto (un byte no válido se convierte en un carácter de reemplazo). Vacío tras un fallo, y tras of_download: ese cuerpo está en el archivo. Un cuerpo de texto está limitado a 64 MB, 16 MB en un proceso de 32 bits: más allá, -4 «body too large» — use of_download |
of_response_headers ( ) → string | Todas las cabeceras de la última respuesta síncrona, una por línea |
of_response_header (string as_name) → string | Una cabecera de la última respuesta síncrona, por nombre; vacía si no se envió; una cabecera enviada varias veces da todos sus valores, unidos por , — Set-Cookie por un salto de línea (una fecha de cookie contiene una coma) |
of_json_value (string as_path) → string | Un valor del cuerpo JSON por su ruta — claves unidas por /, posiciones de array desde 1, la notación de n_pbt_json: "customer", "json/customer", "items/1/qty". Una cadena vuelve decodificada, un número o true/false tal como están escritos ("4152"), null vacío, un objeto o un array como JSON; vacío si la ruta no existe. Para más de un valor, cargue of_response_text en un n_pbt_json. El cuerpo se analiza una sola vez por respuesta: leer diez valores cuesta un único análisis |
of_url (string as_path, string as_names[], string as_values[]) → string | Una URL con su consulta, cada nombre y cada valor codificados: con q = "C# & R&D" y page = "2", of_url("/items", …) devuelve /items?q=C%23%20%26%20R%26D&page=2. Una ruta que ya tiene consulta recibe los pares tras un &, un fragmento (#top) queda al final, un nombre vacío se omite. Nada se guarda: seguro entre dos peticiones asíncronas. La biblioteca nunca codifica la consulta de una URL que escribe usted mismo — un # la corta, un & añade un parámetro, un + se vuelve un espacio |
of_url_encode (string as_text) → string | Un valor hecho seguro para una URL (RFC 3986, UTF-8): letras, cifras, -, ., _ y ~ se quedan, todo lo demás se vuelve %XX — "C# & co" devuelve C%23%20%26%20co. Vacío para un texto vacío o NULL |
of_reset ( ) → long | Vuelta a los valores por defecto: ninguna cabecera, ninguna URL base, el tiempo por defecto, ningún reintento, cookies conservadas; el cliente se cierra — sus cookies se van, sus peticiones en curso se cancelan sin ningún evento — y la última respuesta se olvida. La autenticación de Windows se desactiva (ib_windows_auth = false) y el certificado de cliente se olvida (is_client_certificate = ""); ib_check_revocation vuelve a false, un token OAuth se olvida y is_last_request se vacía Devuelve 0 una vez hecho. |
Ejemplos #
Leer una lista y recorrerla #
// 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
Descargar 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
Una API que responde con error #
// 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
Una API de intranet con autenticación de 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 búsqueda escrita por el usuario #
// 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))
Una API protegida por 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
Una lista en 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())
Asíncrono (sin congelar la interfaz) #
Durante una llamada síncrona la interfaz queda congelada hasta la respuesta: la ventana ya no se redibuja ni responde a los clics. No tiene consecuencias en una llamada corta; para toda llamada que pueda tardar — un servidor lento, un cuerpo grande, una red incierta —, use la API asíncrona: la petición sale en un hilo de trabajo y su respuesta vuelve más tarde como evento. Síncrono y asíncrono comparten la misma sesión: la cookie puesta por un inicio de sesión síncrono sirve a las llamadas asíncronas que siguen, y viceversa; una llamada síncrona nunca espera detrás de las peticiones asíncronas del mismo objeto.
Preparación — nada que conectar: el componente entrega la respuesta solo en ue_response. of_open / of_close abren y cierran el cliente (opcional: la primera petición lo abre), of_clear_cookies olvida las cookies guardadas entre peticiones. Internamente, una bomba del componente llama a of_process_events para drenar las respuestas — usted nunca la llama.
Enviar — of_request_async(method, url, body), o los atajos of_get_async, of_post_async, of_put_async, of_patch_async, of_delete_async, devuelven al instante un id de petición — o -5 al instante, sin ningún evento después, para una URL o una cabecera no válida (of_get_last_error() dice por qué, por ejemplo «invalid URL: …»). Seis peticiones como máximo salen a la vez; las siguientes esperan su turno en una cola. of_download_async(url, path) descarga a un archivo y of_upload(url, field, path) sube un archivo en multipart/form-data — ambos con progreso. of_cancel(id) detiene una petición. Los reintentos (il_max_retries, il_retry_backoff_ms) repiten en 429/503/fallo de red.
Recibir — ue_response(al_id, al_status) (leer el cuerpo con of_response_text(al_id) / of_status(al_id) DENTRO del manejador), ue_failed(al_id, al_code, as_error), ue_progress(al_id, al_done, al_total). Un id solo vale para el objeto que envió la petición. Un error de ejecución en uno de estos eventos no se traga: llega al evento SystemError de la aplicación como cualquier error de script, y las respuestas siguientes se entregan igualmente.
| Método | Función |
|---|---|
of_open ( ) → long | Abre el cliente: su sesión (cookies compartidas por las llamadas síncronas y asíncronas) y su canal de eventos nativo (sin WebView, sin CORS). Opcional: la primera petición lo abre. Devuelve el id del cliente (> 0), o -2 si no se pudo crear (of_get_last_error() dice por qué) |
of_close ( ) → long | Cierra el cliente: sus cookies (la sesión) se van con él, y sus peticiones aún en curso se cancelan sin ningún evento — ni ue_response, ni ue_failed. La siguiente petición lo reabre. Lo hacen por usted of_reset y la destrucción. La última respuesta síncrona del objeto se olvida con él Devuelve 0 una vez hecho. |
of_process_events ( ) | Reparte las respuestas asíncronas y lanza ue_response / ue_failed / ue_progress. Pública SOLO porque el pump interno del componente la llama en el bucle de PowerBuilder, cada pocos milisegundos mientras una petición está en vuelo — usted nunca la llama, y no cablea ni receptor ni temporizador. Llamarla usted mismo es inofensivo: solo vacía lo que ya está esperando |
of_clear_cookies ( ) | Olvida las cookies guardadas de una petición a otra (un cierre de sesión). Guardar o no cookies después sigue dependiendo de ib_keep_cookies |
of_get_async (string as_url) → long | GET asíncrono. Devuelve al instante un id de petición; la respuesta llega en ue_response(id, status). Devuelve -5 al instante — y no sigue ningún evento — para una URL o una cabecera no válida (of_get_last_error() dice por qué), -2 si el cliente no puede abrirse |
of_post_async (string as_url, string as_body) → long | POST asíncrono con un cuerpo (JSON por defecto, construido con n_pbt_json). Devuelve de inmediato un id de petición; la respuesta llega en ue_response. Devuelve -5 / -2 como of_get_async |
of_post_form_async (string as_url, string as_names[], string as_values[]) → long | El mismo formulario que of_post_form, enviado sin esperar. Devuelve en el acto un id de petición; la respuesta llega en ue_response. Devuelve -5 / -2 como of_get_async |
of_put_async (string as_url, string as_body) → long | PUT asíncrono con un cuerpo. Devuelve de inmediato un id de petición; la respuesta llega en ue_response. Devuelve -5 / -2 como of_get_async |
of_patch_async (string as_url, string as_body) → long | PATCH asíncrono con un cuerpo. Devuelve de inmediato un id de petición; la respuesta llega en ue_response. Devuelve -5 / -2 como of_get_async |
of_delete_async (string as_url) → long | DELETE asíncrono. Devuelve de inmediato un id de petición; la respuesta llega en ue_response. Devuelve -5 / -2 como of_get_async |
of_request_async (string as_method, string as_url, string as_body {, string as_headers}) → long | Petición asíncrona con el método HTTP que elija (of_get_async y sus hermanos la llaman). Devuelve al instante un id de petición; la respuesta llega en ue_response, el fallo en ue_failed. Seis peticiones como máximo a la vez, las siguientes esperan su turno. Devuelve -2 si el cliente no se abre, -4 si la petición no puede ponerse en cola, -5 al instante — sin evento — para una URL o una cabecera no válida. as_headers: cabeceras solo para esta petición, como en of_request |
of_download_async (string as_url, string as_path) → long | GET asíncrono directamente a un ARCHIVO, con ue_progress por el camino. Devuelve un id de petición, -5 con una ruta vacía, -6 rechazado en el acto en modo demo, -2 si el cliente no se abre. Una ruta relativa se resuelve en la llamada, como para of_download |
of_upload (string as_url, string as_field, string as_path) → long | Sube un ARCHIVO en multipart/form-data (campo de formulario as_field), en asíncrono, con ue_progress — siempre un POST, con ese único campo. Devuelve un id de petición, -5 con una ruta vacía, -6 rechazado en el acto en modo demo, -2 si el cliente no se abre. Un archivo relativo se busca en la carpeta actual, luego en la de la aplicación al cargar la biblioteca, luego junto al exe. El Content-Type puesto por of_set_header se ignora: multipart/form-data lo pone la biblioteca |
of_cancel (long al_id) → long | Pide a una petición que se detenga; termina con ue_failed(id, -4, "cancelled"), al instante si aún esperaba su turno. Devuelve 0, -5 si el id es desconocido o pertenece a otro objeto — no se cancela nada —, -4 si es demasiado tarde: la respuesta ha llegado y viene en ue_response |
of_status (long al_id) → long | El estado HTTP de una respuesta asíncrona, por id de petición — a leer DENTRO de ue_response. Devuelve el estado (200, 404…); 0 mientras la petición corre, para una petición fallida (en ue_failed), para un id desconocido, ya olvidado o de otro objeto |
of_response_text (long al_id) → string | El cuerpo de una respuesta asíncrona, por id de petición — a leer DENTRO de ue_response: la petición se olvida al volver del evento; vacío mientras está en curso; vacío también para el id de otro objeto |
of_response_headers (long al_id) → string | Todas las cabeceras de una respuesta asíncrona, una por línea |
of_response_header (long al_id, string as_name) → string | Una cabecera de una respuesta asíncrona, por nombre (sin distinguir mayúsculas); los valores repetidos unidos como en of_response_header(as_name) |
of_json_value (long al_id, string as_path) → string | Un valor del cuerpo JSON de una respuesta asíncrona, por ruta — la misma notación que of_json_value(as_path). Léala en ue_response; leída antes de la respuesta (en ue_progress) devuelve vacío, y la respuesta se lee igualmente cuando llega |
| Evento | Función |
|---|---|
ue_response (long al_id, long al_status) | Una petición asíncrona ha terminado: al_id es el id devuelto al enviar, al_status el estado HTTP. Lea el cuerpo con of_response_text(al_id) DENTRO del manejador: la petición se olvida al salir del evento. Se escribe en el objeto cliente colocado en la ventana, o en un descendiente (véase «Dónde escribir los eventos»). Cerrar desde él la ventana que lleva el cliente está permitido: las respuestas que aún esperaban no se entregan |
ue_failed (long al_id, long al_code, string as_error) | Una petición asíncrona ha fallado. al_code dice la naturaleza: -4 la petición falló (sin red, tiempo agotado, TLS, cuerpo demasiado grande, o cancelada por of_cancel — as_error vale entonces cancelled), -5 archivo no válido o certificado de cliente no encontrado, -6 rechazado en modo demo (una petición Range); as_error dice por qué. of_status(al_id) devuelve ahí 0 |
ue_progress (long al_id, long al_done, long al_total) | Avance de una petición asíncrona: el cuerpo de la respuesta a medida que llega (cualquier petición, un JSON grande incluido), el archivo de una descarga (of_download_async) o de un envío (of_upload). al_done bytes de al_total (0 cuando el servidor no anunció el tamaño); el último, justo antes de ue_response, dice al_done = al_total. Más allá de 2 GB los bytes ya no caben en un long |
Dónde escribir los eventos #
Un objeto creado con create n_pbt_restclient sirve muy bien para las llamadas síncronas — es lo que hace el inicio rápido —, pero sus eventos no tienen script: PowerBuilder no permite escribir ue_response en un objeto creado en un script. Para las llamadas asíncronas, dos vías:
- el objeto colocado en la ventana (o en el objeto de usuario): en el pintor, inserte un objeto no visual
n_pbt_restclienty llámeloinv_rest; sus eventosue_response,ue_failedyue_progressse programan entonces como los de un control, y nace y muere con la ventana — lo que hace la aplicación de demostración; - un descendiente: herede de
n_pbt_restclient(n_my_api), escribaue_responseen ese descendiente y créelo concreate n_my_api.
Cerrar la ventana que lleva el cliente desde uno de sus eventos está permitido: las respuestas que aún esperaban no se entregan.
// 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)
Buenas prácticas #
- Un cliente por API, con su
is_base_urly sus cabeceras puestas una vez: el código de las llamadas solo lleva la ruta y el cuerpo. - El cuerpo se construye con
n_pbt_json, nunca por concatenación: una comilla en el nombre de un cliente no rompe nada. - Pruebe el estado, luego el código negativo:
>= 200 y < 300está bien,>= 400el servidor dijo que no,< 0la petición no salió —of_get_last_error()dice por qué. - El tiempo límite: 30 segundos por defecto; un informe pesado merece más, una comprobación de presencia menos.
- Lo que puede tardar va en asíncrono: una llamada síncrona congela la interfaz hasta la respuesta.
- Una redirección conserva su petición: un 301 o un 302 solo reescribe en GET un POST — un PUT, un PATCH o un DELETE se rehace tal cual, cuerpo incluido; un 303 sigue en GET sin cuerpo. Una
Locationrelativa (?page=2,../) se resuelve como lo haría un navegador. - Una redirección hacia otro origen no se lleva nada suyo: ninguna de las cabeceras puestas con
of_set_header(token, clave de API) la sigue — solo quedaContent-Type. Un origen es un host, un puerto y un esquema: una APIhttp://redirigida ahttps://pierde su token, así que escribahttps://enis_base_url. Las credenciales de Windows (ib_windows_auth) y el certificado de cliente solo se dan al host y al puerto de la petición. Una redirección HTTPS → HTTP se rechaza. - Pruebe lo que devuelven
of_set_header,of_set_beareryof_set_basiccuando el valor viene de una entrada o de un archivo: un salto de línea, o un carácter más allá del Latin-1 (una cabecera viaja en Latin-1; no paraof_set_basic, cuyo par sale en base64), se rechaza (-5) y no se pone nada. Una cabecera rechazada nunca sale a medias. - Las credenciales de Windows solo salen si usted lo pide:
ib_windows_auth = truepara una API de intranet con autenticación de Windows, nunca en un cliente que habla con Internet. - Un cuerpo de texto está limitado a 64 MB, 16 MB en una aplicación de 32 bits: una exportación voluminosa se lee con
of_download/of_download_async, que escriben un archivo sin límite. - Codifique lo que el usuario escribió: la consulta de una URL sale tal como está escrita — un
#la corta, un&añade un parámetro, un+se vuelve un espacio. Constrúyala conof_url, o codifique un valor conof_url_encode. - Nunca credenciales en la URL (
https://user:pwd@…): la petición se rechaza (-5); useof_set_basic. - El
User-AgentvalePBToolboxAI/<versión>;of_set_header("User-Agent", …)lo sustituye cuando una API exige el suyo.