PBToolboxAI v4 ← Site

restclient — n_pbt_restclient #

← Referência dos componentes · Índice do guia

Um cliente HTTPS para PowerBuilder, versão 10 incluída: GET, POST, PUT, PATCH, DELETE, cabeçalhos, um corpo JSON, a transferência de um ficheiro. Nativo — o TLS, o proxy e a descompressão do sistema, e nenhum muro CORS, porque o pedido não parte de uma página.

▶ Ver ao vivo — Aplicação de demonstração, mosaico REST client: as respostas, o código que as obtém e esta página, lado a lado (ligação à Internet necessária).


Em resumo #

Objeto não visualn_pbt_restclient
Serve paraChamar uma API REST a partir de uma aplicação PowerBuilder: ler, criar, atualizar, transferir
PrincípioUm pedido é uma chamada que devolve o estado HTTP; a resposta espera no objeto, lida por of_response_text ou of_json_value
DependênciaWinHTTP, na DLL da biblioteca — sem página, sem runtime adicional

Início rápido #

// 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

Cada método HTTP devolve o estado que o servidor escolheu — um 404 é uma resposta, não um erro — ou um código negativo quando o pedido não foi concluído: -5 URL ou cabeçalho inválido, certificado de cliente não encontrado, -4 falha (sem rede, nome desconhecido, tempo esgotado, TLS, um corpo de texto com mais de 64 MB — 16 MB num processo de 32 bits; is_last_error diz qual), -6 recusado em modo demo.


Porquê nativo, e não a página #

Um fetch() lançado a partir de uma página da biblioteca está sujeito a CORS: toda a API que não envie Access-Control-Allow-Origin fica-lhe fechada, e as API de empresa não o enviam. O pedido parte por isso da DLL, por WinHTTP: o TLS do sistema com os seus certificados, o proxy configurado, gzip. O que perde: nada; o que ganha: todas as API.


Propriedades #

PropriedadeTipoPredefiniçãoFunção
is_base_urlstring""Posto à frente de um URL relativo: is_base_url = "https://api.example.com" e depois of_get("/orders/4152"). Um URL absoluto é usado tal como está
il_timeout_mslong30000Quanto pode durar cada etapa de um pedido: resolução do nome, ligação, envio e espera de cada bloco da resposta. Uma transferência grande que avança nunca é cortada; um servidor mudo durante esse tempo é: -4 e «timed out». 0 ou menos vale a predefinição, 30 s: não existe «sem limite de tempo»
is_last_errorstring""Porque é que o último pedido síncrono — ou uma chamada assíncrona recusada de imediato — devolveu um código negativo. Vazio após um pedido que chegou ao servidor — um 404 não é um erro. Uma falha assíncrona diz o motivo em ue_failed
il_statuslong0O estado HTTP do último pedido síncrono, ou o seu código negativo; o valor que o pedido devolveu. Uma resposta assíncrona tem o seu: of_status(id)
ib_keep_cookiesbooleantrueGuarda os cookies de um pedido para o outro — síncronos e assíncronos, que partilham uma mesma sessão: uma API que define um cookie no início de sessão mantém-no ligado. A false, nenhum cookie é enviado nem guardado, e os já guardados são esquecidos; aplicado de imediato
il_max_retrieslong0Quantas vezes um pedido assíncrono é repetido perante 429, 503 ou uma falha de rede. Um POST cuja resposta se perdeu nunca é repetido: pode ter sido processado. Uma falha que se repetiria de forma idêntica nunca é repetida: demasiados redirecionamentos, redirecionamento inválido, redirecionamento HTTPS → HTTP recusado, falha TLS, ficheiro impossível de escrever
il_retry_backoff_mslong500A espera antes da primeira nova tentativa, em milissegundos, duplicada a cada tentativa e limitada a 60 s. Um cabeçalho Retry-After do servidor (segundos ou data) tem prioridade
ib_windows_authbooleanfalseAutenticação integrada do Windows (Negotiate / NTLM): quando um servidor a pede — uma API IIS da intranet —, respondem as credenciais da sessão Windows, sem escrever nada. Desativada por predefinição: sem ela nunca são enviadas, para nenhum servidor. Lida em cada pedido
is_client_certificatestring""Um certificado de cliente (TLS mútuo): a impressão digital SHA-1 de um certificado do arquivo pessoal — o do utilizador, depois o da máquina —, tal como o gestor de certificados a mostra; espaços e dois pontos ignorados. Vazio = nenhum. Uma impressão digital não encontrada faz com que todo o pedido devolva -5 («client certificate not found»). Lida em cada pedido. Não existe opção para ignorar um erro de certificado do servidor
ipo_ownerpowerobjectnullO objeto visual para o qual este cliente trabalha: a licença verifica-se na sua classe. Necessário apenas na aplicação de demonstração; uma chave de desenvolvimento ou de execução desbloqueia o cliente sem ele. Não desbloqueado, o cliente está em modo demo — corpos cortados a 4096 caracteres, nem transferência nem envio

