Boas Práticas de Programação – Código Simples, Resultados Rápidos

Introdução

Este tópico está entre “Dicas Valiosas” e “Boas Práticas de Programação”, de qualquer modo aborda um exemplo de código, ainda em desenvolvimento por um colega que está iniciando no “Mundo do AdvPL”. Partindo deste fonte de exemplo, vamos analisar o que o fonte faz, como faz, e inclusive avaliar algumas opções de escrever um algoritmo que faça a mesma coisa, usando funções diferentes ou escritos de uma outra forma, e se existe algum ganho ou perda, direto ou indireto ao optarmos por uma ou outra forma de resolução.

O Fonte em AdvPL – Primeira versão

#INCLUDE "PROTHEUS.CH"
#INCLUDE "TOTVS.CH"
#INCLUDE "FILEIO.CH"

USER FUNCTION TESTE()
  
        local nArquivo
        local i
        local nNumero
        local nNum := 7 

        msgAlert("Início.")

        nArquivo := fcreate("C:\devstudio\Melhorias\src\arquivo.txt")

        if ferror() != 0
                msgalert ("ERRO GRAVANDO ARQUIVO, ERRO: " + str(ferror()))
                return
        endif 
        for i:=1 to 500
                nNumero = randomize(100000,999999)
                if (mod(nNumero,nNum)) == 0 //dividendo e divisor respectivamente
                        fWrite(nArquivo, "O numero < " + alltrim(str(nNumero)) + " > é divisível por " + alltrim(str(nNum)) + chr(13) + chr(10))
                else
                        fwrite(nArquivo, "O numero <" + alltrim(str(nNumero)) + "> não é divisível " + alltrim(str(nNum)) + chr(13) + chr(10))
                Endif
        next    
        fclose(nArquivo)

        msgAlert("Fim.")
Return

Engenharia Reversa

Olhando o fonte linha por linha, podemos deduzir o que ele faz ao ser executado. Inicialmente, o fonte declara as variáveis utilizadas com escopo “Local”, mostra uma mensagem na tela do SmartClient informando que o programa está no “Início”, após pressionar OK ou o simplesmente fechar a janela a aplicação continua, criando um arquivo em um path específico na máquina onde está sendo executado o SmartClient –que pode ser a mesma máquina onde o SmartClient pode estar sendo executado, ou uma outra máquina conectando neste Protheus Server via REDE TCP/IP.  — Arquivo “C:\devstudio\Melhorias\src\arquivo.txt””.

A aplicação usa fCreate() para criar o arquivo, verifica se houve falha na criação verificando o retorno da função fError(), faz um loop de 500 iterações,  sorteia um número entre 100000 e 999999, e escreve no arquivo o número sorteado usando fWrite(), e antes de escrever determina se o número sorteado é múltiplo de 7 ou não, usando a função Mod().

Primeira Revisão

Logo nas primeiras linhas do arquivo, foram usados dois includes: “protheus.ch” e “totvs.ch”. Internamente, ambos são o mesmo arquivo. Na verdade, o include totvs.ch inclui internamente o arquivo protheus.ch. Foi criado um include com o nome de “totvs.ch” apenas para padronização de código. Logo, tanto faz qual deles for usado, precisamos somente de um deles.

O include fileio.ch possui algumas constantes que opcionalmente podem ser usadas pelas funções de baixo nível de arquivo, como a fCreate(), fOpen() e fSeek(). No caso do fonte acima, como nenhuma constante foi usada, fazer referência a este arquivo também é desnecessário.

Quanto a verificação de erro de criação de arquivo, normalmente verificamos se o resultado da função fCreate() é diferente de -1 (menos um), pois este é o valor retornado em caso de falha da função. Porém, não há nada de mais em usar a função fError() — desde que logo após a chamada da fCreate() — pois em caso de sucesso, fError() retorna 0 (zero).

Dento do laço for…next de 500 iterações, cada volta sorteia um número e verifica se ele é ou não múltiplo de 7. Quando queremos uma conversão de um valor numérico para string, sem espaços adicionais, podemos usar tranquilamente a expressão alltrim(str(nNumero)). De qualquer modo, no AdvPL temos uma função básica da linguagem que realiza exatamente este tipo de conversão, inclusive para outros tipos de dados de entrada, resultando em uma string — chama-se cValToChar(). A utilização dela neste cenário apenas economiza a chamada de funções — ao invés de duas, realizamos a conversão com apenas uma.

