CRUD em AdvPL ASP – Parte 04

Introdução

No post anterior (CRUD em AdvPL ASP – Parte 03), foi implementada a consulta básica da Agenda em ADVPL ASP — pelo menos os quatro botões de navegação (Primeiro, Anterior, Próximo e Último). Agora, vamos ver como mostrar no Browse a imagem de cada contato, gravada no Banco de Dados.

Trocando o retorno para o Browse

Qualquer link apw solicitado ao Web Server do Protheus executará uma função AdvPL definida em uma configuração de pool de processos, que por default devem retornar uma string em AdvPL, que será interpretada pelo Web Browse como sendo texto HTML.

Este comportamento default é implementado pelo Web Server, que por default informa no Header HTTP de retorno da requisição a informação Content-Type: text/html — Quando utilizamos a função AdvPL HTTPCTTYPE(), dentro do processamento de uma requisição AdvPL ASP, nós podemos TROCAR o tipo do conteúdo de retorno. Por exemplo, se eu quiser retornar uma imagem do tipo PNG para o Web Browse, a partir de uma requisição de link .apw, basta eu chamar a função HttpCTType(“image/png”), e ao invés de retornar ao Browse um HTML, eu retorno o conteúdo (bytes) do arquivo da imagem.

Logo, vamos implementar o retorno da imagem de forma BEM SIMPLES. Primeiro, vamos aproveitar a requisição “agenda.apw”, e verificar se ela recebeu um identificador adicional na URL, que vamos chamar de IMGID. Este identificador vai conter o numero do registro do contato da agenda que nós gostaríamos de recuperar a imagem. E, no fonte WAGENDA.PRW, vamos acrescentar o seguinte tratamento, logo no inicio da função WAGENDA() — pouco depois de abrir a tabela ‘AGENDA” no Banco de Dados.

If !empty(HTTPGET->IMGID)
  DbSelectArea("AGENDA")
  Dbgoto( val(HTTPGET->IMGID) )
  cBuffer := AGENDA->IMAGE
  HTTPCTType('image/png') 
  Return cBuffer
Endif

Simples assm, caso seja recebida a URL agenda.apw?IMGID=nnn, o programa vai posicionar no registro correspondente da tabela de agenda, ler o conteúdo da imagem gravada no campo memo “IMAGE”, e retornar ela ao Browser, avisando antes pela função HTTPCTType() que o Browse deve interpretar este conteúdo como uma IMAGEM.

Agora, dentro do fonte WAGENDA.APH, que compõe a página da agenda, vamos fazer uma alteração na tag “img”, responsável por mostrar a imagem do contato atual da agenda.

<tr><td>
<%If HTTPPOST->_SHOWRECORD .and. !Empty(AGENDA->IMAGE) %>
<img style="width: 120px;height: 160px;" src="agenda.apw?IMGID=<%=cValToChar(recno())%>">
<% else %>
<img style="width: 120px;height: 160px;" src="./images/Agenda_3x4.png">
<% Endif %>
</td></tr>

Dentro do APH em questão, eu já estou com a tabela da Agenda aberta. Caso eu vá mostrar o registro de algum contato no Browse, e o campo de imagem deste contato possua conteúdo, eu coloco que a imagem deve ser buscada no endereço

agenda.apw?IMGID=<%=cValToChar(recno())%>

Desta forma, quando o Browse receber o HTML de retorno de uma página da Agenda, o Browse vai desenhar a tela, e na hora de mostrar a imagem, o Browse internamente dispara mais uma requisição para a a URL agenda.apw, informando via URL o identificador da imagem desejada.

Vou fazer um teste no Browse, por exemplo retornando a foto do registro 2 da agenda no meu ambiente, digitando direto a URL “http://localhost/agenda.apw?IMGID=2

WEB Agenda IMGID

Ao navegar na agenda, e posicionar no contato, a imagem passa a ser atualizada na tela, veja exemplo abaixo:

WEB Agenda Imagem

Conclusão

Utilizando a troca do retorno, eu consigo informar ao browser o que eu estou retornando, e isso me permite eu retornar por exemplo XML, Imagens, Documentos, o céu (e a String AdvPL) são o limite!

Agradeço novamente a audiência, e desejo a todos TERABYTES DE SUCESSO !!! 

Referências

 

CRUD em AdvPL ASP – Parte 03

Introdução

No post anterior (CRUD em AdvPL ASP – Parte 02), foi mostrado um pouco da forma como as páginas de uma aplicação WEB foram e são atualizadas e o que é o AJAX, e a primeira verão — apenas desing — da página da Agenda em HTML. Agora, vamos começar a adaptar alguns fontes da Agenda para utilizá-los no AdvPL ASP.

Separar processamento da interface

Este é um dos mandamentos do bom desenvolvimento de sistemas. O projeto do CRUD em AdvPL, com foco em uma agenda de contatos, foi escrito da forma mais simples de desenvolver um sistema: O mesmo código é responsável pela interface e pelo processamento e acesso aos dados.

Embora seja uma das formas mais simples e rápidas de desenvolver, o processamento depende das interface, e isso acaba dobrando o trabalho para você criar uma nova interface para fazer as mesmas tarefas. No exemplo do CRUD, o processo em execução é persistente, isto é, o SmartClient possui uma conexão com o Protheus Server, e nesta conexão o arquivo da agenda é mantido aberto e posicionado, e a conexão da aplicação é mantida junto com o seu contexto até o usuário finalizar a interface.

Já em uma aplicação Web, cada chamada de atualização de interface cria uma nova conexão com o servidor de aplicação, que pode ser atendida por qualquer um dos processos (também chamados de Working Threads, ou processos de trabalho), então não há persistência do contexto de execução. Existem apenas as variáveis de seção (HTTPSESSION), onde você pode armazenar informações que são exclusivas daquela seção de navegação, que como vimos nos tópicos sobre AdvPL ASP, podem ser usadas por exemplo para guardar a existência por exemplo de uma seção de login.

Logo, como o paradigma da nova interface não parte de uma conexão persistente, precisamos desamarrar o processamento da interface, e criar algum mecanismo de persistência de um mínimo de contexto, ou emular a persistência de um contexto, para que uma ação disparada pela interface saiba de onde partir para ser executada.

A separação entre processamento e interface será feita em doses homeopáticas, vamos primeiro fazer a implementação funcionar com o que temos disponível implementando alguns pontos por vez.

Iniciando o processo de consulta

Como já temos a agenda criada em AdvPL, e com uma base preenchida, vamos começar a interface WEB pela consulta dos dados da agenda. Partimos de uma tabela no banco de dados, com dois índices previamente criados. Vamos iniciar a visualização dos dados a partir do primeiro registro da tabela em ordem alfabética. Para isso, inicialmente vamos replicar (duplicar) alguns códigos na interface WEB, para realizar as operações de consulta.

Uma vez que seja mostrada ao usuário uma tela com os dados preenchidos, o processamento daquela requisição terminou. Logo, quando eu clicar por exemplo no botão “Próximo” — para mostrar o próximo registro da agenda — eu preciso pelo menos saber qual era o registro que estava sendo visualizado, para eu saber de onde partir para buscar o próximo registro.

Eu até poderia usar variáveis de seção (HTTSESSION), mas como o objetivo é minimizar o uso destas variáveis, eu posso criar dentro do meu formulário — por exemplo — um input em HTML, do tipo “hidden” — ou escondido — onde eu coloco o numero do registro atual que eu estou visualizando, no momento que eu gerei a página a ser retornada para o Browse.

Conectando com o DBAccess

Antes de mais nada, eu preciso que as minhas Working Threads tenham acesso ao Banco de Dados. Lembram-se da função U_ASPINIT(), executada para preparar cada um dos processos para AdvPL ASP? Vamos alterá-la para ela estabelecer logo de cara uma conexão com o DBAccess, que será mantida no processo e aproveitada por todas as requisições que precisarem usar o SGDB. Esta função fica no fonte ASPThreads.prw

User Function ASPInit()
Local nTopHnd

SET DATE BRITISH
SET CENTURY ON

nTopHnd := TCLInk()
If nTopHnd < 0 
  ConsoleMsg("ASPINIT - Falha de conexão "+cValToChar(nTopHnd))
  Return .F.
Endif

SET DELETED ON

ConsoleMsg("ASPINIT - Thread Advpl ASP ["+cValToChar(ThreadID())+"] Iniciada")

Return .T.

Reparem que apenas o trecho em negrito foi acrescentado na função. Agora, vamos implementar alguns controles no programa responsável pela agenda na interface AdvPL ASP — Fonte wagenda.prw

Implementando a consulta nos fontes

Dentro da User Function WAGENDA(), responsável pela interface HTML da Agenda para AdvPL ASP, vamos abrir o alias da tabela da Agenda, caso ele ainda não esteja aberto dentro do contexto do processo de trabalho (Working Thread) atual.

If Select("AGENDA") == 0 
   USE (cFile) ALIAS AGENDA SHARED NEW VIA "TOPCONN"
   DbSetIndex(cFile+'1')
   DbSetIndex(cFile+'2')
Endif
DbSelectArea("AGENDA")

Partimos de duas premissas. A primeira é que a tabela e os índices existem. E, a segunda é que, uma vez que eu deixe o alias da tabela aberto neste processo, as próximas requisições que precisarem consultar esta tabela vão encontrar ela já aberta.