Constantes: METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE para of_request.


Métodos #

MétodoFunção
of_set_header (string as_name, string as_value) → longUm cabeçalho enviado com cada pedido a partir de agora; um nome já definido é substituído, um valor vazio retira o cabeçalho. Content-Type é acrescentado sozinho quando um corpo parte sem ele (JSON, UTF-8); num envio de ficheiro, o definido aqui é ignorado (multipart/form-data é definido pela biblioteca). Nenhum destes cabeçalhos segue um redirecionamento para outro anfitrião. Devolve 0, ou -5 — nada muda — para um nome vazio, um nome com um espaço, dois pontos ou um carácter de controlo, ou um valor com uma quebra de linha
of_remove_header (string as_name)Esquece um cabeçalho, por nome (sem distinção de maiúsculas)
of_clear_headers ( )Esquece todos os cabeçalhos, autenticação incluída
of_set_bearer (string as_token) → longAuthorization: Bearer em cada pedido. Uma só autenticação de cada vez: substitui uma autenticação Basic; um token vazio retira a autenticação. Devolve 0, ou -5 para um token com uma quebra de linha — não fica então nenhuma autenticação, nunca meia
of_set_basic (string as_user, string as_password) → longAutenticação Basic; a DLL codifica ela própria o base64 — o PowerBuilder 10 não o tem. Substitui um token Bearer; um utilizador vazio retira a autenticação. Devolve 0, ou -5 para um utilizador ou uma palavra-passe com uma quebra de linha — não fica então nenhuma autenticação
of_get (string as_url) → longGET. Devolve o estado HTTP, ou -5 URL ou cabeçalho inválido, certificado de cliente não encontrado, -4 falha, -6 recusado em demo. O fragmento #… de um URL nunca é enviado ao servidor
of_post (string as_url, string as_body) → longPOST com um corpo (JSON por defeito, construído com n_pbt_json). Devolve o estado HTTP, ou -5 / -4 / -6
of_put (string as_url, string as_body) → longPUT com um corpo. Devolve o estado HTTP, ou -5 / -4 / -6
of_patch (string as_url, string as_body) → longPATCH com um corpo. Devolve o estado HTTP, ou -5 / -4 / -6
of_delete (string as_url) → longDELETE. Devolve o estado HTTP, ou -5 / -4 / -6
of_request (string as_method, string as_url, string as_body) → longQualquer método (METHOD_*, ou o seu), qualquer corpo enviado em UTF-8: o que os cinco atalhos chamam. Devolve o estado HTTP, ou -5 / -4 / -6
of_download (string as_url, string as_path) → longGET diretamente para um ficheiro, bytes intactos, qualquer que seja o tamanho. Devolve o estado HTTP, ou -5 URL ou caminho vazio, -4 falha, -6 recusado em demo. Um caminho relativo é resolvido na chamada na pasta atual da aplicação no carregamento da biblioteca — nunca onde um DirList ou uma caixa de ficheiros a tenha movido desde então
of_response_text ( ) → stringO corpo da última resposta síncrona deste objeto — outro cliente não o substitui —, em texto: descodificado segundo o charset anunciado pelo servidor (UTF-8, e os conjuntos históricos: windows-125x, iso-8859-x, shift_jis, gbk/gb2312, big5, euc-kr, koi8-r…), UTF-8 por predefinição (um byte inválido torna-se um carácter de substituição). Vazio após uma falha, e após of_download: esse corpo está no ficheiro. Um corpo de texto está limitado a 64 MB, 16 MB num processo de 32 bits: além disso, -4 «body too large» — use of_download
of_response_headers ( ) → stringTodos os cabeçalhos da última resposta síncrona, um por linha
of_response_header (string as_name) → stringUm cabeçalho da última resposta síncrona, por nome; vazio se não foi enviado
of_json_value (string as_path) → stringUm valor do corpo JSON pelo seu caminho — chaves unidas por /, posições de array a partir de 1, a notação de n_pbt_json: "customer", "json/customer", "items/1/qty". Uma cadeia volta descodificada, um número ou true/false tal como escritos ("4152"), null vazio, um objeto ou um array como JSON; vazio se o caminho não existir. Para mais de um valor, carregue of_response_text num n_pbt_json. O corpo é analisado uma só vez por resposta: ler dez valores custa uma única análise
of_reset ( )Regresso aos valores por omissão: nenhum cabeçalho, nenhum URL de base, o tempo por omissão, nenhuma nova tentativa, cookies guardados; o cliente é fechado — os seus cookies vão com ele, os seus pedidos em curso são cancelados sem qualquer evento — e a última resposta é esquecida. A autenticação Windows é desligada (ib_windows_auth = false) e o certificado de cliente esquecido (is_client_certificate = "")