Na declaração de variáveis, foi usada corretamente a notação húngara — a primeira letra do nome da variável indica o seu tipo. Isto é “praxe” em AdvPL, ajuda bastante. E, outra coisa que ajuda é colocar nomes que tenham relação com o conteúdo. Olhando o fonte, sabemos que nNumero foi gerado por sorteio, e nNum é o número para verificar se o primeiro é múltiplo dele. Porém, ficaria mais “elegante” nomear nNum com, por exemplo “nDivTst” — divisor de teste.

Outros modos de fazer a mesma coisa

Existem duas formas de verificar se um número tem resto inteiro após uma divisão por outro. Podemos usar a função “mod()” ou o operador “%“, o resultado é o mesmo, ambos estão certos.

if ( mod(nNumero, nNum )  ==  0)
if ( ( nNumero % nNum ) == 0 )

 

Porém, existe também uma forma de redução de código, que eu particularmente acho feia, pois dificulta a legibilidade do código, e não há ganho NENHUM em desempenho, por exemplo:

if (mod(nNumero,nNum)) == 0 //dividendo e divisor respectivamente
   fWrite(nArquivo, "O numero < " + alltrim(str(nNumero)) + " > é divisível por " + alltrim(str(nNum)) + chr(13) + chr(10))
else
   fwrite(nArquivo, "O numero <" + alltrim(str(nNumero)) + "> não é divisível " + alltrim(str(nNum)) + chr(13) + chr(10))
Endif

O código acima poderia ser escrito de outra forma, com a mesma funcionalidade, porém com uma sintaxe totalmente diferente.

fWrite(nArquivo, ;
    "O numero <" + cValToChar(nNumero) + ">"+;
    IIF( mod(nNumero,nNum) == 0 , " é "," não é ") + ;
    "divisível por " + cValToChar(nNum) +chr(13)+chr(10) )

EXCEPCIONALMENTE NESSE EXEMPLO usando o recurso de quebrar linhas de código com ponto e vírgula no final, e tratando uma condição SIMPLES, com duas situações de retorno PEQUENO, escrever dessa forma não ficou tão “feio”. Usamos a função IIF() — ou “if em linha” — cujo comportamento é avaliar e retornar o segundo parâmetro informado caso a condição informada no primeiro argumento seja verdadeira, ou processar e retornar o terceiro parâmetro caso a condição informada seja falsa.

De qualquer modo, eu recomendo fortemente que esse tipo de redução seja usada com muita cautela, escrever um código todo rebuscado cheio de operadores e expressões “MegaZórdicas” não quer dizer que você é um bom programador. Escrever o IF — ELSE — ENDIF tradicional e colocar exatamente o que você quer fazer, mesmo que em alguns casos possa repetir um pouco de código, é preferível para dar LEGIBILIDADE ao código.

“Codifique sempre como se o cara que for dar manutenção seja um psicopata violento que sabe onde você mora.” – Martin Golding

“Qualquer um pode escrever um código que o computador entenda. Bons programadores escrevem códigos que os humanos entendam.” – Martin Fowler

 

Desempenho

Para realizar um teste de desempenho, eu removi as chamadas de MsgAlert(), e inseri uma contagem de tempo, declarando uma variável nTempo como “Local”, atribuindo nesta variável imediatamente antes da instrução “For” o retorno da função seconds(), e depois na instrução “Next”, eu mostro na tela uma MsgAlert() com o tempo que a função demorou para executar o loop de geração de números e de gravação em disco, com precisão de 3 casas decimais.  O fonte após a revisão ficou desta forma:

#INCLUDE "PROTHEUS.CH"

USER Function TESTE()
Local nArquivo , nNumero
Local nDiv := 7 
Local i, nTempo

nArquivo := fcreate("c:\Temp\arquivo.txt")
If ferror() != 0
   MsgAlert("ERRO GRAVANDO ARQUIVO, ERRO: " + cValToChar(ferror()))
   return
EndIf
nTempo := seconds()
For i:=1 to 5000
   nNumero = randomize(100000,999999)
   If (mod(nNumero,nDiv)) == 0
      fWrite(nArquivo, "O numero < " + cValToChar(nNumero) + " > é divisível por " + cValToChar(nDiv) + chr(13) + chr(10))
   Else
      fwrite(nArquivo, "O numero <" + cValToChar(nNumero) + "> não é divisível " + cValToChar(nDiv) + chr(13) + chr(10))
   EndIf
