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 visual | n_pbt_restclient |
| Serve para | Chamar uma API REST a partir de uma aplicação PowerBuilder: ler, criar, atualizar, transferir |
| Princípio | Um pedido é uma chamada que devolve o estado HTTP; a resposta espera no objeto, lida por of_response_text ou of_json_value |
| Dependência | WinHTTP, 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 #
| Propriedade | Tipo | Predefinição | Função |
|---|---|---|---|
is_base_url | string | "" | 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_ms | long | 30000 | Quanto 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_error | string | "" | 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_status | long | 0 | O 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_cookies | boolean | true | Guarda 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_retries | long | 0 | Quantas 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_ms | long | 500 | A 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_auth | boolean | false | Autenticaçã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_certificate | string | "" | 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_owner | powerobject | null | O 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étodo | Função |
|---|---|
of_set_header (string as_name, string as_value) → long | Um 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) → long | Authorization: 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) → long | Autenticaçã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) → long | GET. 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) → long | POST 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) → long | PUT com um corpo. Devolve o estado HTTP, ou -5 / -4 / -6 |
of_patch (string as_url, string as_body) → long | PATCH com um corpo. Devolve o estado HTTP, ou -5 / -4 / -6 |
of_delete (string as_url) → long | DELETE. Devolve o estado HTTP, ou -5 / -4 / -6 |
of_request (string as_method, string as_url, string as_body) → long | Qualquer 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) → long | GET 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 ( ) → string | O 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 ( ) → string | Todos os cabeçalhos da última resposta síncrona, um por linha |
of_response_header (string as_name) → string | Um cabeçalho da última resposta síncrona, por nome; vazio se não foi enviado |
of_json_value (string as_path) → string | Um 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étodo | Função |
|---|---|
of_open ( ) → long | Abre 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) → long | GET 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) → long | POST 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) → long | PUT 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) → long | PATCH 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) → long | DELETE 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) → long | Pedido 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) → long | GET 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) → long | Envia 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) → long | Pede 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) → long | O 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) → string | O 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) → string | Todos os cabeçalhos de uma resposta assíncrona, um por linha |
of_response_header (long al_id, string as_name) → string | Um cabeçalho de uma resposta assíncrona, por nome (maiúsculas indiferentes) |
of_json_value (long al_id, string as_path) → string | Um valor do corpo JSON de uma resposta assíncrona, por caminho — a mesma notação que of_json_value(as_path) |
| Evento | Funçã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 #
- Um cliente por API, com o seu
is_base_urle os seus cabeçalhos definidos uma vez: o código das chamadas só leva o caminho e o corpo. - O corpo constrói-se com
n_pbt_json, nunca por concatenação: uma aspa no nome de um cliente não parte nada. - Teste o estado, depois o código negativo:
>= 200 e < 300está bem,>= 400o servidor disse que não,< 0o pedido não partiu —is_last_errordiz porquê. - O tempo limite: 30 segundos por predefinição; um relatório pesado merece mais, uma verificação de presença menos.
- O que pode demorar vai em assíncrono: uma chamada síncrona congela a interface até à resposta.
- Um redirecionamento mantém o seu pedido: um 301 ou um 302 só reescreve em GET um POST — um PUT, um PATCH ou um DELETE é refeito tal como está, corpo incluído; um 303 segue em GET sem corpo. Uma
Locationrelativa (?page=2,../) é resolvida como um navegador o faria. - Um redirecionamento para outro anfitrião não leva nada seu: nenhum dos cabeçalhos definidos por
of_set_header(token, chave de API) o segue — só ficaContent-Type. Um redirecionamento HTTPS → HTTP é recusado. - Teste o que
of_set_header,of_set_bearereof_set_basicdevolvem quando o valor vem de uma introdução ou de um ficheiro: uma quebra de linha é aí recusada (-5) e nada é definido. Um cabeçalho recusado nunca parte a meio. - As credenciais Windows só partem se o pedir:
ib_windows_auth = truepara uma API de intranet com autenticação Windows, nunca num cliente que fala com a Internet. - Um corpo de texto está limitado a 64 MB, 16 MB numa aplicação de 32 bits: uma exportação volumosa lê-se com
of_download/of_download_async, que escrevem um ficheiro sem limite.