Exemplos #

Ler uma lista e percorrê-la #

// 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

Transferir um 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

Uma API que responde com erro #

// 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

Uma API de intranet com autenticação 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

Assíncrono (sem congelar a interface) #

Durante uma chamada síncrona a interface fica congelada até à resposta: a janela deixa de se redesenhar e de responder aos cliques. Não tem consequências numa chamada curta; para qualquer chamada que possa demorar — um servidor lento, um corpo grande, uma rede incerta —, use a API assíncrona: o pedido parte numa thread de trabalho e a sua resposta volta mais tarde como evento. Síncrono e assíncrono partilham a mesma sessão: o cookie definido por um início de sessão síncrono serve as chamadas assíncronas que se seguem, e vice-versa; uma chamada síncrona nunca espera atrás dos pedidos assíncronos do mesmo objeto.

Preparação — nada a ligar: o componente entrega sozinho a resposta em ue_response. of_open / of_close abrem e fecham o cliente (facultativo: o primeiro pedido abre-o), of_clear_cookies esquece os cookies guardados de um pedido para o outro. Internamente, uma bomba do componente chama of_process_events para drenar as respostas — nunca a chama.

Enviar — of_request_async(method, url, body), ou os atalhos of_get_async, of_post_async, of_put_async, of_patch_async, of_delete_async, devolvem de imediato um id de pedido — ou -5 de imediato, sem nenhum evento depois, para um URL ou um cabeçalho inválido (is_last_error diz porquê, por exemplo «invalid URL: …»). Seis pedidos no máximo partem ao mesmo tempo; os seguintes esperam a sua vez numa fila. of_download_async(url, path) transfere para um ficheiro e of_upload(url, field, path) envia um ficheiro em multipart/form-data — ambos com progresso. of_cancel(id) pára um pedido. As tentativas (il_max_retries, il_retry_backoff_ms) repetem em 429/503/falha de rede.

Receber — ue_response(al_id, al_status) (ler o corpo com of_response_text(al_id) / of_status(al_id) DENTRO do handler), ue_failed(al_id, al_code, as_error), ue_progress(al_id, al_done, al_total). Um id só vale para o objeto que enviou o pedido. Um erro de execução num destes eventos não é engolido: chega ao evento SystemError da aplicação como qualquer erro de script, e as respostas seguintes são entregues na mesma.