Next
MsgAlert("Tempo Total = "+str(seconds()-nTempo,12,3)+' s.')
fclose(nArquivo)
Return

Para uma métrica de tempo mais apurada, 500 iterações era muito pouco. Aumentei para 5000 iterações, e no meu equipamento a execução deste código demora entre 500 e 600 milissegundos (0.5 a 0.6 segundos).

Pontos de Atenção

Após olhar o arquivo com as informações geradas, reparei em um comportamento interessante. Os números gerados randomicamente começavam em 100000 , mas nunca eram maiores que 132000. Após consultar a documentação da função Randomize() na TDN, reparei que ela têm uma observação sobre seu funcionamento.

A função Randomize(), trabalha com um intervalo interno de 32767 números, a partir do número inicial informado, inclusive se o número inicial for negativo.

Logo, embora eu possa especificar um número inicial e final, se a diferença entre ambos for maior que 32767, ela considera o limite final igual ao limite inicial + 32767 menos uma unidade, e não o limite informado. Neste caso, precisamos usar um outro algoritmo para chegar a um número tão grande —  veremos como contornar isto mais para frente, em um próximo post.

Pontos de Melhoria

Sempre têm alguma coisinha que podemos fazer para melhorar o código, direta ou indiretamente. No caso do programa de exemplo acima, por uma mera questão circunstancial o arquivo a ser criado e que receberá os dados a serem gravados foi passado para a função contendo o que chamados de “path completo”, desde a unidade de disco, diretório e nome do arquivo com extensão. A maioria das funções de baixo nível de arquivo do AdvPL entende que, se um arquivo possui a letra da unidade de disco informada, este arquivo deve ser criado e manipulado pelo SmartClient.

Isto significa que, quando o Servidor roda a instrução fCreate(), ele envia uma mensagem ao SmartClient para que ele crie o arquivo, não importa se o SmartClient e o Protheus Server estão rodando na mesma máquina. E, cada instrução fWrite() executada faz o Protheus Server enviar uma linha com as informações para o SmartClient acrescentar no arquivo.

Os tempos de execução deste programa no meu ambiente, inserindo 5 mil linhas, estava em torno de 0.5 a 0.6 segundos. Vamos criar dentro da pasta do ambiente atual em uso (RootPath especificado na configuração de ambiente do appserver.ini) uma pasta chamada “Temp”, e no fonte AdvPL, vamos passar a criar este arquivo a partir do Servidor, mais precisamente dentro da pasta “Temp”.

nArquivo := fcreate("c:\Temp\arquivo.txt")

Deve ser trocado para

nArquivo := fcreate("\Temp\arquivo.txt")

Agora, executamos o programa novamente, e … surpresa, o tempo de execução CAIU  para 0,170 a 0,200 segundos. Isso é quase três vezes mais rápido, e olhe que no meu ambiente o SmartClient e o Application Server estão no mesmo equipamento. Logo, este tempo a mais — que chamamos de “overhead” — envolve a aplicação montar a mensagem de gravação para o SmartClient, enviar pela rede, o SmartClient receber a mensagem, decodificar a mensagem, processar a mensagem e informar que a operação foi executada com sucesso. Esta diferença de desempenho é mais acentuada ainda quando o SmartClient e o Application Server estão realmente sendo executados em duas máquinas diferentes, com uma rede TCP entre elas.

É possível melhorar esse tempo no SmartClient ?

Sim, é possível sim, melhorar significativamente. Repare que cada iteração no fonte gera uma linha de aproximadamente 41 bytes quando o número não é divisível por 7, e 37 bytes quando é divisível. Um pacote único de rede TCP normalmente suporta até 1500 bytes. Logo, se ao invés de escrever 36 linhas de 41 bytes, uma para cada fWrite(), a gente acumulasse na memória essas 36 linhas em uma variável caractere, gerando uma string de aproximadamente 1476 bytes, e enviasse essa string na função fWrite(), podemos reduzir bem o tempo de transmissão de dados na rede, pois é mais rápido transmitir um pacote de 1476 bytes do que transmitir 36 pacotes de 41 bytes. Neste caso, o fonte ficaria assim:

USER Function TESTE()
Local nArquivo , nNumero
Local nNum := 7 
Local i, nTempo
Local nX
Local cTemp := ''

nArquivo := fcreate("c:\Temp\arquivo.txt")
If ferror() != 0
   msgalert ("ERRO GRAVANDO ARQUIVO, ERRO: " + str(ferror()))
   return
