Na primeira parte deste artigo, começamos a construir nosso disco virtual, um aplicativo onde o usuário poderá enviar arquivos para um servidor remoto, listá-los e recuperá-los posteriormente. Criamos a interface gráfica do cliente, vimos um pouco sobre autenticação com SOAP, exceções remotas, envio e recebimento de SOAP Headers e como rastrear as chamadas ao servidor. Nesta edição, veremos gerenciamento de sessão, envio de arquivos via SOAP, compactação e muito mais.

Gerenciamento de sessão

O mecanismo de autenticação que implementamos funciona, mas não está completo. Ele retorna sempre o mesmo identificador de sessão, definido de forma hard-coded no código do servidor, o que não é, obviamente, uma solução segura. Precisamos gerar um valor que seja único entre todos os usuários que podem vir a se autenticar.

Aqui temos um problema: Web Service são por natureza stateless, o que significa que nenhuma informação é persistida entre as requisições processadas. Mas o servidor precisa “lembrar” dos identificadores gerados, para poder comparar com os fornecidos pelos clientes. Como conseguir isso? Uma alternativa é controlar sessões.

A implementação do protocolo SOAP disponível com o Delphi não fornece nenhum mecanismo de persistência nativo. Dessa forma, precisamos criar um. Atente então para um detalhe importante: nosso Web Service reside em uma DLL ISAPI, que é carregada pelo IIS na primeira requisição recebida pelo servidor.

Essa DLL fica no contexto do processo DLLHost.exe, também conhecido como COM Surrogate. E ela ficará lá, em memória, até ser descarregada, como você fez algumas vezes ao longo desse artigo. Mas Web Service não são statesless?

Sim, são! Abra novamente uDiscoVirtualImpl e observe a classe TDiscoVirtual. Esse é o nosso Web Service. É essa classe que é criada e destruída automaticamente a cada execução remota. O resto da unit permanece intacto, carregado na memória. Confuso? Vamos implementar para você entender melhor.

Selecione o projeto do servidor e adicione a ele um Data Module. Chame-o de “TdmSession” e salve o arquivo com o nome “uSessionDataModule”. Clique agora no menu Project e escolha View Source. Remova a seguinte linha de código: Application.CreateForm(tdmSession, dmSession);

Fizemos isso porque criaremos manualmente o Data Module. Adicione um ClientDataSet. Dê um duplo clique sobre ele para abrir o editor de campos e insira os seguintes Fields:

  1. SessionID: Tipo String, tamanho 38;
  2. LastAccess: Tipo DateTime;
  3. UserName: Tipo String, tamanho 10;
  4. UserID: Tipo Integer.

Clique de direita sobre o ClientDataSet e escolha Create DataSet. Pronto, já temos um DataSet para armazenar os identificadores de sessão gerados para cada usuário. Vamos então criar alguns métodos para gerenciar sessões. Adicione as linhas da Listagem 1 na seção public da classe TdmSessions:


function NewSession(const UserID: Integer; 
 UserName: string): string;
procedure RemoveSession(const SessionID: string);
function IsValidSession(
 const SessionID: string): Boolean;
function GetUserID(const SessionID: string): Integer;
procedure UpdateLastAccess(const SessionID: string);
Listagem 1. Métodos de TdmSessions

Implemente agora todos esses métodos (Ctrl+Shift+C) com o código da Listagem 2.


function TdmSessions.NewSession(const UserID: Integer; 
 UserName: string): string;
var
 SessionID: TGUID;
begin
 CriticalSection.Enter;
 try
 if not cdsSessions.Active then
 cdsSessions.Open;

 CreateGUID(SessionID);
 Result := GuidToString(SessionID);

 cdsSessions.Insert;
 cdsSessionsSessionID.AsString := Result;
 cdsSessionsStartDateTime.AsDateTime := Now;
 cdsSessionsLastAccess.AsDateTime := Now;
 cdsSessionsUserName.AsString := UserName;
 cdsSessionsUserID.AsInteger := UserID;
 cdsSessions.Post;
 finally
 CriticalSection.Leave;
 end;
end;

function TdmSessions.IsValidSession(
 const SessionID: string): Boolean;
begin
 CriticalSection.Enter;
 try
 Result := cdsSessions.Locate('SessionID', 
 SessionID, []);
 if Result then
 Result := MinutesBetween(
 cdsSessionsLastAccess.AsDateTime, Now) < 1;
 finally
 CriticalSection.Leave;
 end;
end;

function TdmSessions.GetUserID(
 const SessionID: string): Integer;
begin
 CriticalSection.Enter;
 try
 Result := 0;
 if IsValidSession(SessionID) then
 Result := cdsSessionsUserID.AsInteger;
 finally
 CriticalSection.Leave;
 end;
end;

procedure TdmSessions.RemoveSession(
 const SessionID: string);
begin
 CriticalSection.Enter;
 try
 if IsValidSession(SessionID) then
 cdsSessions.Delete;
 finally
 CriticalSection.Leave;
 end;
end;

procedure TdmSessions.UpdateLastAccess(
 const SessionID: string);
begin
 CriticalSection.Enter;
 try
 if IsValidSession(SessionID) then
 begin
 cdsSessions.Edit;
 cdsSessionsLastAccess.AsDateTime := Now;
 cdsSessions.Post;
 end;
 finally
 CriticalSection.Leave;
 end;
end;
Listagem 2. Métodos de manipulação de sessão

Esses métodos servem para:

  • Criar um novo registro no ClientDataset de sessões, retornando um identificador único (NewSession);
  • Remover uma sessão do DataSet (RemoveSession);
  • Verificar se existe uma sessão com o identificador informado ou se o usuário está há muito tempo sem realizar uma requisição (IsValidSession);
  • Retornar o ID do usuário de uma determinada sessão (GetUserID) e para atualizar a data/hora do último acesso do usuário (UpdateLastAccess).

Observe o uso de critical sections para sincronizar o acesso ao código, pois o Web Service pode receber muitas solicitações simultâneas. Essas seções críticas vão garantir que apenas uma chamada por vez execute o código entre os métodos Enter e Leave, preservando a aplicação em um estado consistente.

...

Quer ler esse conteúdo completo? Tenha acesso completo