As operações feitas no CRUD em AdvPL foram numeradas. Vamos seguir a mesma numeração. Como por hora estamos implementando apenas a consulta de dados, as operações são: 4 – Primeiro contato, 5 = Contato anterior, 6 = Próximo contato e 7 = Último contato. Para este controle, vamos usar um campo input de formulário escondido no HTML, chamado “OP”. Caso este campo não seja recebido na requisição, eu assumo que a agenda está sendo aberta, e que a operação default é “4” = Primeiro registro.

If empty(HTTPPOST->OP)
  // Caso nao tenha sido informada operação, a operação 
  // default é posicionar no primeiro registro da tabela
  // em ordem alfabérica
  HTTPPOST->OP := '4'
Endif

Antes do fonte chamar a montagem da tela da agenda, encapsulada no arquivo wagenda.aph — correspondendo a chamada de função H_WAGENDA() em AdvPL — vamos tratar as quatro opções possíveis.

If HTTPPOST->OP == '4' // Primeiro

  DBSetOrder(2)
  Dbgotop()
  HTTPPOST->_SHOWRECORD := .T.

ElseIf HTTPPOST->OP == '5' // Anterior

  DBSetOrder(2)
  Dbgoto( val(HTTPPOST->RECNO) )
  DbSkip(-1)
  IF bof()
    HTTPPOST->_ERRORMSG := 'Este é o primeiro contato.'
  Endif
  HTTPPOST->_SHOWRECORD := .T.

ElseIf HTTPPOST->OP == '6' // Próximo

  DbSetOrder(2) 
  Dbgoto( val(HTTPPOST->RECNO) )
  DbSkip() 
  If Eof()
    Dbgobottom()
    HTTPPOST->_ERRORMSG := 'Este é o último contato.'
  Endif
  HTTPPOST->_SHOWRECORD := .T.

ElseIf HTTPPOST->OP == '7' // Último

  DBSetOrder(2)
  Dbgobottom() 
  HTTPPOST->_SHOWRECORD := .T.

Endif

Dentro deste fonte nós criamos duas variáveis dentro do alias virtual HTTPPOST, chamadas de _SHOWRECORD e _ERRORMSG. Estas variáveis foram criadas desta fora para servirem como containers de troca de valores entre o programa AdvPL que estamos rodando agora, e a página AdvPL ASP que vamos rodar em seguida.

Cada operação já assume que o Alias da tabela de AGENDA está aberto, e realiza o posicionamento do registro correspondente. Para fazer o posicionamento no registro anterior ou no próximo do registro mostrado no Web Browse, vamos no AdvPL ASP criar um campo do tipo INPUT HIDDEN, escondido em um formulário de controle invisível, e cada requisição de POST feita para trazer um novo registro vai receber o registro que estava sendo visto no Browse naquele momento.

Implementando no AdvPL ASP

Agora que já preparamos as operações dentro do fonte AdvPL, vamos preparar o fonte AdvPL ASP para tratar a existência destes dados e as operações solicitadas. Primeiro, dentro do fonte WAGENDA.APH, na parte de Scripts no inicio da página, vamos acrescentar duas funções novas em JavaScript.

function onLoad()
{
<%If HTTPPOST->_SHOWRECORD %>
<% aStru := DbStruct() %>
<% For nI := 1 to fCount() %>
<% If aStru[ni][2] != 'M' %>
document.getElementById("I_<%=alltrim(fieldname(nI))%>").value = "<%=cValToChar(fieldget(nI))%>";
<% Endif %>
<% Next %>
<% Endif %>
<% IF !empty(HTTPPOST->_ERRORMSG) %>
window.alert("<%=HTTPPOST->_ERRORMSG%>");
<% Endif %>
};

function CallOp(nOp)
{
document.getElementById("I_OP").value = nOp;
var f = document.getElementById("F_STATUS"); 
f.action="agenda.apw"
f.submit();
}

A primeira função chama-se onLoad(), e vamos fazer o Browse chamá-la imediatamente após a carga da página, inserindo na tag de abertura do corpo da página HTML (body) a chamada desta função:

<body onload="onLoad()" style="font-family:Courier New;font-size: 12px;background-color:rgb(128,128,128);">

O corpo da função onLoad() será montado dinamicamente para cada requisição. Seu objetivo é, quando houver a visualização de um determinado registro, este script vai encontrar todos os campos INPUT da página, destinados a mostrar os valores dos campos da Agenda, e preencher estes campos com os valores lidos do Banco de Dados.

Já a função CallOp() será chamada em JavaScript pelos botões de navegação de consulta na página HTML. Os novos botões vão ficar assim:

<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(4)">Primeiro</a></td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(5)">Anterior</a></td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(6)">Próximo</a></td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="CallOp(7)">Último</a></td></tr>

E, finalmente, antes do final do corpo do HTML (/body), vamos acrescentar um formulário de controle, para através dele fazer as requisições via POST — para não deixar rastro, cache ou histórico no Web Browse — das operações solicitadas de navegação.

<form id="F_STATUS" action="agenda.apw" method="post">
<input type="hidden" id="I_OP" name="OP" type="text" value="">
<input type="hidden" id="I_RECNO" name="RECNO" type="text" value="<%=cValToChar(recno())%>">
</form>

Neste formulário vamos criar apenas dois campos de INPUT HIDDEN, um para guardar a operação a ser solicitada, e outro para guardar o registro que está atualmente sendo visualizado. No momento que eu pedir por exemplo o próximo registro, o botão “Próximo” vai executar a função javascript CallOp(6), que vai preencher o input “I_OP” com o valor “6” e submeter novamente o formulário para o link “agenda.apw

Desta forma, o programa U_WAGENDA() vai receber a operação e o registro anteriormente posicionado, reposicionar no registro, fazer a navegação, e se por um acaso não houver registro anterior ou próximo, será mostrada na tela uma mensagem de alerta no Browse, repare no finalzinho da função onLoad().

Implementação funcionando

Com tudo atualizado e compilado, o comportamento esperado do acesso ao link http://localhost/agenda.apw deve ser:

  • Caso o usuário não esteja logado na seção atual, será exibida a tela de login
  • Após o login, deve ser exibida a tela da agenda com o primeiro contato em ordem alfabética.
  • Os quatro botões de navegação devem funcionar atualizando a página inteira e trazendo o contato correspondente.

WEB Agenda 3

Houve mais uma alteração importante no arquivo WAGENDA.APH: Todos os campos INPUT do formulário, que antes estavam com a propriedade “disabled”, passaram para “readonly”. Caso um campo não esteja habilitado, ele sequer pode receber ou mostrar um valor novo.

Conclusão

Aos poucos vamos implementando as funcionalidades da agenda original. Normalmente eu começaria pelo mais complicado — API de Cadastro — porém, como o estado default da interface é a consulta, vamos começar por ela. No próximo post, vamos fazer a consulta atualizar a foto do contato da agenda, sem precisar gravar a imagem que está no banco de dados na pasta de publicações Web.

Agradeço a todos novamente as curtidas, compartilhamentos, comentários e afins, e lhes desejo TERABYTES DE SUCESSO !!!

Referências

 

CRUD em AdvPL ASP – Parte 02

Introdução

No post anterior (CRUD em AdvPL ASP – Parte 01), montamos um controle de login, usando um formulário em AdvPL ASP, e uma variável de seção (HTTPSESSION). E, para servir de base para a continuação do CRUD, foram publicados uma sequência de posts para abordar o “básico” do AdvPL ASP:

Agora, vamos começar a montar a interface em HTML e JavaScript para montar as funcionalidades da Agenda via WEB.

Design da Interface

Poderíamos usar algum framework Web, por exemplo o THF – Totvs Html Framework , mas por hora vamos fazer a tela usando o arroz-com-feijão das páginas WEB com AdvPL ASP, e de quebra ver um pouco mais sobre como as coisas funcionam por dentro, e ver algumas alternativas de desenvolvimento. A interface da agenda está sendo desenhada originalmente para ter um layout muito parecido com o layout do programa original, que roda pelo SmartClient.

A ideia é que a interface HTML permita realizar todas as operações a partir da mesma tela, porém cada operação que requer atualização de dados desta tela deve submeter uma requisição via POST para o Web Server, que receberá novamente a tela inteira para executar a ação desejada.

Atualização de Páginas

Nos primórdios da Internet, navegar em qualquer Web Site na Internet construído com HTML, não tinha a possibilidade de alteração ou atualização dinâmica de conteúdo. Imagine um Web Site com um layout de menu lateral, com vários links e opções, uma área de topo ou Header com um logotipo e algumas informações sobre a parte do site que você está navegando, e uma área de conteúdo mostrando duas ou três notícias. Caso você clicasse em um link para ver a próxima página de notícias, a tela inteira é apagada e recarregada, com os mesmos menus laterais, a mesma área de topo, e uma página nova de notícias.

Isso acabava tornando pesada a navegação em alguns Web Sites. Com a possibilidade de criação de páginas de FRAMES em HTML —  acho que a partir do Internet Explorer 4 — você poderia definir um lay-out com frames — por exemplo um superior, um lateral e um central, onde a carga de uma página de notícias apenas recarregava uma página do frame, colocando as notícias desejadas.

Com as melhorias feitas no JavaScript, e sendo possível alterar dinamicamente o conteúdo de um HTML já desenhado na tela do Web Browser, vários Web Sites usavam um frame “escondido” na tela, onde através dele era feita uma requisição de uma nova página. Nesta página, que na verdade não era visível — frame escondido — era carregado um JavaScript retornado pelo Web Server, para atualizar dinamicamente o conteúdo da página sendo mostrada no frame de conteúdo — esse sim visível — sem a necessidade de recarregar (ou fazer REFRESH) da página inteira.