EndIf
nTempo := seconds()
For i:=1 to 5000
   nNumero = Randomize(100000,999999)
   If ( nNumero % nNum ) == 0
      cTemp += "O numero <" + cValToChar(nNumero) + "> é divisível por " + cValToChar(nNum) + chr(13) + chr(10)
   Else
      cTemp += "O numero <" + cValToChar(nNumero) + "> não é divisível por " + cValToChar(nNum) + chr(13) + chr(10)
   EndIf
   IF i % 36 == 0 
      fWrite(nArquivo,cTemp)
      cTemp := ''
   Endif
Next 
if !empty(cTemp)
   fWrite(nArquivo,cTemp)
Endif
conout("Tempo Total = "+str(seconds()-nTempo,12,3)+' s.')
fclose(nArquivo)
return

Conclusão

Para meu espanto, na máquina local (APPServer e SmartClient no mesmo equipamento), os tempos ficaram praticamente iguais, gravando o arquivo no path “c:\Temp” através do SmartClient, ou gravando dentro da pasta “\temp\” a partir do RootPath do ambiente no APPServer.

Espero que este post agregue conhecimento para quem está começando no AdvPL, e para quem já têm alguma experiência com a linguagem e com o ambiente. Afinal, conhecimento nunca é demais. Desejo a todos TERABYTES DE SUCESSO. 

Agradecimentos

Agradeço o colaborador Hussyvel Ribeiro, que me concedeu um fonte de testes para usar neste post. Hussyvel, seja bem vindo ao AdvPL !!! 😀 

Referências

 

 

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

 

Algoritmos – Parte 02 – Permutações

Introdução

No post anterior (Algoritmos – Parte 01 – Loterias), vimos a criação de um algoritmo para realizar combinações simples, que pode ser usado na maioria das loterias numéricas. Agora, vamos ver um algoritmo de permutação — Algoritmo de Heap — e ver como fazer a portabilidade de um pseudo-código para AdvPL.

Algoritmo de Heap

O Algoritmo de Heap é até hoje a forma mais optimizada de gerar todas as possibilidades de permutações em um conjunto de elementos. A permutação é um processo pelo qual podemos criar sequências não repedidas dos elementos de um conjunto, alterando a sua ordem. Por exemplo, partindo de um conjunto de três números (1, 2 e 3), podemos criar as seguintes permutações:

1 2 3 
1 3 2 
2 1 3
2 3 1 
3 1 2
3 2 1

Calcula-se o número total de possibilidades de permutação de um conjunto com a fórmula P(m) = m! –> Onde P é o número de possibilidades de permutação e m é o número de elementos do conjunto, e  “!” é o símbolo da operação fatorial. Por exemplo, em um conjunto de 3 elementos, temos 3! ( 3! = 3*2 = 6) conjuntos ordenados resultantes.

Pseudocódigo

A partir da Wikipedia, por exemplo, podemos obter o pseudocódigo do algoritmo — uma representação em linguagem quase natural da sequência de operações nos conjuntos de dados para se chegar ao resultado. Esta versão do pseudo-código é a não-recursiva.

procedure generate(n : integer, A : array of any):
    c : array of int

    for i := 0; i < n; i += 1 do
        c[i] := 0
    end for

    output(A)

    i := 0;
    while i < n do
        if  c[i] < i then
            if i is even then
                swap(A[0], A[i])
            else
                swap(A[c[i]], A[i])
            end if
            output(A)
            c[i] += 1
            i := 0
        else
            c[i] := 0
            i += 1
        end if
    end while

Agora, vamos converter isso para AdvPL, primeiro de uma forma bem simples, depois de uma forma mais elaborada. Inicialmente, vamos fazer uma tradução “crua” para o AdvPL, porém funcional.

STATIC Function Generate( n , A )
Local c := {} , i
For i := 1 to n
  aadd(c,0)
Next
output(A)
i := 0 
While i < n
  If  c[i+1] < i
    if ( i % 2 ) == 0 
      swap(A, 1 , i+1)
    else
      swap(A, c[i+1]+1, i+1)
    end if
    output(A)
    c[i+1]++
    i := 0
  Else
    c[i+1] := 0
    i++
  EndIf
Enddo


STATIC Function swap(aData,nPos1,nPos2)
Local nTemp := aData[nPos1]
aData[nPos1] := aData[nPos2] 
aData[nPos2] := nTemp
Return

