PBToolboxAI v3 ← Site

restclient — n_pbt_restclient #

← Component reference · Guide contents

An HTTPS client for PowerBuilder, version 10 included: GET, POST, PUT, PATCH, DELETE, headers, a JSON body, a file download. Native — the system's TLS, proxy and decompression, and no CORS wall, because the request does not leave from a page.

▶ See it live — Demo application, REST client tile: the answers, the code that gets them and this page, side by side (Internet connection required).


At a glance #

Nonvisual objectn_pbt_restclient
Used forCalling a REST API from a PowerBuilder application: read, create, update, download
PrincipleA request is a call that returns the HTTP status; the answer waits in the object, read by of_response_text or of_json_value
DependencyWinHTTP, inside the library's DLL — no page, no extra runtime

Quick start #

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

Every HTTP method returns the status the server chose — a 404 is an answer, not an error — or a negative code when the request did not go through: -5 invalid URL, -4 failure (no network, unknown name, timeout, TLS; is_last_error says which), -6 refused in demo mode.


Why native, and not the page #

A fetch() launched from a page of the library is subject to CORS: every API that does not send Access-Control-Allow-Origin is closed to it, and enterprise APIs do not send it. So the request leaves from the DLL, through WinHTTP: the system's TLS and certificates, the configured proxy, gzip. What you lose: nothing; what you gain: every API.


Properties #

PropertyTypeDefaultRole
is_base_urlstring""Put in front of a relative URL: is_base_url = "https://api.example.com" then of_get("/orders/4152"). An absolute URL is used as it is
il_timeout_mslong30000How long a request may take, connection and answer included. Past it: -4 and "timed out"
is_last_errorstring""Why the last request returned a negative code. Empty after a request that reached the server — a 404 is not an error
il_statuslong0The HTTP status of the last request, or its negative code; the value the request returned

Constants: METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE for of_request.


Methods #

MethodRole
of_set_header (string as_name, string as_value)A header sent with every request from now on; a name already set is replaced. Content-Type is added by itself when a body is sent without one (JSON, UTF-8)
of_remove_header (string as_name)Forgets one header, by name (case does not matter)
of_clear_headers ( )Forgets every header, the authentication included
of_set_bearer (string as_token)Authorization: Bearer on every request; an empty token removes it
of_set_basic (string as_user, string as_password)Basic authentication; the DLL encodes the base64 itself — PowerBuilder 10 has none
of_get (string as_url) → longGET. Returns the HTTP status, or -5 invalid URL, -4 failure, -6 refused in demo
of_post (string as_url, string as_body) → longPOST with a body (JSON by default, built with n_pbt_json). Returns the HTTP status, or -5 / -4 / -6
of_put (string as_url, string as_body) → longPUT with a body. Returns the HTTP status, or -5 / -4 / -6
of_patch (string as_url, string as_body) → longPATCH with a body. Returns the HTTP status, or -5 / -4 / -6
of_delete (string as_url) → longDELETE. Returns the HTTP status, or -5 / -4 / -6
of_request (string as_method, string as_url, string as_body) → longAny method (METHOD_*, or your own), any body sent as UTF-8: what the five shortcuts call. Returns the HTTP status, or -5 / -4 / -6
of_download (string as_url, string as_path) → longGET straight into a file, bytes untouched, whatever the size. Returns the HTTP status, or -5 invalid URL or empty path, -4 failure, -6 refused in demo
of_response_text ( ) → stringThe body of the last answer, as text (UTF-8 decoded)
of_response_headers ( ) → stringEvery header of the last answer, one per line
of_response_header (string as_name) → stringOne header of the last answer, by name; empty when it was not sent
of_json_value (string as_key) → stringOne value of the JSON body by its key, the first occurrence at any depth. For more than one value, read of_response_text and n_pbt_utils.of_json_get_*
of_reset ( )Back to the defaults: no header, no base URL, the default timeout, the last answer forgotten

Examples #

Reading a list and walking it #

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

Downloading a document #

long ll_status
ll_status = lnv_rest.of_download("/orders/4152/invoice.pdf", "C:\temp\invoice-4152.pdf")
if ll_status = 200 then
	Run("C:\temp\invoice-4152.pdf")