Algum tempo depois, foram descobertas vulnerabilidades nos navegadores Web relacionados ao uso de Frames, que poderiam mascarar um Web Site malicioso que poderia usar o JavaScript para interagir — e roubar dados e credenciais por exemplo — com frames de outros domínios, e a mecânica de atualização dos Frames tornava mais complicado desde o desenvolvimento do site, até a usabilidade do usuário — como usar o botão Voltar do Browse ou mesmo fazer um BookMark.

AJAX – Seus problemas terminaram

Mais melhorias e implementações foram feitas no JavaScript, e surgiu  o AJAX  —  Asynchronous Javascript And XML. NA verdade, este recurso é a união de duas funcionalidades: Primeira, uma função assíncrona do JavaScript foi criada para enviar ou solicitar informações para um Web Server, sem a necessidade de recarga da página atual. E, quando o Web Server retornar os dados solicitados (não necessariamente precisa ser um XML, pode ser usado texto plano, JSON, …), um JavaScript é acionado para processar os dados da requisição, que podem ser usados para atualizar o conteúdo HTML da página sendo atualmente exibida ao usuário.

Vamos pegar por exemplo o FACEBOOK — Uma vez que você  entra no seu “Feed de Notícias”, a URL permanece a mesma, e conforme você vai rolando a tela para baixo, usando a barra de rolagem do lado direito do Browse, ou mesmo o botão “Scroll” presente hoje até nos modelos mais simples de Mouse, novos posts vão sendo trazidos para a sua tela, sem a necessidade de repintar a tela inteira.

É claro que podemos usar AJAX com AdvPL ASP, inclusive vamos abordar este assunto mais para a frente, no decorrer do desenvolvimento do CRUD em AdvPL ASP, por hora estamos entrando neste assunto para fins informativos e didáticos.

Página AdvPL ASP da AGENDA

Sem mais delongas, vamos ver como está a primeira versão — ainda não funcional, mas apresentável — da interface AdvPL ASP da Agenda — futuro arquivo “agenda.aph”

<!DOCTYPE html>
<html>
<head>
<meta charset="ANSI">
<title>Agenda em AdvPL ASP</title>
<style>
.agbutton {
display: inline-block;
text-decoration : none;
width: 120px;
height: 18px;
background: rgb(240, 240, 240);
text-align: center;
color: black;
padding-top: 4px;
}
.agget { 
display: block;
width: 110px;
height: 22px; 
color: black; 
padding-top: 6px; 
text-align: right;
}
.aginput { 
width: 320px;
height: 22px; 
color: black; 
padding-top: 0px; 
padding-right: 10px; 
text-align: left;
}
</style>
</head>

function doLogoff() { 
var f = document.getElementById("F_AGENDA"); 
f.action="/logoff.apw"
f.submit();
};

<body style="font-family:Courier New;font-size: 12px;background-color:rgb(128,128,128);">

<table align="left">
<tr>

<!-- Primeira Tabela - Opções e Imagem 3x4 -->
<td align="left" valign="top">
<table>
<tr><td><a class="agbutton" href="?Op=1">Incluir</a></td></tr>
<tr><td><a class="agbutton" href="?Op=2">Alterar</a></td></tr>
<tr><td><a class="agbutton" href="?Op=3">Excluir</a></td></tr>
<tr><td style="height: 22px;">&nbsp;</td></tr>
<tr><td><a class="agbutton" href="javascript:void(0)" onclick="doLogoff()">Sair</a></td></tr>
<tr><td style="height: 22px;">&nbsp;</td></tr>
<tr><td><img style="width: 120px;height: 160px;" src="./images/Agenda_3x4.png"></td></tr>
</table>
</td>

<!-- Segunda Tabela - Mais Opções -->
<td align="left" valign="top">
<table>
<tr><td><a class="agbutton" href="?Op=4">Primeiro</a></td></tr>
<tr><td><a class="agbutton" href="?Op=5">Anterior</a></td></tr>
<tr><td><a class="agbutton" href="?Op=6">Próximo</a></td></tr>
<tr><td><a class="agbutton" href="?Op=7">Último</a></td></tr>
<tr><td><a class="agbutton" href="?Op=8">Pesquisa</a></td></tr>
<tr><td><a class="agbutton" href="?Op=9">Ordem</a></td></tr>
<tr><td><a class="agbutton" href="?Op=10">Mapa</a></td></tr>
<tr><td><a class="agbutton" href="?Op=11">G-Mail</a></td></tr>
<tr><td><a class="agbutton" href="?Op=12">Foto 3x4</a></td></tr>
</table>
</td>

<!-- Terceira Tabela - Dados do Contato -->
<td align="left" valign="top"> 
<form id="F_AGENDA" action="#" method="post">
<table style="border-spacing: 1px; background: rgb(192,192,192);">
<tr><td colspan="2" style="width: 500px; height: 22px; color: white; padding-top: 4px; background: rgb(0,0,128);">&nbsp;Ordem ...</td></tr>
<tr><td class="agget">ID</td> <td class="aginput"><input id="I_D" type="text" name="ID" disabled size="6" ></td></tr>
<tr><td class="agget">Nome</td> <td class="aginput"><input id="I_NOME" type="text" name="NOME" disabled size="50" ></td></tr>
<tr><td class="agget">Endereço</td> <td class="aginput"><input id="I_ENDER" type="text" name="ENDER" disabled size="50" ></td></tr>
<tr><td class="agget">Complemento</td><td class="aginput"><input id="I_COMPL" type="text" name="COMPL" disabled size="20" ></td></tr>
<tr><td class="agget">Bairro</td> <td class="aginput"><input id="I_BAIRRO" type="text" name="BAIRRO" disabled size="30" ></td></tr>
<tr><td class="agget">Cidade</td> <td class="aginput"><input id="I_CIDADE" type="text" name="CIDADE" disabled size="40" ></td></tr>
<tr><td class="agget">UF</td> <td class="aginput"><input id="I_UF" type="text" name="UF" disabled size="2" ></td></tr>
<tr><td class="agget">CEP</td> <td class="aginput"><input id="I_CEP" type="text" name="CEP" disabled size="9" ></td></tr>
<tr><td class="agget">Fone 1</td> <td class="aginput"><input id="I_FONE1" type="text" name="FONE1" disabled size="20" ></td></tr>
<tr><td class="agget">Fone 2</td> <td class="aginput"><input id="I_FONE2" type="text" name="FONE2" disabled size="20" ></td></tr>
<tr><td class="agget">e-Mail</td> <td class="aginput"><input id="I_EMAIL" type="text" name="EMAIL" disabled size="40" ></td></tr>
<tr>
<td class="agget">&nbsp;</td>
<td>
<a class="agbutton" id="btnConfirm" href="?Op=13">Confirmar</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a class="agbutton" id="btnVoltar" href="?Op=14">Voltar</a>
</td>
</tr>
</table>
</form>

</td>
</tr>
</table>

</body>
</html>

Por hora, o resultado da apresentação desta tela no Web Browse pode ser visto abaixo:

Web Agenda 2

Ainda não há conteúdo dinâmico, as ações dos botões ainda estão sendo escritas, e a foto mostrada na tela é o arquivo “Agenda_3x4.png”, que por hora precisa estar dentro de uma pasta chamada “images” a partir da pasta raiz de publicação WEB (No caso do meu ambiente, a pasta images deve estar dentro do Path especificado na seção HTTP).

Pontos de Atenção

  • Por se tratar de uma rotina de consulta dinâmica, não há interesse em manter ou permitir que sejam feitos “BookMarks” das operações da Agenda, mesmo a de consulta. Por isso, boa parte das operações desta rotina vão requisitar o próprio link da agenda.apw passando parâmetros usando POST, e para isso os campos INPUT do formulário. E, inclusive, existe um tipo de campo INPUT que pode ser usado dentro do código HTML, mas que não é mostrado para o usuário, permitindo submeter valores adicionais dentro da requisição POST feita pelo Web Browse, onde podemos por exemplo usar JavaScript para colocar valor nestes campos. Veremos isso em detalhes no decorrer desta sequência de posts.
  • Mesmo que o JavaScript possa nos permitir colocar algumas validações no lado do Web Browse, como não submeter uma página com campos obrigatórios faltando, ou até mesmo implementar algumas verificações de consistência, não devemos confiar cegamente que isto será respeitado. Isto é, como qualquer script retornado ao Web Browse pode e vai ser interpretado pelo mesmo, qualquer um com um pouco de maldade no coração pode abrir uma interface de desenvolvedor no Web Browse, alterar ou desligar dinamicamente uma validação da página em questão, e burlar a validação ou consistência do lado do Client. Logo, sempre critique os dados vindos do Browse, pois eles não necessariamente podem vir da forma que você está esperando. Quer um exemplo simples? Você cria uma página de busca de itens em uma loja virtual, e permite no Browse que o cliente escolha receber os dados em páginas de 10, 20 ou 50 registros, e exige que o usuário informe um termo de busca com pelo menos três letras. Estes parâmetros são enviados junto da requisição de busca, que no servidor monta uma Query no banco, e limita os resultados baseado no número de linhas por página informado. Se esta validação estiver apenas no lado do cliente, um cidadão com más intenções pode burlar esta validação, e submeter uma busca pela letra “A”, estipulando um tamanho de página de  30 mil registros. Pronto, o Web Server vai disparar uma Query contra o banco de dados que vai retornar um volume medonho de registros, e o Web Server vai sofrer pra montar uma página de retorno que pode passar de 1 MB para retornar isso ao Browse.
  • Para evitar dores de cabeça desnecessárias, independente da metodologia ou ferramental aplicado, procure conhecer e seguir o principio “Keep It Simple” — MANTENHA SIMPLES — valorização da simplicidade e descarte de toda a complexidade desnecessária.