STATIC Function output(A)
Local i, R := ''
For i := 1 to len(A)
  If !empty(R)
    R += ', '
  Endif
  R += cValToChaR(A[i])
Next
conout(R)
Return

Diferenças na Implementação

A primeira diferença nós vemos logo de início ao usar os Arrays em AdvPL. O pseudo-código parte da premissa que um Array de N posições é endereçado de 0 a N-1 — Isto é, o primeiro elemento do Array é o elemento 0 (zero.) Já em AdvPL, o primeiro elemento do array é 1 (um). Logo, nós mantemos toda a lógica do programa inicial, inclusive as variáveis como se o array fosse base 0 (zero), porém na hora de endereçar os elementos do array, somamos uma unidade. Logo:

if c[i] < i

foi transformado para

if c[i+1] < i

A função swap() tem como objetivo trocar os elementos do array, um pelo outro. Como em AdvPL os arrays são passados por referência, podemos implementar a função de troca guardando o valor do elemento informado em uma variável local, depois atribuímos o conteúdo do segundo elemento informado sobre o primeiro, e então atribuímos o conteúdo salvo do primeiro elemento no segundo — vide função swap(). O diferencial dela em relação ao pseudocódigo é que eu passo para ela em AdvPL três parâmetros: O Array, e as duas posições a serem trocadas.

No pseudo-código, para verificar se um determinado numero é par (even em inglês), pode ser em AdvPL verificando  se o resto da divisão por dois é zero. Para isso, poderíamos usar a função mod(), ou de forma mais prática, o operador “%”.

if ( i % 2 ) == 0

Outro ponto de atenção é justamente a chamada da função swap() quando o número não for par. Veja no pseudocódigo:

swap(A[c[i]], A[i])

Agora, em AdvPL, a implementação ficou assim:

swap(A, c[i+1]+1, i+1)

Reparem que o array c[] guarda uma posição de um array. Como estamos trabalhando com array em base 1, eu devo somar 1 para recuperar o elemento do array c, e como o seu resultado será usado para indicar uma posição do array A[], eu também preciso somar 1.

Já a função output(), cujo entendimento óbvio é mostrar um dos conjuntos obtidos na permutação, implementamos simplesmente recebendo o Array , e criando uma string com o conteúdo de seus elementos separados por vírgula. Para testar o fonte acima, vamos usar a seguinte função:

User Function Permuta()
Generate( 4 , {'A' ,'B' ,'C' ,'D' } )
Return

Após salvarem, compilarem e executarem o programa acima, o resultado no log de console do Protheus Server deve ser:

A, B, C, D
B, A, C, D
C, A, B, D
A, C, B, D
B, C, A, D
C, B, A, D
D, B, A, C
B, D, A, C
A, D, B, C
D, A, B, C
B, A, D, C
A, B, D, C
A, C, D, B
C, A, D, B
D, A, C, B
A, D, C, B
C, D, A, B
D, C, A, B
D, C, B, A
C, D, B, A
B, D, C, A
D, B, C, A
C, B, D, A
B, C, D, A

Conclusão

Vendo o fonte assim, prontinho, parece fácil. Porém, eu comecei resolvendo trocar o nome das variáveis da implementação em AdvPL ao transcrever o pseudocódigo, e o programa não gerava os números corretamente. Joguei fora a primeira versão e parti do código original, mantendo o nome das variáveis, então funcionou.

Novamente agradeço a todos pela audiência e desejo a todos 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

 

Algoritmos – Parte 01 – Loterias

Introdução

Nos primeiros posts no Blog sobre programação — vide Desmistificando a análise de sistemas e Desmistificando a programação — foi colocada de forma simples a ideia de programar alguma coisa, como sendo apenas uma sequência de instruções e decisões para se realizar uma tarefa. E, realmente é simples assim, o que precisamos fazer é usar corretamente a gramática da linguagem para realizar as tarefas em pequenas etapas, criando tarefas maiores e reaproveitando tarefas menores.

Algoritmos

Uma sequência de instruções para realizar uma tarefa específica pode ser chamada de “Algoritmo” — verifique a definição na Wikipedia – Algoritmo. Não é algo aplicado apenas a programação de computadores.

Consultando também a Wikipédia – Lista de Algoritmos, podemos ver vários exemplos de problemas específicos resolvidos com sequências de operação. Como o algoritmo em si trata apenas da sequência de operações para obter um resultado, ele pode ser implementado em várias linguagens de programação.

