24 octubre 2008

Crea tu propio servidor HTTP (2)

Si en el anterior artículo vimos como validar la entrada de usuarios utilizando el usuario y la contraseña que pide el navegador del cliente, ahora vamos a ver como mantener el estado del usuario permanentemente en el servidor.

El estado en el servidor suele utilizarse por ejemplo para comprobar el tiempo que lleva conectado, el número de peticiones que ha realizado o para guardar las últimas consultas que ha realizado en el servidor. Esto es muy utilizado en los juegos RPG online donde suele guardarse la puntuación, energía, objetos, etc.

CREANDO UNA SESION

Los componentes Indy tienen un clase asociada el protocolo HTTP llamada TIdHTTPSession. Para utilizar este objeto hay que crearlo dentro de la lista de sesiones que tiene la clase TIdHTTPServer.

En el ejemplo que he realizado para el evento OnCommandGet voy a guardar en la sesión la fecha y la hora de cuando comenzó la sesión ese usuario:

procedure TFServidorHTTP.ServidorCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
sDocumento, sSesionID: String;
Sesion: TIdHTTPSession;
begin
Log.Lines.Add( ARequestInfo.RemoteIP + ': ' +
ARequestInfo.Command + ARequestInfo.Document );

// ¿Va a entrar a la página principal?
if ARequestInfo.Document = '/' then
AResponseInfo.ServeFile( AContext, ExtractFilePath( Application.ExeName ) + 'index.html' )
else
begin
// Cargamos la página web que vamos a enviar
sDocumento := ExtractFilePath( Application.ExeName ) +
Copy( ARequestInfo.Document, 2, Length( ARequestInfo.Document ) );

// ¿Existe la página que ha solicitado?
if FileExists( sDocumento ) then
begin
// validamos al usuario
if not ( ( ARequestInfo.AuthUsername = 'admin' ) and
( ARequestInfo.AuthPassword = '1234' ) ) then
AResponseInfo.AuthRealm := 'ServidorHTTP'
else
begin
// Componemos el ID de la sesión con el nombre del usuario y su password
sSesionID := ARequestInfo.RemoteIP + '_' + ARequestInfo.AuthUsername +
'_' + ARequestInfo.AuthPassword;

// Comprobamos si ese usuario ya tiene una sesión abierta
Sesion := Servidor.SessionList.GetSession( sSesionID, ARequestInfo.RemoteIP );

// Si no tiene sesión le pedimos autentificarse
if Sesion = nil then
// Creamos una nueva sesión para este usuario
Sesion := Servidor.SessionList.CreateSession( ARequestInfo.RemoteIP, sSesionID );

AResponseInfo.ServeFile( AContext, sDocumento );
end
end
else
// No hemos encontrado la página
AResponseInfo.ResponseNo := 404;
end;

AResponseInfo.CloseConnection := True;
end;

Después de identificar al usuario comprobamos si existe una sesión asociada al mismo. Cuando se crea una nueva sesión tenemos que darle un identificador que sea único en nuestro servidor. En nuestro caso he creado un ID juntando la IP del usuario, el nombre y su contraseña:

sSesionID := ARequestInfo.RemoteIP + '_' + ARequestInfo.AuthUsername +
'_' + ARequestInfo.AuthPassword;

Después compruebo si ya está abierta una sesión para este usuario y si no es así entonces le creamos una sesión:

// Comprobamos si ese usuario ya tiene una sesión abierta
Sesion := Servidor.SessionList.GetSession( sSesionID, ARequestInfo.RemoteIP );

// Si no tiene sesión le pedimos autentificarse
if Sesion = nil then
// Creamos una nueva sesión para este usuario
Sesion := Servidor.SessionList.CreateSession( ARequestInfo.RemoteIP, sSesionID );

Primero llama al método GetSession que necesita el identificador de la sesión y la IP del usuario remoto. Si no la encuentra creamos una nueva sesión que se añadirá automáticamente a la lista de sesiones del servidor.

Ahora introducimos en el evento OnSessionStart el código que guarda la fecha y la hora de cuando el usuario comenzó su conexión:

procedure TFServidorHTTP.ServidorSessionStart(Sender: TIdHTTPSession);
begin
Sender.Content.Text := DateTimeToStr( Now );
Log.Lines.Add( 'Iniciada sesion de ' + Sender.SessionID + ' en ' +
Sender.Content.Text );
end;

La clase TIdHTTPSession permite guardar en su variable Content (que es de la clase TStrings) cualquier texto que nos venga en gana. En mi caso sólo he guardado la fecha y hora de cuando entró el usuario.

En el evento OnSessionEnd mostramos en la ventana del servidor cuando finalizó el usuario:

procedure TFServidorHTTP.ServidorSessionEnd(Sender: TIdHTTPSession);
begin
Log.Lines.Add( 'Finalizada sesion de ' + Sender.SessionID + ' en ' +
DateTimeToStr( Now ) + ' (' + FormatFloat( '###0', MinuteSpan(
Now, StrToDateTime( Sender.Content.Text ) ) ) + ' minutos)' );
end;

La función MinuteSpan calcula la diferencia en minutos entre dos variables TDateTime. Para poder utilizar esta función hay que añadir arriba la unidad DateUtils.

Este sería el resultado al entrar a nuestro servidor:


Entramos en la zona privada:


Una vez dentro podemos esperar un par de minutos:


Al desactivar el servidor se cerrarán automáticamente todas las sesiones (lo hace sólo el componente TIdHTTPServer) mostrando en pantalla los minutos que ha permanecido nuestro usuario con la sesión abierta:


Mediante este sistema podemos guardar todas las acciones del usuario en el servidor sin necesidad de utilizar los cookies del navegador del cliente. Si queremos que los datos de cada usuario sean permanentes sólo hay que guardar el contenido de la variable Content a disco en un fichero cuyo nombre sea por ejemplo el ID del usuario. De ese modo, cuando el usuario se conecte otro día puede recuperar sus datos.

VARIACIONES PARA DELPHI 7

Como vimos en el artículo anterior, para Delphi 7 hay que hacer una pequeña variación ya que el objeto TIdHTTPResponseInfo no tiene el método ServeFile:

procedure TFServidorHTTP.ServidorCommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
sDocumento, sSesionID: String;
Sesion: TIdHTTPSession;
S: TStringList;
begin
S := TStringList.Create;
Log.Lines.Add( ARequestInfo.RemoteIP + ': ' +
ARequestInfo.Command + ARequestInfo.Document );

// ¿Va a entrar a la página principal?
if ARequestInfo.Document = '/' then
begin
S.LoadFromFile( ExtractFilePath( Application.ExeName ) + 'index.html' );
AResponseInfo.ContentText := S.Text;
end
else
begin
// Cargamos la página web que vamos a enviar
sDocumento := ExtractFilePath( Application.ExeName ) +
Copy( ARequestInfo.Document, 2, Length( ARequestInfo.Document ) );

// ¿Existe la página que ha solicitado?
if FileExists( sDocumento ) then
begin
// validamos al usuario
if not ( ( ARequestInfo.AuthUsername = 'admin' ) and
( ARequestInfo.AuthPassword = '1234' ) ) then
AResponseInfo.AuthRealm := 'ServidorHTTP'
else
begin
// Componemos el ID de la sesión con el nombre del usuario y su password
sSesionID := ARequestInfo.RemoteIP + '_' + ARequestInfo.AuthUsername +
'_' + ARequestInfo.AuthPassword;

// Comprobamos si ese usuario ya tiene una sesión abierta
Sesion := Servidor.SessionList.GetSession( sSesionID, ARequestInfo.RemoteIP );

// Si no tiene sesión le pedimos autentificarse
if Sesion = nil then
// Creamos una nueva sesión para este usuario
Sesion := Servidor.SessionList.CreateSession( ARequestInfo.RemoteIP, sSesionID );

S.LoadFromFile( sDocumento );
AResponseInfo.ContentText := S.Text;
end
end
else
// No hemos encontrado la página
AResponseInfo.ResponseNo := 404;
end;

AResponseInfo.CloseConnection := True;
S.Free;
end;

Los demás métodos funcionan exactamente igual.

En el siguiente artículo vamos a seguir exprimiendo nuestro servidor con nuevas funcionalidades.

Pruebas realizadas en RAD Studio 2007 y Delphi 7.

Publicidad