MétodoFunção
of_open ( ) → longAbre o cliente: a sua sessão (cookies partilhados pelas chamadas síncronas e assíncronas) e o seu canal de eventos nativo (sem WebView, sem CORS). Opcional: o primeiro pedido abre-o. Devolve o id do cliente (> 0), ou -2 se não foi possível criá-lo (is_last_error diz porquê)
of_close ( )Fecha o cliente: os seus cookies (a sessão) vão com ele, e os seus pedidos ainda em curso são cancelados sem qualquer evento — nem ue_response, nem ue_failed. O pedido seguinte reabre-o. Feito por si por of_reset e na destruição. A última resposta síncrona do objeto é esquecida com ele
of_process_events ( )Distribui as respostas assíncronas e levanta ue_response / ue_failed / ue_progress. Pública APENAS porque o pump interno do componente a chama no ciclo PowerBuilder, de poucos em poucos milissegundos enquanto um pedido está em curso — nunca a chama, e não liga nem recetor nem temporizador. Chamá-la você mesmo é inofensivo: só esvazia o que já está à espera
of_clear_cookies ( )Esquece os cookies guardados de um pedido para o outro (um fim de sessão). Guardar ou não cookies a seguir continua a depender de ib_keep_cookies
of_get_async (string as_url) → longGET assíncrono. Devolve de imediato um id de pedido; a resposta chega em ue_response(id, status). Devolve -5 de imediato — e nenhum evento se segue — para um URL ou um cabeçalho inválido (is_last_error diz porquê), -2 se o cliente não se puder abrir
of_post_async (string as_url, string as_body) → longPOST assíncrono com um corpo (JSON por defeito, construído com n_pbt_json). Devolve de imediato um id de pedido; a resposta chega em ue_response. Devolve -5 / -2 como of_get_async
of_put_async (string as_url, string as_body) → longPUT assíncrono com um corpo. Devolve de imediato um id de pedido; a resposta chega em ue_response. Devolve -5 / -2 como of_get_async
of_patch_async (string as_url, string as_body) → longPATCH assíncrono com um corpo. Devolve de imediato um id de pedido; a resposta chega em ue_response. Devolve -5 / -2 como of_get_async
of_delete_async (string as_url) → longDELETE assíncrono. Devolve de imediato um id de pedido; a resposta chega em ue_response. Devolve -5 / -2 como of_get_async
of_request_async (string as_method, string as_url, string as_body) → longPedido assíncrono com o método HTTP à sua escolha (of_get_async e os seus irmãos chamam-no). Devolve de imediato um id de pedido; a resposta chega em ue_response, a falha em ue_failed. Seis pedidos no máximo ao mesmo tempo, os seguintes esperam a sua vez. Devolve -2 se o cliente não abrir, -4 se o pedido não puder ser posto em fila, -5 de imediato — sem evento — para um URL ou um cabeçalho inválido
of_download_async (string as_url, string as_path) → longGET assíncrono diretamente para um FICHEIRO, com ue_progress pelo caminho. Devolve um id de pedido, -5 com um caminho vazio, -6 recusado de imediato em modo demo, -2 se o cliente não abrir. Um caminho relativo é resolvido na chamada, como para of_download
of_upload (string as_url, string as_field, string as_path) → longEnvia um FICHEIRO em multipart/form-data (campo de formulário as_field), em assíncrono, com ue_progress — sempre um POST, com esse único campo. Devolve um id de pedido, -5 com um caminho vazio, -6 recusado de imediato em modo demo, -2 se o cliente não abrir. Um ficheiro relativo é procurado na pasta atual, depois na da aplicação no carregamento da biblioteca, depois ao lado do exe. O Content-Type definido por of_set_header é ignorado: multipart/form-data é definido pela biblioteca
of_cancel (long al_id) → longPede a um pedido que pare; termina com ue_failed(id, -4, "cancelled"), de imediato se ainda esperava a sua vez. Devolve 0, -5 se o id for desconhecido ou pertencer a outro objeto — nada é cancelado —, -4 se for tarde demais: a resposta chegou e vem em ue_response
of_status (long al_id) → longO estado HTTP de uma resposta assíncrona, por id de pedido — a ler DENTRO de ue_response. Devolve o estado (200, 404…); 0 enquanto o pedido corre, para um pedido falhado (em ue_failed), para um id desconhecido, já esquecido ou de outro objeto
of_response_text (long al_id) → stringO corpo de uma resposta assíncrona, por id de pedido — a ler DENTRO de ue_response: o pedido é esquecido ao regressar do evento; vazio enquanto decorre; vazio também para o id de outro objeto
of_response_headers (long al_id) → stringTodos os cabeçalhos de uma resposta assíncrona, um por linha
of_response_header (long al_id, string as_name) → stringUm cabeçalho de uma resposta assíncrona, por nome (maiúsculas indiferentes)
of_json_value (long al_id, string as_path) → stringUm valor do corpo JSON de uma resposta assíncrona, por caminho — a mesma notação que of_json_value(as_path)
EventoFunção
ue_response (long al_id, long al_status)Um pedido assíncrono terminou: al_id é o id devolvido no envio, al_status o estado HTTP. Leia o corpo com of_response_text(al_id) DENTRO do handler: o pedido é esquecido à saída do evento
ue_failed (long al_id, long al_code, string as_error)Um pedido assíncrono falhou. al_code diz a natureza: -4 o pedido falhou (sem rede, tempo esgotado, TLS, corpo demasiado grande, ou cancelado por of_cancel — as_error vale então cancelled), -5 ficheiro inválido ou certificado de cliente não encontrado, -6 recusado em modo demo (um pedido Range); as_error diz porquê. of_status(al_id) devolve aí 0
ue_progress (long al_id, long al_done, long al_total)Progresso de uma transferência (of_download_async) ou de um envio (of_upload) assíncrono: al_done bytes de al_total (0 quando o servidor não anunciou o tamanho). Para além de 2 GB, os bytes já não cabem num 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

Boas práticas #

← Referência dos componentes · Índice do guia