Conclusão

Devagar a gente chega lá. Fazer uma tela de login com dois campos é moleza… agora fazer uma tela com atualização dinâmica de um cadastro de 11 campos, 14 opções e uma foto 3×4, vamos por partes.

Referências

 

Protheus e AdvPL ASP – Parte 05

Introdução

No post anterior (Protheus e AdvPL ASP – Parte 04), vimos mais dois alias virtuais — HTTPHEADIN e HTTPHEADOUT. Agora, vamos ver o alias virtual HTTPCOOKIES, que permite lidar diretamente com Cookies de seção, e depois algumas propriedades estendidas ou campos reservados de cada um dos alias virtuais do AdvPL ASP.

Quer ler sobre esse assunto desde o início?
Veja os links de referência no final do post.

O que é um Cookie ?

Bem superficialmente, um Cookie é um fragmento de dados enviado por um Web Server ao Web Browser (ou Client), que é transmitido de volta pelo Client ao Web Server em próximas requisições. Recomendo a leitura do documento “Cookies HTTP“, ele explica bem os tipos de Cookies, escopo, persistência, utilização, e inclusive como evitar vulnerabilidades.

Um Cookie também é uma tupla chave/valor, e podemos transferir um ou mais Cookies do Servidor HTTP ao Web Browser, e receber seus valores de volta. Estas informações são trafegadas em chaves específicas dentro do Header da requisição HTTP. Para tornar mais fácil a manutenção destas informações, e você não precisar buscar as informações de Cookies e fazer a interpretação delas dentro da sua aplicação, o Protheus como Web Server faz este trabalho, e permite ler as informações de Cookies recebidos, e gravar novos cookies ou redefinir os valores dos Cookies a serem enviados como retorno ao Web Browse.

Alias Virtual HTTPCOOKIES

De forma similar aos demais alias virtuais, podemos recuperar valores de Cookies previamente retornados ao Web Browse, e e criar novas chaves com novos valores a serem armazenados no Web Browser, bem como trocar valores de cookies já existentes — desde que eles tenha sido criados pela nossa aplicação, e pertençam pelo menos ao mesmo host ou domínio — veja sobre escopo de Cookies no link “Cookies HTTP“.

No exemplo de requisição HTTP mostrado no post anterior (Protheus e AdvPL ASP – Parte 04), um dos campos do Header HTTP continha a seguinte informação:

Cookie: SESSIONID=41a79408567715479073622c71988001

Este Cookie foi criado internamente pelo Web Server do Protheus, para identificar a seção atual do usuário. Por isso ele foi recebido. Na primeira requisição que este usuário fez para o Web Server, após iniciar o Web Browser, este Cookie não existia, então o Web Server Protheus sabe que trata-se de uma nova seção, e cria um identificador para aquela seção, e retorna ele no header de retorno (HTTPHEADOUT) do HTTP.

Set-cookie: SESSIONID=41a79408567715479073622c71988001

Utilizando o alias virtual HTTPCOOKIES, eu poderia ler diretamente este cookie sem precisar fazer o tratamento do valor de HTTPHEADIN->COOKIE, que poderia inclusive conter mais de um Cookie. Por exemplo, para ler o cookie SESSIONID, eu utilizaria HTTPCOOKIES->SESSIONID. O Cookie SESSIONID é de uso interno do mecanismo de controle de sessões de usuário — inclusive das variáveis de seção ( HTTPSESSION ). Logo, não devemos mexer neste valor.

Vale lembrar que a soma de chaves e valores de Cookies têm capacidade limitada de armazenamento — eu pessoalmente não usaria nada maior que 256 bytes — lembrando que a transmissão destes valores em cada requisição podem acabar interferindo no peso e desempenho das requisições.

Propriedades estendidas dos Alias Virtuais

Agora que nós já vimos todos os alias virtuais disponíveis para o AdvPL ASP, vamos ver algumas propriedades estendidas de cada um deles, que podem ser muito úteis em várias situações.

HTTPGET->AGETS

O alias virtual HTTPGET possui um campo reservado, chamado “AGETS”. Trata-se de um array de strings, contendo em cada elemento o nome de um identificador de parâmetro informado na URL da requisição. Este campo montado dinamicamente pelo servidor Protheus antes de alocar um processo (Working Thread) para processar uma requisição de link .apw

Por exemplo, vejamos a URL abaixo:

http://localhost/info.apw?a=1&b=2&c

Ao processar esta requisição em AdvPL ASP, podemos recuperar o valor de cada um dos parâmetros informados no AdvPL, usando o alias virtual HTTPGET, desde que saibamos os nomes dos parâmetros, usando por exemplo:

cValorA := HTTPGET->A
cValorB := HTTPGET->B

Caso eu queira saber quais foram os identificadores usados na URL, eu posso consultar o campo HTTPGET->AGETS, por exemplo:

For nI := 1 to len(HTTPGET->AGETS)
  conout("GET "+cValToChar(nI)+" Identificador "+HTTPGET->AGETS[nI]
Next
// o resultado esperado no log de console do 
// servidor de aplicação é:
// GET 1 Identificador a
// GET 2 Identificador b
// GET 3 Identificador c

Reparem que “c” foi um identificador passado na URL, porém ele não teve valor atribuído. Agora, existe uma forma de recuperarmos os conteúdos destes identificadores, a partir dos nomes, usando macro-execução:

For nI := 1 to len(HTTPGET->AGETS)
  cIdent := HTTPGET->AGETS[nI]
  conout("GET "+cValToChar(nI)+" Id "+ cIdent + ;
  " Valor "+cValToChar( &("HTTPGET->"+cIdent) ) )
Next
// o resultado esperado no log de console do 
// servidor de aplicação é:
// GET 1 Id a Valor 1
// GET 2 Id b Valor 2
// GET 3 Id c Valor

HTTPPOST->APOST

Para o alias virtual HTTPPOST, existe o campo reservado “APOST“, com o mesmo propósito e funcionamento do AGETS. A diferença é que somente são informados valores nestes campos caso a requisição HTTP seja enviado o comando POST, o tipo de conteúdo da requisição seja “application/x-www-form-urlencoded“, e o corpo da requisição contenha uma ou mais tuplas chave/valor codificadas.

A mesma técnica utilizada para recuperar os valores associados aos identificadores recebidos em AGETS pelo alias virtual HTTPGET pode ser utilizada para recuperar os valores recebidos em APOST pelo alias virtual HTTPPOST.

HTTPHEADIN->AHEADERS

Para o alias virtual HTTPHEADIN, existe o campo reservado AHEADERS, criado com o propósito de tornar possível receber todas as linhas do cabeçalho da requisição HTTP vindas do Web Browse ou do Web Client que fez a solicitação. No caso, cada linha do cabeçalho HTTP, formado por uma tupla chave/valor, é retornada como um elemento deste array — e não apenas o nome do identificador, como é retornado por AGETS (do Alias HTTPGET) ou APOST (do alias HTTPPOST). Por exemplo:

<pre>
<% For nI := 1 to len(HTTPHEADIN->AHEADERS) %>
<%= HTTPHEADIN->AHEADERS[nI] %>
<% Next %>
</pre>

O trecho acima, dentro de um fonte APH, retornaria ao Browse em texto pré-formatado (ou fonte de tamanho fixa) todas as linhas do Header HTTP recebido pelo Web Server do Protheus para a requisição atual.

HTTPHEADIN->COMMAND

A primeira linha do HTTP Header enviada por um Web Browser ou Web Client do protocolo HTTP contém um comando — normalmente GET ou POST.  Para saber qual foi o comando recebido, podemos usar o campo reservado “COMMAND”.

HTTPHEADIN->MAIN

Ao receber uma requisição de link .apw, para processamento de AdvPL ASP, usamos o campo reservado “main”, do alias virtual HTTPHEADIN, alimentado apenas com o nome da página que foi requisitada, desconsiderando URL, domínio, path e parâmetros. Por exemplo, ao solicitar http://seuhostouip/suapasta/meulink.apw?a=1&b=2, o resultado de HTTPHEADIN->MAIN será apenas “meulink

HTTPHEADIN->CMDPARMS

De forma similar ao campo reservado MAIN, o campo CMDPARMS recebe o nome do link .apw chamado, e os eventuais parâmetros recebidos na URL, no formato de uma única string, onde apenas é removida a extensão “.apw” da requisição. Por exemplo, ao solicitar http://seuhostouip/suapasta/meulink.apw?a=1&b=2, o resultado de HTTPHEADIN->CMDPARMS será apenas “meulink?a=1&b=2

HTTPCOOKIES->ACOOKIES

Este campo reservado do alias virtual HTTPCOOKIES funciona exatamente igual ao AGETS e APOST. Ele traz apenas os nomes dos Cookies enviado pelo Cliente Web ao Web Server Protheus em uma requisição de AdvPL ASP.

<pre>
<% For nI := 1 to len(HTTPCOOKIES->ACOOKIES) %>
<%= HTTPCOOKIES->ACOOKIES[nI] %>
<% Next %>
</pre>

O trecho acima, dentro de um fonte APH, mostrará todos os nomes dos identificadores de Cookies recebidos pela requisição atual.

HTTPSESSION->SESSIONID

Funciona de forma similar ao HTTPCOOKIES->SESSIONID, porém com uma diferença: Na primeira requisição recebida pelo AdvPL ASP de uma nova seção do Web Browser, o identificador do SESSIONID vêm em branco, pois o Cookie identificador de seção ainda não foi retornado ao Browse. Já o HTTPSESSION->SESSIONID já vem preenchido com o identificador criado para esta seção antes mesmo dele ser retornado ao browse.

Exemplo COMPLETO

Vamos ver o que a podemos recuperar de uma requisição HTTP vinda de um Web Browse em AdvPL ASP, agora em um único exemplo? Primeiro, criamos um desvio na função U_ASPConn(), para executar a função H_ASPINFO() caso seja informado no browse a chamada para o link “aspinfo.apw“.

case cAspPage == 'aspinfo'
  cReturn := H_ASPINFO()

Agora, criamos o arquivo “aspinfo.aph“, com o seguinte conteúdo:

<html><body>
<pre>
HTTP HEADER INFO
<hr>
<% For nI := 1 to len(HTTPHEADIN->AHEADERS) %>
<%= HTTPHEADIN->AHEADERS[nI] %>
<% Next %>

Command .....: <%=HTTPHEADIN->COMMAND%>
Main ........: <%=HTTPHEADIN->MAIN%>
CmdParms ....: <%=HTTPHEADIN->CMDPARMS%>
Remote Addr .: <%=HTTPHEADIN->REMOTE_ADDR%>
Remote Port .: <%=HTTPHEADIN->REMOTE_PORT%>

<% If len(HTTPCOOKIES->ACOOKIES) > 0 %>
COOKIES
<hr>
<% For nI := 1 to len(HTTPCOOKIES->ACOOKIES) %>
<% cVar := HTTPCOOKIES->ACOOKIES[nI] %>
<%= cVar + " = [" + &("HTTPCOOKIES->"+cVar) + "]" %>
<% Next %>
<% Endif %>

<% If len(HTTPGET->AGETS) > 0 %>
GET PARAMS
<hr>
<% For nI := 1 to len(HTTPGET->AGETS) %>
<% cVar := HTTPGET->AGETS[nI] %>
<%= cVar + " = [" + &("HTTPGET->"+cVar) + "]" %>
<% Next %>
<% EndIf %>

<% If len(HTTPPOST->APOST) > 0 %> 
POST PARAMS
<hr>
<% For nI := 1 to len(HTTPPOST->APOST) %>
<% cVar := HTTPPOST->APOST[nI] %>
<%= cVar + " = [" + &("HTTPPOST->"+cVar) + "]" %>
<% Next %>
<% EndIf %>

REQUEST DETAILS
<hr>
HttpOtherContent() ......: <%=HttpOtherContent()%>
HttpRCTType() ...........: <%=HttpRCTType()%>
HttpRCTDisp() ...........: <%=HttpRCTDisp()%>
HttpRCTLen() ............: <%=HttpRCTLen()%>
</pre>
</body></html>

Agora vamos ver a mágica funcionando. Com tudo compilado e o Protheus no ar, entramos com a seguinte URL: http://localhost/aspinfo.apw?a=1Veja abaixo o resultado esperado no Web Browse:

HTTP HEADER INFO

GET /aspinfo.apw?a=1 HTTP/1.1
Host: localhost
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6
Cookie: SESSIONID=72b298eea3eed9544ad95459e833e7c2

Command .....: GET
Main ........: aspinfo
CmdParms ....: aspinfo?a=1
Remote Addr .: 127.0.0.1
Remote Port .: 52764

COOKIES

SESSIONID = [f62f4ac8afc9f1b3456faafc93551bc5]

GET PARAMS

A = [1]

REQUEST DETAILS

HttpOtherContent() ......: 
HttpRCTType() ...........: 
HttpRCTDisp() ...........: 
HttpRCTLen() ............: -1

E se fosse um POST ?

Claro, vamos ver o que eu receberia em um post. Aproveitando o exemplo de um post anterior, vamos editar o arquivo formpost.aph, e trocar o ACTION do formulário de “/postinfo.apw” para “/aspinfo,apw”, e ver  o que acontece.

<html><body>
<p>Formulário de POST</p>
<form action="/aspinfo.apw" method="post">
First name:<br>
<input type="text" name="firstname"><br>
Last name:<br>
<input type="text" name="lastname">
<hr>
<input type="submit" value="Enviar">
</form>
</body></html>

Agora, vamos abrir a URL http://localhost/formpost.apw, e no caso, eu vou digitar meu nome e sobrenome nos campos:

Web formpost

Após clicar no botão ENVIAR, o retorno esperado no Browse deve ser algo assim:

HTTP HEADER INFO

POST /aspinfo.apw HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 34
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost/formpost.apw
Accept-Encoding: gzip, deflate, br
Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6
Cookie: SESSIONID=fe5ab7f15a1b9788fd945bf14581af82

Command .....: POST
Main ........: ASPINFO
CmdParms ....: ASPINFO
Remote Addr .: 127.0.0.1
Remote Port .: 53500

COOKIES

SESSIONID = [f9d8c4a25dc307251bf05300f59bf3e4]

POST PARAMS

FIRSTNAME = [Júlio]
LASTNAME = [Wittwer]

REQUEST DETAILS

HttpOtherContent() ......: 
HttpRCTType() ...........: application/x-www-form-urlencoded
HttpRCTDisp() ...........: 
HttpRCTLen() ............: 34

Conclusão

Com este post, cobrimos o básico do AdvPL ASP. Nos próximos posts deste assunto, vamos abordar algumas capacidades de retorno diferenciado da interface HTTP, e como utilizá-las quando necessário. Afinal, podemos retornar ao Web Browser ou ao Client HTTP utilizado muito mais do que apenas HTML 😀

Agradeço novamente a audiência, e desejo a todos TERABYTES DE SUCESSO !!!! 

Referências

 

 

Protheus e AdvPL ASP – Parte 04

Introdução

Nos posts anteriores sobre AdvPL ASP, vimos até o momento três alias virtuais usados para receber parâmetros ( HTTPGET / HTTPPOST ) e controlar variáveis de seção de usuário (HTTPSESSION). Agora, vamos ver mais dois alias virtuais: HTTPHEADIN e HTTPHEADOUT.

Alias Virtual HTTPHEADIN

Cada requisição HTTP possui um formato interno, separada em cabeçalho (ou Header) e corpo (ou Body). Atenção, estamos falando do protocolo HTTP, e não do header e body de um formulário HTML.

Quando um Web Browse ou um cliente de aplicação WEB faz uma requisição via HTTP, esta requisição é acompanhada de algumas tuplas chave/valor, colocadas no cabeçalho da requisição, que indicam por exemplo o tipo da requisição (GET, POST, …), o tipo do conteúdo enviado na requisição (texto, html, imagem, …), a codificação do conteúdo (ANSI, UTF-8 ou outro CodePage), algumas informações sobre o Web Browse ou da aplicação Cliente que está fazendo a solicitação, entre outros identificadores de uso específico.

Usando o Google Chrome, por exemplo, temos uma janela especial chamada DevTools, onde temos ferramentas que permitem investigar por dentro uma requisição feita pelo Web Browse a um determinado servidor, os parâmetros da requisição e seu retorno. Após abrir a janela do DevTools e digitar a URL http://localhost/ , a requisição enviada ao Web Server foi:

GET / HTTP/1.1 
Host: localhost 
Connection: keep-alive 
Cache-Control: max-age=0 
Upgrade-Insecure-Requests: 1 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 
Accept-Encoding: gzip, deflate, br 
Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6 
Cookie: SESSIONID=41a79408567715479073622c71988001 
If-Modified-Since: Fri, 30 Nov 2018 00:30:20 GMT

Só nesta requisição existe um universo de informações. Não vou entrar nos méritos internos de cada uma, mas apenas nas que realmente importam para o momento, e na forma de recuperá-las.

Da mesma forma que os demais alias virtuais, o alias virtual HTTPHEADIN permite fazer a leitura de um determinado valor a partir do seu nome. Por exemplo:

HTTPHEADIN->CONNECTION deve retornar a string “keep-alive”
HTTPHEADIN->HOST deve retornar a string “localhost”

Agora, o que acontece se eu tentar recuperar o valor de “User-agent” ? HTTPHEADIN->USER-AGENT não é um formato válido para o AdvPL, o compilador vai entender que você quer pegar o valor HTTPHEADIN->USER e fazer uma subtração operação aritmética de subtração da variável “AGENT” , que não existe.

Pensando nesta possibilidade, qualquer caractere que faz parte do nome de uma chave no alias virtual HTTPHEADIN, que não seja válido como nome de variável ou de identificador, será trocado pelo caractere “_” (underline). Desse modo, para recuperar o valor de User-Agent, você deve usar HTTPHEADIN->USER_AGENT

HTTPHEADIN – Valores adicionais

Quando o Web Browse ou a aplicação cliente fez uma requisição HTTP ao Web Server, esta conexão foi aberta sobre o protocolo TCP/IP. Logo, mesmo que a requisição HTTP possa ser minimalista e não ter muitos detalhes, é possível obter dentro do prococolo TCP/IP qual é o IP de Origem da conexão, e qual é a porta TCP usada na conexão aberta pelo Cliente. O IP da Conexão, por exemplo, pode ser recuperado e gravado em um LOG de requisições ou de atividade do Web Site, para uso posterior em estatísticas ou mesmo auditoria de operações. Para obter estes valores, usamos respectivamente:

HTTPEADIN->REMOTE_ADDR
HTTPEADIN->REMOTE_PORT

Estes campos retornam respectivamente um valor “C” caractere, contendo o IP Client da Conexão, e um valor “N” numérico contendo a porta dinâmica TCP da máquina Client que fez a requisição.

Seguindo a mesma linha dos posts anteriores, vamos criar um arquivo chamado headinfo.aph, e dentro dele colocar o seguinte conteúdo:

<html><body>
<pre>
Host ........: <%=HTTPHEADIN->HOST%>
Connection...: <%=HTTPHEADIN->CONNECTION%>
User-Agent ..: <%=HTTPHEADIN->User_Agent%>
Remote Addr..: <%=HTTPHEADIN->REMOTE_ADDR%>
Remote Port..: <%=HTTPHEADIN->REMOTE_PORT%>
</pre>
</body></html>

E, dentro do fonte ASPThreads.prw, inserir mais uma entrada na lista de páginas, para ao receber o link “headinfo.apw”, processar e retornar a função H_HeadInfo() — que corresponde ao arquivo headinfo.aph compilado.

case cAspPage == 'headinfo'
  cReturn := H_HEADINFO()

E, ao testar esta chamada no meu ambiente, usando http://localhost/headinfo.apw, eu obtive o seguinte resultado na tela do Web Browse:

WEB HeadInfo

Agora, vamos ver o que acontece se eu acessar esta página usando um iPhone? Vejamos, primeiro eu preciso achar o IP da minha máquina na rede de casa. Como estou usando Wi-fi, basta eu usar o prompt de comando do Windows (cmd) e usar o comando IPCONFIG, e procurar o endereço IPV4 da interface de rede Wi-fi. Como meu sistema operacional está em Inglês, eu consigo usar alguns comandos adicionais para filtrar os resultados:

ipconfig | findstr /i "Address Adapter"

ipconfig 1

A interface e o valor que eu quero estão destacados em vermelho. Agora, acessando o Chrome do meu telefone, eu digito a URL

http://192.168.0.4/headinfo.apw

E com ela eu recupero as seguintes informações:

IMG_3661

Alias Virtual HTTPHEADOUT

Bem, da mesma forma que uma requisição HTTP é dividida em cabeçalho e corpo, uma resposta de uma requisição também têm esta mesma divisão. Quando você solicita através de um Web Browse ou Cliente HTTP um link de uma página em AdvPL ASP, o próprio Web Server do Protheus monta um cabeçalho de retorno da requisição, com alguns valores pré-definidos. Vou usar a mesma requisição do ultimo teste, e rodar ela com a janela do DevTools aberta. As informações que eu quero estão em uma aba chamada “Response Headers”, que devem mostrar algo assim:

HTTP/1.1 200 OK
Date: Fri, 30 Nov 2018 01:34:01 GMT
Server: Application Web Server
MIME-version: 1.0
Content-type: text/html
Last-modified: Fri, 30 Nov 2018 01:34:01 GMT
Set-cookie: SESSIONID=68c0be02ceaec73ddfcf14281c4f1909 
X-Frame-Options: SAMEORIGIN
Content-Length: 284

Este cabeçalho de retorno foi montado pelo Web Server do Protheus, logo que a página AdvPL ASP foi procecssada, e a função ASPConn() retornou a string cHTML. A requisição foi processada com sucesso (código de retorno HTTP 200), a data de processamento, o retorno deve ser tratado pelo Browser como um texto html (definido pelo Content-type), e o tamanho do HTML retornado foi de 284 bytes.

Lembram-se no post anterior, quando eu falei de SESSIONS de usuário? O Web Server do Protheus retorna um Cookie de memória chamado SESSIONID, gerado para esta instância de Browse, para saber nas próximas requisições que se trata da mesma instância e/ou usuário. Pode comparar este valor com o valor recebido demonstrado no exemplo dos Headers da requisição HTTP — os valores estão diferentes, pois entre um teste e outro, eu fechei o Browser e abri novamente. Mais para frente vamos ver um alias virtual que permite lidar com Cookies de memória, então vamos entrar neste assunto novamente e com maior profundidade.

Voltando ao alias virtual HTTPHEADOUT, ele foi criado para que você possa, durante o processamento de uma requisição de AdvPL ASP, criar um novo valor de retorno, para ser acrescentado no Header de retorno da requisição HTTP para o Web Browser ou Cliente que fez a solicitação. PAra ver a lista de campos mais usadas, veja os links de referência no final do post.

Normalmente você não precisa utilizar diretamente o alias HTTPHEADOUT, inclusive por que ele têm a restrição de não permitir a criação de campos de retorno usando por exemplo o símbolo “-“, existem outras funções feitas especificamente para mexer em valores default. O uso projetado para este alias virtual é a possibilidade de criar um header com um nome de identificador exclusivo, para uma integração entre sistemas por exemplo.

Conclusão

Falta pouco para cobrir o básico do AdvPL ASP. Se você começou a ler sobre este assunto agora, pegue os posts desde o primeiro da sequência de AdvPL ASP, o entendimento de boa parte do conteúdo dos posts daqui para a frente requer esta leitura.

Agradeço novamente a audiência, e desejo a todos TERABYTES DE SUCECSSO !!! 

Referências

CRUD em AdvPL ASP – Parte 01

Introdução

Nos posts anteriores sobre o CRUD em AdvPL, o programa de exemplo partiu de uma agenda de contatos, escrita originalmente para ser executado via SmartClient. Agora, vamos aproveitar algumas partes do “núcleo” do programa agenda.prw, e criar uma interface de manutenção para WEB, usando AdvPL ASP. Eu recomendo fortemente que você, caso ainda não tenha lido, leia os posts sobre AdvPL ASP e CRUD, eles estão acessíveis através das respectivas categorias no menu inicial superior do BLOG, e também nas referências no final deste post.

Agenda em AdvPL ASP

Inicialmente, vamos aproveitar o programa aspthreads.prw, que serve de casca para execução das nossas aplicações em AdvPL ASP, para desviar a execução do código para um novo fonte, chamado wagenda.prw, quando a URL informada for http://localhost/agenda.apw

Para isso, de posse do fonte aspthreads.prw, dentro da função U_ASPConn(), criamos uma nova entrada no DO CASE para a agenda, acrescentando as linhas abaixo:

  case cAspPage == 'agenda'
    // Executa a agenda em AdvPL ASP 
    // Os controles e páginas estão encapsulados pela função U_WAgenda()
    cReturn := U_WAGENDA()

Agora, vamos criar o fonte wagenda.prw, por partes, para ele funcionar como uma máquina de estado de tela única, de forma similar ao que fizemos com o programa no SmartClient. Algumas funções do fonte agenda.prw deverão ser alteradas, para serem visíveis para este novo fonte. Inicialmente, precisamos de uma tela APH para desenhar a interface da agenda. Vamos começar o fonte wagenda.prw de forma simples, e ir incrementando ele aos poucos.

#include "protheus.ch"

User Function WAgenda()
Local cHtml := ''
If empty(HTTPSESSION->LOGIN)
  // Usuário ainda não está logado. 
  // Retorna para ele a tela de login
  cHtml := H_WLogin()
Else
  cHtml := H_WAgenda()
Endif
Return cHtml

Aqui fazemos uso de uma variável de SESSION que o programa mesmo vai criar, para exigir que apenas um usuário autenticado — que passou primeiro pela página de LOGIN — tenha acesso à agenda. Para maiores detalhes sobre o funcionamento das SESSIONS no AdvPL ASP, consulte o post Protheus e AdvPL ASP – Parte 03.

Caso o usuário abra diretamente a página da agenda (http://localhost/agenda,apw), na verdade ele var receber uma tela HTML com um formulário de Login. Vamos ver como esta tela seria — arquivo wLogin.aph

*** OBSERVAÇÃO : Por hora os arquivos APH dos exemplos abaixo estão todos usando o CODEPAGE ANSI (ou CP1252) , e foram criadas manualmente usando o IDE do Protheus. Estas páginas também utilizam o padrão HTML5. PAra pbter mais detalhes sobre como o Web Browse trata as versões de HTML e JavaScript, uma excelente fonte de informações é o site W3SCHOOLS

<% 
/* ----------------------------------------------------------------
Login da Agenda
---------------------------------------------------------------- */ 
%> 
<!DOCTYPE html>
<html>
<head>
<meta charset="ANSI">
<title>LOGIN</title>
<style>
html, body { height: 100%; } 
html { display: table; margin: auto; }
body { display: table-cell; vertical-align: middle; }
.agbutton {
display: inline-block;
text-decoration : none;
width: 120px;
height: 18px;
background: rgb(240, 240, 240);
text-align: center;
color: black;
padding-top: 4px;
}
.agget { 
display: block;
width: 110px;
height: 22px; 
color: black; 
padding-top: 6px; 
text-align: right;
}
.aginput { 
width: 320px;
height: 22px; 
color: black; 
padding-top: 0px; 
padding-right: 10px; 
text-align: left;
}
</style>

function doLogin() { document.getElementById("F_LOGIN").submit(); };

</head>
<body style="font-family:Courier New;font-size: 12px;background-color:rgb(128,128,128);">

<form id="F_LOGIN" action="/login.apw" method="post">
<table style="border-spacing: 1px; background: rgb(192,192,192);">
<tr><td colspan="2" style="width: 500px; height: 22px; color: white; padding-top: 4px; background: rgb(128,0,0);">
<center>LOGIN</center></td></tr>
<tr><td class="agget">Usuário</td> <td class="aginput"><input id="I_USER" type="text" name="USER" size="50" ></td></tr>
<tr><td class="agget">Senha</td> <td class="aginput"><input id="I_PASS" type="password" name="PASS" size="32" ></td></tr>
<% If !empty(HTTPPOST->_ERRORMSG) %>
<tr><td colspan="2" style="width: 500px; height: 22px; color: white; padding-top: 4px; background: rgb(128,0,0);">
<center><%=HTTPPOST->_ERRORMSG%></center>
</td></tr>
<% Endif %>
<tr>
<td class="agget">&nbsp;</td>
<td>
<a class="agbutton" id="btnConfirm" href="javascript:void(0)" 
  onclick="doLogin()">Confirmar</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a class="agbutton" id="btnVoltar" href="/">Voltar</a>
</td>
</tr>
</table>
</form>
</body>
</html>

Somente nesta tela nós temos a utilização de vários recursos do Web Browser, inclusive JavaScript, e recursos do AdvPL ASP. Vamos ver um por um, e por quê foi optado por ser feito desta forma.

Formulário HTML

Inicialmente, para ser possível o Web Browse realizar um POST informando campos e valores informados pelo usuário para o Web Server, precisamos criar um formulário HTML usando a tag form, e dentro do corpo do formulário colocar tags de input para permitir o usuário informar valores para os campos determinados.

<form id="F_LOGIN" action="/login.apw" method="post">

Damos a este formulário um identificador (id), para poder nomear este formulário para ele ser tornar-se acessível de forma nomeada para um script em JavaScript, que vai ser executado dentro da página. Você escolhe o nome do formulário, eu resolvi usar um prefixo “F_” para saber dentro dos meus fontes que este componente é um form.

Outra escolha importante foi o método do formulário. Eu poderia usar um formulário do tipo GET, mas isto não seria nada elegante para uma tela de login, por várias razões. Primeira, os formulários com método GET colocariam os nomes e valores dos campos input na URL. Isso expõe a senha do usuário na URL. E, os Web Browses costumam fazer cache e permitir Bookmark de URL passando parâmetros via GET. Nada bom para uma operação de autenticação.

Quando usamos POST, o Browse não faz cache dos dados submetidos, e os dados são enviados internamente do Web Browse ao Web Server, dentro do corpo da requisição HTTP.

Links e JavaScript

Existem componentes no HTML, como o button, que permitem a criação de botões em um Web Site. Os botões normalmente disparam ações em JavaScript, e podem ter seu layout alterado usando CSS (ou folhas de estilo). Eu poderia usar botões, porém opter por usar o componente âncora (a) do HTML, usando CSS para dar a aparência de um botão, e internamente usar o evento de disparo (onclick) do componente para chamar uma função JavaScript declarada dentro da minha página, vide exemplo do botão de Login.

<a class="agbutton" id="btnConfirm" href="javascript:void(0)" onclick="doLogin()"

Já o botão para voltar à página principal do site, usa o componente âncora (a) apontando diretamente para a URL raiz do site “/”, sem usar javascript.

<a class="agbutton" id="btnVoltar" href="/">Voltar</a>

Outro detalhe interessante é o uso de CSS. Para você que já ouviu falar nisso, basicamente eu posso criar uma classe de layout, ou estender classes já existentes dos componentes padrão, para mudar a forma de apresentação destes componentes. As definições de estilo são feitas dentro da tag style, onde a definição pode estar dentro do póprio HTML, ente as tuplas <style> e </style>, ou mesmo em um arquivo separado, onde usamos um parâmetro da tag style para indicar onde está o arquivo. No momento, o estilo está dentro da página atual. Assim que acrescentarmos mais páginas ao projeto, colocamos as definições de estilo comuns em um arquivo de estilo separado, e fazemos referência a este arquivo nas páginas da aplicação.

Validação do Login

A ação do formulário, isto é, a URL para a qual a requisição de POST gerada no momento que o formulário for submetido também é importante. No caso, vamos chamar a aplicação login,apw, responsável por receber os campos do POST (Usuário e Senha)  deste formulário, para verificar se o usuário deve ter acesso ou não para a página da agenda. Para isso, acrescentamos mais uma entrada no programa ASPThreads.prw, vide abaixo:

case cAspPage == 'login'
  cReturn := U_WLOGIN()

E, para realizar a tarefa de validação do login, vamos criar o arquivo wlogin.prw, que vai conter a função U_WLOGIN() — e inclusive uma de Logoff.

#include "protheus.ch"

User Function WLogin()
Local cHtml := ''
Local cUser := ''
Local cPass := ''

If HTTPPOST->USER != NIL .and. HTTPPOST->PASS != NIL
  // Houve um POST , informando usuario e senha 
  // Valida usuario e senha informados 
  cUser := Upper(alltrim(HTTPPOST->USER))
  cPass := HTTPPOST->PASS
  If cUser == "ADMIN" .AND. cPass == ""
    // Usuário logado com sucesso 
    // Alimenta a session LOGIN 
    HTTPSESSION->LOGIN := cUser
  Else
    // Informa mensagem de erro usando o alias virtual HTTPPOST
    HTTPPOST->_ERRORMSG := "Usuário ou senha inválidos."
  Endif
Endif

If empty(HTTPSESSION->LOGIN)
  // Usuário ainda não está logado, retorna a tela de login
  cHtml := H_WLogin()
Else
  // Usuário atual está logado. Redireciona ele para a agenda
  HTTPSRCODE(307,"Login Redirection")
  HTTPHEADOUT->Location := "/agenda.apw"
  Return ""
Endif
Return cHtml

/* -------------------------------------------------------
Logoff de Usuario 
Retorna Limpa as variaveis de session do usuario 
e retorna a página de indice do site 
------------------------------------------------------- */
USER Function WLogOff()
HTTPFREESESSION()
return H_INDEX()

Olhando ao mesmo tempo a página wlogin.aph, e a função u_wlogin(), reparem que ambas usaram um campo do alias virtual HTTPPOST, que não estava em nenhum formilário — HTTPPOST->_ERRORMSG — onde a criação deste campo é feita diretamente no alias virtual HTTPPOST, dentro da aplicação, e a verificação da existência e do conteúdo deste campo é feita dentro do arquivo windex.aph.

Sim, eu posso criar em tempo de programação um identificador e um conteúdo, nos alias curtuais HTTPGET e HTTPPOST, e depois veremos outros casos onde isto também é possível. Desta forma, ao invés de precisarmos declarar variáveis PRIVATE dentro de um fonte prw para enviar dados de programa para um aph, criamos um ou mais campos no alias virtual HTTPPOST por exemplo, tomando o cuidado destes campos não conflitarem com nomes de campos vindos do Web Browse ou de formulários.

Como isso vai funcionar — passo a passo

O usuário abre o Web Browse e entra com a URL http://localhost/agenda.apw . A função U_ASPConn() do fonte ASPThreads.prw será chamada, e vai executar a função U_WAgenda()

case cAspPage == 'agenda'
  cReturn := U_WAGENDA()

A função U_WAgenda() vai consultar a HTTPSESSION->LOGIN, que não existe ainda, indicando que o usuário ainda não foi autenticado, retornando para ele o conteúdo do arquivo wlogin.aph . Este arquivo retorna uma página de login centralizada no Web Browse, solicitando informar usuário e senha.

Web Login

Após informar o usuário ADMIN e a senha em branco, e clicar no botão Confirmar, a ação do formulário será submeter os campos digitados em uma requisição POST, para a URL http://localhost/login.apw, que por sua vez vai chamar a função U_WLOGIN()

case cAspPage == 'login'
  cReturn := U_WLOGIN()

Dentro da função U_WLOGIN(), uma vez verificado que houve o envio de parâmetros de POST na requisição, verificado pelo recebimento dos campos USER e PASS, declarados no formulário HTML, caso o usuário e senha informados sejam aptos de realizar a operação, a HTTPSESSION->LOGIN será criada e alimentada com o ID do usuário. Caso contrário, a variável de POST _ERRORMSG será criada com uma mensagem de erro de Login. Uma vez que o usuário esteja logado com sucesso, retornamos para o Browse mediante instruções especiais — que ainda não foram vistas por aqui — para redirecionar o Browse a abrir a página da agenda no link “/agenda.apw”, que por sua vez retornará a página ainda em construção wAgenda.aph.

Web Agenda.png

Revisão de Conceitos

  1. Qualquer página com link .apw  será processada pela função U_ASPCONN(), que recebe o nome da página no alias virtual HTTPHEADIN->MAIN. No nosso caso, existe um “DO CASE” com uma lista de páginas e as suas respectivas funções para chamada.
  2. Qualquer requisição feita do Browse via URL é do tipo GET, e pode passar parâmetros via URL.
  3. Um formulário HTML, ao ser submetido, pode fazer um GET ou um POST, dependendo do método configurado no form.
  4. Um formulário do tipo POST também pode passar parâmetros via URL, colocados no action do formulário.
  5. O Web Browse apenas estabelece uma conexão HTTP com o Application Server para fazer uma requisição, encerrando a conexão automaticamente após o retorno — ou antes do retorno em caso de time-out ou cancelamento da requisição pelo usuário.
  6. Mesmo que você use alguma validação no Client — por exemplo funções JavaScript — para evitar que dados inconsistentes sejam enviados ao Servidor, não deixe de fazer as consistências da recepção dos dados no Advpl ASP. Pessoas mal intencionadas podem tentar submeter conteúdos inválidos para tentar burlar comportamentos, causar danos ou mesmo indisponibilidade de serviço.
  7. Um processamento AdvPL ASP dentro de um arquivo APH deve ser usado para montar conteúdo dinâmico para ser apresentado e/ou processado no Web Browse. Tudo o que estiver entre as tags <% , <%= e %> será processado somente no momento em que o servidor receber a requisição, cujo retorno será enviado ao Web Browse como uma página HTML.

Conclusão

Os posts anteriores sobre AdvPL ASP são a base para esta nova etapa do Crud, recomendo a leitura deles para uma melhor compreensão dos conceitos aqui apresentados, bem como uma leitura extra sobre o protocolo HTTP. A página da agenda ainda não foi publicada, pois está em construção, aguardem que eu ainda não coloquei todos os ingredientes na cumbuca … risos …

Agradeço a todos os comentários, compartilhamentos e likes, e desejo a todos(as) TERABYTES DE SUCESSO !!! 

Referências

 

 

Protheus e AdvPL ASP – Parte 03

Introdução

No post anterior, Protheus e AdvPL ASP – Parte 02, vimos dois alias virtuais, usados para receber parâmetros do Browse, a partir de requisições GET e POST — são eles o alias virtual HTTPGET e HTTPPOST, respectivamente. Agora vamos os demais alias virtuais disponíveis no AdvPL, começando pelo HTTPSESSION.

Alias virtual HTTPSESSION

É possível criar dinamicamente variáveis em um container global do AdvPL ASP, cujo escopo seja a instância do navegador ou Web Browse utilizado — ou em outras palavras,  “sessions de usuário”. Para isso, usamos o alias virtual HTTPSESSION.

O armazenamento no alias virtual HTTPSESSION é feito dinamicamente, na forma de tuplas chave=valor, onde damos o nome a um campo virtual, e atribuímos a ele uma informação. Por exemplo:

HTTPSESSION->USERNAME := "José"

Para consultarmos se um determinado campo virtual existe e/ou têm conteúdo, fazemos referência a ele usando da mesma forma o alias virtual HTTPSESSION, vide exemplo:

IF Empty(HTTPSESSION->USERNAME)
  conout("Session USERNAME vazia ou inexistente")
Else
  conout("Session USERNAME = " + cValToChaR(HTTPSESSION->USERNAME) )
Endif

Como o AdvPL ASP identifica o usuário?

Uma vez que um usuário abra um navegador, e solicite ao Protheus Server uma página qualquer com link .apw, que fará um processamento de AdvPL ASP, um Cookie de Memória (recurso do Web Browse para armazenar uma tupla chave=valor durante a navegação) é usado para identificar a seção (usuário) atual.

Quando você acabou de abrir o Browse, e fez a primeira requisição de link .apw para o AdvPL ASP, o Protheus Server não vai receber este identificador, então ele cria um identificador novo para a seção atual — aquela instância de Web Browse acessando o site — e retorna este identificador ao Web Browse como um “Cookie de Memória”. O Web Browse, por sua vez, a partir deste momento, e enquanto ele estiver aberto, envia de volta esse identificador como uma informação no cabeçalho HTTP de cada requisição GET ou POST que ele fizer para o Protheus Server.

Escopo e Tempo de Vida das Sessions

Uma vez que você atribua um conteúdo para uma variável de session, este conteúdo é gravado na memória da instância atual do Protheus Server, e somente será possível recuperá-lo através de um código AdvPL executado dentro de uma Working Thread do AdvPL ASP, que foi feita a partir da mesma instância de Web Browser, que fez a gravação da informação e respectivo identificador.

Todas as informações (identificadores e conteúdos) gravados para um determinado e distinto usuário, permanecerão na memória do servidor por tempo indeterminado, desde que este usuário não deixe de fazer uma requisição ao Protheus Server em até 10 minutos. Após 10 minutos sem atividade em um conjunto de dados de HTTPSESSION atrelado a um usuário, os identificadores e conteúdos serão descartados — apagados da memória. Isto não muda o identificador interno da seção daquele usuário.

Este tempo de 10 minutos — ou 600 segundos — é o valor DEFAULT da configuração SESSIONTIMEOUT, que permite definir o tempo de permanência máximo por inatividade do conjunto de variáveis de session por usuário — vide links de referência no final do post.

Onde eu uso variáveis de SESSION?

O uso mais comum são propriedades e parâmetros exclusivos que a aplicação permite definir para um ou mais usuários distintos que estão navegando no Web Site ou Aplicação WEB em questão. Por exemplo, um uso muito comum é a identificação de acesso de usuário, ou Login”.

Imagine que várias páginas dinâmicas da aplicação escrita em AdvPL ASP pode ser acessada por qualquer pessoa — acesso público e irrestrito. Porém, determinadas operações feitas através de determinados programas deste Web Site possuem acesso restrito, onde o usuário que estiver navegando deve fornecer algum tipo de informação para identificar-se na aplicação, e tentar garantir que ele “é quem diz ser”.

Nas páginas ou funções onde esta autenticação ou Login é necessária, podemos verificar se uma determinada SESSION — por exemplo HTTPSESSION->LOGIN possui conteúdo. Esta SESSION somente será criada se o usuário passar pelo processo de Login da Aplicação Web, normalmente usando uma página exclusiva na aplicação para esta finalidade. E, em cada função ou página que requer identificação ou é de acesso restrito, caso a SESSION de LOGIN não esteja definida, podemos lhe informar uma mensagem de “Acesso restrito a usuários inscritos”, e direcioná-lo a uma tela de cadastro ou a uma tela de Login.

O Que eu posso guardar em SESSION ?

Nobre desenvolvedor, você armazenar em campos do alias virtual HTTPSESSION qualquer valor básico do AdvPL, EXCETO “B” (Blocos de Código ou Code-Blocks) e “O” (Objetos ou Instâncias de Classe). O resto, inclusive Array, pode.

Agora, preste a atenção no seguinte: Um usuário ou internauta navegando no seu Web Site em AdvPL ASP, pode simplesmente parar de navegar por qualquer razão. E, durante a navegação, cada requisição de URL vinda do Web Browse é atendida por uma conexão estabelecida entre o Web Browse e o Protheus Server, que é encerrada após o processamento e envio dos dados ao Browse. Trata-se de uma conexão não-persistente.

Logo, se você coloca um botão ou link de “LOGOFF” no seu site, e o usuário realmente clica neste botão, você pode disparar uma função dentro do AdvPL para limpar manualmente todas as variáveis de SESSION deste usuário (HTTPFreeSession). Porém, se o usuário não clicar neste botão e simplesmente fechar o Web Browse, toda a memória consumida por aquele usuário, atrelada a um identificador exclusivo da seção que ele estava navegando, ficarão na memória do Protheus Server até que passe os dez minutos de INACTIVETIMEOUT, ou o tempo de inatividade configurado.

Se você, para um determinado usuário, usou 1 MB de memória para armazenar informações de SESSION, esta memória será ocupada por até 10 minutos a mais do que o usuário está realmente usando no Web Site. Ao aumentar o INACTIVETIMEOUT para valores maiores, aumentamos o tempo de retenção dessa memória. Aproveitando este exemplo, de 1 MB de consumo por usuário, e INACTIVETIMEOUT de 30 minutos. Das 12:00 às 12:10, 500 usuários navegaram no site, dos quais 100 entraram na área restrita e usaram SESSIONs. Em 10 minutos, 100 MB de uso de memória. Entre 12:10 e 12:20, entraram mais 50 usuários na área restrita, e 50 usuários que entraram às 12:00 fecharam o browse e foram almoçar. Logo, eu tenho agora (12:20) 100 usuários acessando a área restrita, mas estou mantendo na memória um total de 150 sessions, de todos os usuários que entraram desde às 12:00. Das 12:20 até 12:30 saíram e entraram mais 50 usuários, às 12:30 eu tenho o mesmo volume de 100 usuários online acessando páginas restritas, mas estou usando 200 MB para armazenar 200 variáveis de SESSION, 100 dos usuários ativos no momento, e as outras 100 que foram criadas desde o meio dia por usuários que já saíram do site.

Boas práticas de Sessions

Só existe uma boa prática de sessions: Evite usar sessions para guardar valores para qualquer pessoa navegando na aplicação WEB ou Web Site. Procure usar somente para guardar o que realmente é imprescindível, apenas para os usuários que precisam disso, como por exemplo uma informação de login ou alguma preferência diferenciada entre usuários.

Se você pretende criar uma aplicação WEB em AdvPL ASP, algo cujo tamanho e quantidade de acessos simultâneos não seja suportado por apenas uma instância única do Protheus como servidor WEB, então monte sua aplicação para não usar variáveis de SESSION, ou na verdade até pode usar, mas prefira utilizar uma abordagem que possibilite por exemplo a execução de requisições não exija “afinidade” — aplicações STATELESS por exemplo. Dessa forma, não importa em qual servidor a sua requisição seja processada, você consegue verificar a sua validade sem depender de um contexto. Se você usa variáveis de SESSION e resolve subir mais de uma instância de Protheus Server, usando um proxy reverso ou NLB (Network Load Balance), e uma requisição cria uma variável de SESSION quando foi processada no Servidor 1, caso a próxima requisição vá consultar a existência dessa variável seja direcionada para o Servidor 2, este servidor não conhece as sessions do Servidor 1, e vai tratar a requisição como se a Session realmente não existisse.

Conclusão

Embora este tópico não tenha visualmente um exemplo palpável, ele é necessário para a implementação em AdvPL ASP de outro tópico em desenvolvimento, sobre o CRUD em ADVPL ASP, onde vamos criar e usar uma SESSION para controle de login de usuário.

Por hora, apenas agradeço a todos(as) pela audiência e desejo a todos(as) TERABYTES DE SUCESSO 😀

Referências