Alguns dos mais conhecidos e utilizados são algoritmos de ordenação e busca de dados, compressão, sorteio de números (números randômicos), criptográficos (segurança da informação), entre outros. Na internet podemos obter não apenas vários modelos de algoritmos, mas também várias implementações dos mesmos em várias linguagens de programação.

Algoritmos em AdvPL

Vamos pegar um problema qualquer, e criar um algoritmo para resolvê-lo, um por tópico. Um problema bem interessante de ser resolvido usando um programa de computador, é por exemplo lidar com análise combinatória. Podemos usar um algoritmo de geração de combinações (ou Combinação Simples) para, por exemplo, criar massas de dados explorando as combinações de vários parâmetros para testes de funções, combinar números para cartões de loteria — adoro esse algoritmo — entre outras finalidades. Vamos ver como realizar uma análise combinatória eficiente para loterias em AdvPL.

Combinação para Loterias

Em um determinado cartão de loteria, podemos preencher no mínimo N números diferentes, dentre um total de X números disponíveis, para concorrer a um prêmio caso uma quantidade mínima de números sorteados estejam presentes entre os N números do cartão.

Quando queremos por exemplo, determinar todas as possibilidades de combinação de Y números em cartões de X números, utilizamos um recurso de análise combinatória chamado “Combinação Simples”. Atualmente, para alguns tipos de loterias,o próprio bilhete de apostas já permitem você fazer uma aposta com mais números do que a aposta mínima. Neste caso, a sua aposta equivale a todas as possibilidades de combinação simples dos Y números em um cartão de X números.

MEGA-SENA

Vamos tomar como exemplo a MEGA-SENA, onde a aposta mínima é um bilhete com seis números preenchidos, a um valor de R$ 3,50 . Podemos preencher de 6 a 15 números em um cartão ou bilhete, porém o preço da aposta sobe proporcionalmente ao número de combinações simples da quantidade de números preenchidos em blocos de seis números. Veja a tabela abaixo, de apostas e preços, e a quantidade de apostas de 6 números equivalente.

NUMEROS NO  | VALOR DA     | EQUIVALE A <n> 
BILHETE     | APOSTA       | APOSTAS de 6 números
------------+--------------+------------------------          
6 números   | R$ 3,50      |    1 aposta 
7 números   | R$ 24,50     |    7 apostas 
8 números   | R$ 98,00     |   28 apostas 
9 números   | R$ 294,00    |   84 apostas 
10 números  | R$ 735,00    |  210 apostas 
11 números  | R$ 1617,00   |  462 apostas 
12 números  | R$ 3234,00   |  924 apostas 
13 números  | R$ 6006,00   | 1716 apostas 
14 números  | R$ 10510,50  | 3003 apostas 
15 números  | R$ 17517,50  | 5005 apostas

Quantidade de combinações possíveis

A fórmula para determinar uma combinação simples C, de n números em conjuntos de k elementos é:

C(n,k) = n! / k! * (n-k)!

Onde “!” significa o valor FATORIAL do elemento, “*” é o operador de multiplicação, “/” é o operador de divisão.

Na matemática, o fatorial (AO 1945: factorial) de um número natural n, representado por n!, é o produto de todos os inteiros positivos menores ou iguais a n. A notação n! foi introduzida por Christian Kramp em 1808.

Vamos pegar por exemplo uma combinação de 12 elementos em conjuntos de 6 elementos — equivalente ao cartão de 12 números da MEGA-SENA.

C(12,6) = 12! / 6! * (12-6)!
C(12,6) = 12! / 6! * 6!
C(12,6) = 479001600 / 720 * 720 
C(12,6) = 479001600 / 518400 
C(12,6) = 924

Logo, antes mesmo de começar a fazer a combinação, podemos determinar o número de resultados possíveis.

Combinando elementos

A lógica para realizar a combinação dos elementos é basicamente a mesma, não importa a quantidade de elementos a combinar ou o tamanho do conjunto resultante. Porém, dependendo da forma que ela for implementada, ela tende a ficar mais pesada quanto maior a quantidade de elementos a combinar. Vamos pegar por exemplo a combinação de 7 elementos numéricos, de 1 a 7, em conjuntos de 6 elementos.

C(7,6) = 7! / 6! * (7-6)!
C(7,6) = 7! / 6! * (1)!
C(7,6) = 5040 / 720 * 1
C(7,6) = 7

Logo de antemão, sabemos que esta combinação resultará em 7 resultados. Agora, vamos fazer a combinação. Partindo dos números já ordenados, a primeira combinação é:

1 2 3 4 5 6

Para determinar o próximo resultado, pegamos o próximo numero do conjunto da última posição e incrementamos uma unidade:

1 2 3 4 5 7

Ao tentar fazer a mesma operação para pegar o terceiro resultado, o número 7 é o último elemento do conjunto. Então, precisamos incrementar o número na posição anterior — quinta posição. Quando fazemos isso, o número da posição que atingiu o limite deve ser o próximo elemento relativo a posição anterior. Logo, o próximo resultado será:

1 2 3 4 6 7

Repetindo novamente esta operação, começando da direita para a esquerda, na sexta posição, o elemento 7 já é o último do conjunto. Então vamos para a quinta posição. O próximo elemento desta posição seria o 7, porém ele é o último elemento da combinação, e eu não estou na última posição do conjunto de resultado, isto significa que ele já está em uso em uma posição posterior. Logo, vamos a posição anterior — quarta posição — e incrementamos o número 4 para 5, obtendo o resultado:

1 2 3 5 6 7

Repetindo os passos anteriores, os números 5, 6 e 7 não podem ser incrementados pois já são os elementos finais da combinação. Logo, incrementamos na terceira posição o 3 para 4, depois na segunda posição de 2 para 3, depois na primeira, de 1 para 2, e depois acabou, pois não há como incrementar mais nada.

1 2 4 5 6 7 
1 3 4 5 6 7 
2 3 4 5 6 7

Com isso obtivemos os 7 resultados possíveis. Agora, vamos colocar essa regra em um código fonte. Para facilitar o uso deste recurso, vamos criá-lo como uma Classe em AdvPL. Após construir e otimizar o algoritmo, a primeira versão ficou assim (fonte ACOMB.PRW):

#include "protheus.ch"

CLASS ACOMB FROM LONGNAMECLASS

  DATA nCols
  DATA aElements
  DATA nSize
  DATA aControl
  DATA aMaxVal
  DATA nPos

  METHOD NEW()
  METHOD GETCOMB()
  METHOD NEXTCOMB()
  METHOD GETTOTAL() 

ENDCLASS

METHOD NEW( nCols , aElements ) CLASS ACOMB
Local nI , nMax
::nCols := nCols
::aElements := aElements
::nSize := len(aElements)
::nPos := nCols
::aControl := {}
::aMaxVal := {}
nMax := ::nSize - ::nCols + 1
For nI := 1 to ::nCols
  aadd(::aControl,nI)
  aadd(::aMaxVal, nMax )
  nMax++
Next
Return self

METHOD GETCOMB() CLASS ACOMB
Local nI , aRet := array(::nCols)
For nI := 1 to ::nCols
  aRet[nI] := ::aElements[ ::aControl[nI] ] 
Next 
Return aRet

METHOD NEXTCOMB() CLASS ACOMB
If ::aControl[::nPos] + 1 > ::aMaxVal[::nPos]
  ::nPos := ::nPos - 1 
  If ::nPos < 1 
    Return .F. 
  Endif
  If ::NEXTCOMB()
    ::nPos := ::nPos + 1
    ::aControl[::nPos] := ::aControl[::nPos-1]+1
  Else
    Return .F. 
  Endif
Else
  ::aControl[::nPos]++
Endif
Return .T.

METHOD GETTOTAL() CLASS ACOMB
Local nFat1 := Fatorial( ::nSize )
Local nFat2 := fatorial( ::nCols )
Local nFat3 := Fatorial( ::nSize - ::nCols )
Local nTot := nFat1 / ( nFat2 * nFat3 ) 
Return nTot

STATIC Function Fatorial(nNum)
Local nI := nNum - 1
While nI > 1 
  nNum *= nI 
  nI--
Enddo
Return nNum

A propriedade aControl controla os contadores de cada posição do conjunto de retorno, e a propriedade aMaxVal eu já determino qual é o valor máximo de um determinado contador para a coluna ou posição atual. O Método GetComb() retorna um array com os elementos combinados, e o método NextComb() determina qual a próxima combinação da sequência, atualizando o array aControl. Quando o método NextComb() retornar .F., não há mais combinações possíveis. E, usando o método GetTotal(), eu determino quantas combinações são possíveis.

Para testar a classe acima, vamos usar o seguinte fonte:

User Function ACombTst()
Local nI
Local nResult := 1
Local lEcho := .F. 
Local nTimer
Local nCols , aData := {}