elseif ll_status < 0 then
	MessageBox("Download", lnv_rest.is_last_error)
end if

An API that answers with an error #

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

Asynchronous (no UI freeze) #

The synchronous calls above block the script (the UI stays alive but the line waits). The asynchronous API does not even block the script: the request runs on a worker thread and its answer comes back later as an event.

Setup — nothing to wire: the component delivers the answer on its own in ue_response. of_open / of_close open and close the client (optional: the first async call opens it), of_clear_cookies forgets the cookies kept between requests. Internally the component's pump calls of_process_events to drain the answers — you never call it.

Send — of_request_async(method, url, body), or the shortcuts of_get_async, of_post_async, of_put_async, of_patch_async, of_delete_async, return a request id at once. of_download_async(url, path) downloads to a file and of_upload(url, field, path) uploads a file as multipart/form-data — both with progress. of_cancel(id) stops an in-flight request. Retries (il_max_retries, il_retry_backoff_ms) replay on 429/503/network failure.

Receive — ue_response(al_id, al_status) (read the body with of_response_text(al_id) / of_status(al_id) INSIDE the handler), ue_failed(al_id, as_error), ue_progress(al_id, al_done, al_total).

MethodRole
of_open ( ) → longOpens the async client, a native event channel (no WebView, no CORS). Optional: the first async call opens it. Returns the client id (> 0), or a negative code
of_close ( )Closes the async client: its cookies (the session) go with it. The next async call reopens it. Done for you by of_reset and on destroy
of_process_events ( )Drains the async answers and raises ue_response / ue_failed / ue_progress. Public ONLY because the component's own pump calls it on the PowerBuilder loop, every few milliseconds while a request is in flight — you never call it, and you wire neither a receiver nor a timer. Calling it yourself is harmless: it only empties what is already waiting
of_clear_cookies ( )Forgets the cookies kept between requests (a logout). The ib_keep_cookies setting is unchanged
of_get_async (string as_url) → longAsync GET. Returns a request id at once; the answer comes in ue_response(id, status). Returns -5 on an invalid URL, -2 when the client cannot open
of_post_async (string as_url, string as_body) → longAsync POST with a body (JSON by default, built with n_pbt_json). Returns a request id at once; the answer comes in ue_response. Returns -5 / -2 like of_get_async
of_put_async (string as_url, string as_body) → longAsync PUT with a body. Returns a request id at once; the answer comes in ue_response. Returns -5 / -2 like of_get_async
of_patch_async (string as_url, string as_body) → longAsync PATCH with a body. Returns a request id at once; the answer comes in ue_response. Returns -5 / -2 like of_get_async
of_delete_async (string as_url) → longAsync DELETE. Returns a request id at once; the answer comes in ue_response. Returns -5 / -2 like of_get_async
of_request_async (string as_method, string as_url, string as_body) → longAsync request with the HTTP method of your choice (of_get_async and its siblings call it). Returns a request id at once; the answer comes in ue_response, a failure in ue_failed. Returns -2 when the client cannot open
of_download_async (string as_url, string as_path) → longAsync GET straight to a FILE, with ue_progress along the way. Returns a request id, -5 on an empty path, -2 when the client cannot open
of_upload (string as_url, string as_field, string as_path) → longUploads a FILE as multipart/form-data (form field as_field), async, with ue_progress. Returns a request id, -5 on an empty path, -2 when the client cannot open, -6 in demo mode
of_cancel (long al_id) → longAsks an in-flight request to stop; it ends with ue_failed(id, "cancelled"). Returns 0, or -1 when the id is unknown
of_status (long al_id) → longThe HTTP status of an async answer, by request id - read it INSIDE ue_response. Returns the status (200, 404...), 0 when the request is unknown or already forgotten
EventRole
ue_response (long al_id, long al_status)An async request finished: al_id is the id returned when sending, al_status the HTTP status. Read the body with of_response_text(al_id) INSIDE the handler: the request is forgotten once the event returns
ue_failed (long al_id, string as_error)An async request failed: no network, timeout, TLS, or cancelled by of_cancel (as_error is then cancelled)
ue_progress (long al_id, long al_done, long al_total)Progress of an async download (of_download_async) or upload (of_upload): al_done bytes out of 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

Good practice #

← Component reference · Guide contents