// Monta 35 dezenas para combinar 
For nI := 1 to 35
  aadd(aData,strzero(nI,2))
Next
// Combinação em conjuntos de 6 dezenas 
nCols := 6
oLoto := ACOMB():New( nCols , aData )
nTotal := oLoto:GetTotal()
conout("Elementos ...... "+cValToChar(len(aData)) )
conout("Conjunto ....... "+cValToChar(nCols) )
conout("Combinacoes .... "+cValToChar(nTotal) )
If lEcho
  aRet := oLoto:GETCOMB()
  conout("("+strzero(nResult,6)+") "+aRet[1]+" "+aRet[2]+;
         " "+aRet[3] +" "+aRet[4]+" "+aRet[5]+" "+aRet[6] )
endif
nTimer := seconds()
While oLoto:NEXTCOMB()
  nResult++
  If lEcho
    aRet := oLoto:GETCOMB()
    conout("("+strzero(nResult,6)+") "+aRet[1]+" "+aRet[2]+;
           " "+aRet[3] +" "+aRet[4]+" "+aRet[5]+" "+aRet[6] )
  Endif
Enddo
nTimer := seconds() - nTimer
conout("Resultados ..... "+cValToChar(nResult) )
conout("Tempo .......... "+str(nTimer,12,3)+" s.")
Return

No meu notebook, determinar as 1623160 combinações possíveis de 35 números em blocos de 6 demorou aproximadamente 3,5 segundos. Vejamos o log de console:

Elementos ...... 35
Conjunto ....... 6
Combinacoes .... 1623160
Resultados ..... 1623160
Tempo .......... 3.531 s.

Sim, neste teste eu não peguei os resultados, e não imprimi os resultados, eu apenas chamei em loop o método NextComb() até ele calcular todas as combinações. Para a aplicação imprimir os resultados em console, basta colocar .T. na variável lEcho e recompilar o fonte. Não é necessário dizer que, haverá uma boa diferença de desempenho quando você passar a resgatar cada uma das combinações e mostrar/gravar cada uma no log de console.

Dividindo o número total de combinações pelo tempo que a aplicação demorou, a rotina gerou aproximadamente 459688 resultados por segundo. Eu diria que isso é um tempo fantasticamente rápido. O tempo necessário para gerar estas combinações é simplesmente irrelevante perto do tempo que você deve gastar para, por exemplo, registrar estas combinações em uma tabela ou Banco de Dados.

Outras Loterias

Vamos pegar agora a Lotofácil. Cada cartão tem 25 dezenas, a aposta mínima é um bilhete com 15 dezenas preenchidas, e o prêmio máximo é você acertar os 15 números. As chances de você ganhar jogando um bilhete com a aposta mínima é 1 em 3.268.760. Afinal, se você fizer todas as combinações de 25 dezenas em grupos de 15, é exatamente esse o número de combinações possível. Vamos ver isso rodando o programa ? Basta alterar o programa de teste para criar 25 dezenas, e atribuir 15 para nCols.

Elementos ...... 25
Conjunto ....... 15
Combinacoes .... 3268760
Resultados ..... 3268760
Tempo .......... 12.002 s.

Pode fazer a mesma coisa para a MEGA-SENA, são 60 elementos em conjunto de 6. Garanto que vai demorar bem mais que 12 segundos, afinal a MEGA-SENA são 50.063.860 possibilidades ou combinações, mais de 15 vezes do que a Loto Fácil. Vamos rodar, apenas pra ver quanto tempo demora.

Elementos ...... 60
Conjunto ....... 6
Combinacoes .... 50063860
Resultados ..... 50063860
Tempo .......... 94.201 s.

Conclusão

Mesmo sabendo de tudo isso, até agora eu não ganhei na loteria, pois matematicamente as chances de um número qualquer ser sorteado é uma para o conjunto de números. As chances de acertar todos os números de uma loteria com uma aposta é uma para o conjunto de possibilidades / combinações dos números que eu posso colocar no cartão. Matematicamente, as chances de qualquer aposta — qualquer uma, inclusive por exemplo as dezenas “01 02 03 04 05 06” , ou “05 10 15 20 25 30”, são as mesmas. A única forma de aumentar matematicamente a sua probabilidade ou chance de acerto é jogar mais cartões diferentes. E, no final das contas, você pode jogar um cartão com 16 números e não acertar nenhum.

Agradeço novamente a audiência, curtidas e compartilhamentos, e desejo a todos TERABYTES DE SUCESSO 😀

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