Mostrando entradas con la etiqueta internet. Mostrar todas las entradas
Mostrando entradas con la etiqueta internet. Mostrar todas las entradas

08 abril 2010

La librería Synapse (3)

Otro de los protocolos que podemos utilizar con esta librería es la descarga de páginas web o archivos así como el envío de los mismos a un servidor. También veremos como crear un pequeño servidor de páginas web utilizando hilos de ejecución.

CREAR UN CLIENTE HTTP

Un cliente de HTTP no es lo mismo que un navegador, ya que ello implica muchísimo más trabajo. Lo que vamos a hacer es leer el código fuente de cualquier página web que nos devolverá el contenido en HTML.

Este sería el formulario donde escribimos la dirección:

Va a constar de un componente TEdit llamado URL para escribir la dirección de la página web que deseamos descargar y volcaremos el resultado en el componente TMemo llamado Pagina.

Cuando pulsemos el botón BDescargar solo hay que introducir esta línea de código:

procedure TFClienteHTTP.BDescargarClick(Sender: TObject);
begin
HTTPGetText(URL.Text, Pagina.Lines);
end;

Para poder utilizar la función HTTPGetText tenemos que añadir la unidad HTTPSend que se encuentra en la carpeta Lib del directorio donde tengamos descomprimida la librería Synapse.

Con una sola línea de código podemos descargar la página:

También tenemos en la misma unidad las siguientes funciones:

function HttpGetBinary(const URL: string; const Response: TStream): Boolean;

Esta función es igual que la anterior pero permite descargar archivos binarios. Para hacerte tu propio Downloader.

function HttpPostBinary(const URL: string; const Data: TStream): Boolean;

Envía un archivo binario al servidor utilizando el método SEND.

function HttpPostURL(const URL, URLData: string; const Data: TStream): Boolean;

Sirve para enviar datos de campos por medio de la URL al servidor, como puede ser el rellevar un formulario de solicitud y pulsar el botón POST del HTML.

function HttpPostFile(const URL, FieldName, FileName: string; const Data: TStream; const ResultData: TStrings): Boolean;

Esta función envía un archivo al servidor utilizando el método POST.

Como dije anteriormente, sigo encontrando en los componentes Synapse el problema de monitorizar el progreso de la descarga o subida de archivos con una barra de progreso.

CREAR UN SERVIDOR HTTP

La forma de crear un servidor es muy parecida a la realizada para crear un servidor FTP. Se crea un hilo primario que controla las peticiones de los clientes y en el momento que se encuentre una solicitud entonces abrimos otro hilo para atender el cliente.

Lo primero de todo es crear el formulario que va a monitorizar el servidor:

Solo vamos a tener un componente TMemo llamado Monitor donde volcaremos las páginas web que solicitan los clientes. La demo que trae Synapse del servidor http es bastante cutre y siempre devuelve la misma página diciendo lo que ha pedido el cliente. Yo he realizado una pequeña ampliación para que busque los archivos HTML en nuestro disco duro y si no los encuentra que lo diga.

Vamos primero con el hilo del servidor:

THiloServidor = class(TThread)
private
Sock: TTCPBlockSocket;
public
Constructor Create;
Destructor Destroy; override;
procedure Execute; override;
end;

El constructor creará el Sock de conexión para esperar peticiones de los clientes:

Constructor THiloServidor.Create;
begin
inherited Create(False);
Sock := TTCPBlockSocket.Create;
FreeOnTerminate := True;
end;

Y el destructor la libera:

Destructor THiloServidor.Destroy;
begin
Sock.free;
inherited Destroy;
end;

Al poner en marcha el hilo de ejecución se queda esperando indefinidamente las peticiones de los clientes por el puerto 80:

procedure THiloServidor.Execute;
var
ClientSock: TSocket;
begin
with Sock do
begin
CreateSocket;
SetLinger(True, 10000);
Bind('0.0.0.0','80');
Listen;
repeat
if Terminated then
Break;

if CanRead(1000) then
begin
ClientSock := Accept;

if LastError = 0 then
THiloCliente.Create(ClientSock);
end;
until False;
end;
end;

Cuando el cliente realiza una petición lanzamos el hilo cliente (THiloCliente) que se compone de lo siguiente:

THiloCliente = class(TThread)
private
sMensaje: string;
Sock: TTCPBlockSocket;
public
Cabeceras: TStringList;
InputData, OutputData: TMemoryStream;
Constructor Create(HSock: TSocket);
Destructor Destroy; override;
procedure Execute; override;
function ProcessHttpRequest(Request, URI: string): integer;
procedure Monitorizar;
function Etiqueta(sEtiqueta: string): string;
end;

Al crear este hilo prepara las conexiones y los streams de entrada y salida:

Constructor THiloCliente.Create(HSock: TSocket);
begin
Sock := TTCPBlockSocket.Create;
Cabeceras := TStringList.Create;
InputData := TMemoryStream.Create;
OutputData := TMemoryStream.Create;
Sock.Socket := HSock;
FreeOnTerminate := True;
inherited Create(False);
end;

Siendo el destructor el encargado de limpiarlo todo:

Destructor THiloCliente.Destroy;
begin
Sock.free;
Cabeceras.Free;
InputData.Free;
OutputData.Free;
inherited Destroy;
end;

Al ejecutar el hilo leerá la petición del cliente y procesará la respuesta:

procedure THiloCliente.Execute;
var
iLimiteTiempo: integer;
s: string;
sMetodo, uri, sProtocolo: string;
size: integer;
x, n: integer;
resultcode: integer;
bCerrar: boolean; // ¿Hay que cerrar la conexión?
begin
iLimiteTiempo := 120000; // aguantamos 120 segundos

repeat
// Leemos la solicitud del cliente
s := Sock.RecvString(iLimiteTiempo);

if Sock.LastError <> 0 then
Exit;

if s = '' then
Exit;

sMetodo := Fetch(s, ' ');

if (s = '') or (sMetodo = '') then
Exit;

uri := Fetch(s, ' ');

if uri = '' then
Exit;

sProtocolo := Fetch(s, ' ');
Cabeceras.Clear;
size := -1;
bCerrar := False;

// Leemos las cabeceras HTTP
if sProtocolo <> '' then
begin
if pos('HTTP/', sProtocolo) <> 1 then
Exit;

if pos('HTTP/1.1', sProtocolo) <> 1 then
bCerrar := True;

repeat
s := Sock.RecvString(iLimiteTiempo);

if Sock.lasterror <> 0 then
Exit;

if s <> '' then
Cabeceras.add(s);

if Pos('CONTENT-LENGTH:', UpperCase(s)) = 1 then
Size := StrToIntDef(SeparateRight(s, ' '), -1);

if Pos('CONNECTION: CLOSE', Uppercase(s)) = 1 then
bCerrar := true;

until s = '';
end;

// Leemos el contenido del documento
InputData.Clear;
if size >= 0 then
begin
InputData.SetSize(Size);
x := Sock.RecvBufferEx(InputData.Memory, Size, iLimiteTiempo);
InputData.SetSize(x);

if Sock.lasterror <> 0 then
Exit;
end;

OutputData.Clear;
ResultCode := ProcessHttpRequest(sMetodo, uri);
Sock.SendString(sProtocolo + ' ' + IntTostr(ResultCode) + CRLF);

if sProtocolo <> '' then
begin
Cabeceras.Add('Content-length: ' + IntTostr(OutputData.Size));

if bCerrar then
Cabeceras.Add('Connection: close');

Cabeceras.Add('Date: ' + Rfc822DateTime(now));
Cabeceras.Add('Server: Servidor HTTP creado con Synapse');
Cabeceras.Add('');

for n := 0 to Cabeceras.count - 1 do
Sock.SendString(Cabeceras[n] + CRLF);
end;

if Sock.Lasterror <> 0 then
Exit;

Sock.SendBuffer(OutputData.Memory, OutputData.Size);

if bCerrar then
Break;

until Sock.LastError <> 0;
end;

Dicha respuesta viene definida en este método:

function THiloCliente.ProcessHttpRequest(Request, URI: string): integer;
var
Pagina: TStringlist;
sRuta: string;
begin
sMensaje := URI;
Synchronize(Monitorizar);
result := 504;
sRuta := ExtractFilePath(Application.ExeName);

if request = 'GET' then
begin
// Preparamos las cabeceras del HTTP
Cabeceras.Clear;
Cabeceras.Add('Content-type: Text/Html');
Pagina := TStringList.Create;

// ¿Existe la página web que estamos buscando?
if FileExists(sRuta + URI) then
begin
Pagina.LoadFromFile(sRuta + URI)
end
else
begin
Pagina.Add(Etiqueta('html'));
Pagina.Add(Etiqueta('head') + Etiqueta('/head'));
Pagina.Add(Etiqueta('body'));
Pagina.Add('No encuentro la página web que ha solicitado:');
Pagina.Add(Etiqueta('br'));
Pagina.Add(URI);
Pagina.Add(Etiqueta('/body'));
Pagina.Add(Etiqueta('/html'));
end;

Pagina.SaveToStream(OutputData);
Pagina.Free;
Result := 200;
end;
end;

Como Blogger no me deja escribir el código fuente con etiquetas he creado esta función que las monta:

function THiloCliente.Etiqueta(sEtiqueta: string): string;
begin
Result := '<' + sEtiqueta + '>';
end;

Para poder ver los mensajes en el servidor he creado este método:

procedure THiloCliente.Monitorizar;
begin
FServidorHTTP.Monitor.Lines.Add(sMensaje);
end;

Que será llamado mediante Synchronize para enviar los mensajes al Memo. En el evento OnCreate del formulario ponemos en marcha el hilo del servidor:

procedure TFServidorHTTP.FormCreate(Sender: TObject);
begin
Monitor.Lines.Add('Arrancando servidor HTTP en puerto 80...');
HiloServidor := THiloServidor.Create;
end;

Y debemos acordarnos de destruirlo al liberar el formulario:

procedure TFServidorHTTP.FormDestroy(Sender: TObject);
begin
HiloServidor.Terminate;
end;

Para hacer una prueba he creado un par de páginas web donde la principal sería con el nombre index.html:

Y la página llamada pagina1.html que permite volver a la principal:

Pues bueno, ponemos en marcha el servidor:

Abrimos un navegador y solicitamos la página principal:

Podemos entrar a la página 1 sin problemas:

Pero si volvemos a la principal e intentamos ir a la página 2 nos devolverá esto:

En todo momento podemos ver lo que le van pidiendo al servidor:

El icono favicon.ico lo solicitan los navegadores cada vez que se entra a una web. Es este icono:

Esta sería la base mínima para crear un servidor de páginas web tipo Apache. Pero también hay que preocuparse de otras cuestiones como servir las imágenes que hay dentro de las mismas, controlar las peticiones GET y POST de formularios, etc. Digamos que no es una cosa que se haga en dos tardes. Pero gozamos de la ventaja de tener un servidor muy rápido para tareas específicas: notificaciones para actualizaciones de software (tipo antivirus), descarga de versiones con contraseña o enviar solicitudes de licencia al servidor.

Seguiremos en el próximo artículo con más protocolos de Synapse.

Pruebas realizadas en RAD Studio 2007.

05 marzo 2010

La librería Synapse (1)

Buscando componentes de comunicaciones alternativos a los indocumentados Indy, me encontré con esta librería que soporta diversos protocolos TCP/IP sin tener que instalar ningún componente en Delphi. Además es gratuita y viene con todo el código fuente abierto.

La última versión es la release nº 39 con fecha 9/10/2009 y se encuentra en esta página web:

http://www.synapse.ararat.cz/doku.php

Nos vamos al apartado Download y nos bajamos la última versión estable:

Donde tengamos el directorio de los otros componentes, creamos la carpeta Synapse y descomprimimos dentro este zip:

Luego solo tenemos que preocuparnos de enlazar la carpeta \Synapse\source\lib dentro del Search Path de nuestro proyecto.

Vamos a ver todo lo que se puede hacer con esta librería. Para hacer las pruebas he utilizado Delphi 2007.

CREAR UN CLIENTE TFTP

El protocolo TFTP (Trivial File Transfer Protocol – Protocolo de Transferencia de Archivos Trivial) es una versión descafeinada del protocolo FTP que solo permite subir o bajar archivos directamente del servidor sin tener en cuenta usuarios, carpetas, permisos, etc.

Para realizar el transporte de datos utiliza el protocolo UDP usando el puerto 69 (en vez del 21 como el FTP). Como necesitaba un servidor TFTP para hacer las pruebas, me bajé el servidor gratuito tftpd32:

Se puede descargar de esta página web:

http://tftpd32.jounin.net/

Solo hay que ejecutarlo y listo. Por defecto, comparte los archivos que están en su misma carpeta, pero podemos pulsar el botón Browse para cambiarla. Comencemos a crear el servidor con un nuevo proyecto te tenga este formulario:

Debemos añadir la unidad FTPTSend en la sección uses de la unidad actual. Y como dije antes, enlazamos la carpeta \Synapse\source\lib dentro del Search Path de nuestro proyecto.

El formulario se divide en tres partes principales:

La dirección del servidor y el puerto que va a utilizar el usuario:

Los componentes TEdit que he utilizado se llaman Servidor y Puerto.

El apartado para descargar un archivo del servidor:

El componente TEdit donde metemos el nombre del archivo se llama Descargar. Al pulsar el botón BDescargar hacemos lo siguiente:

procedure TFClienteTFTP.BDescargarClick(Sender: TObject);
var
Cliente: TTFTPSend;
begin
// Creamos el cliente
Cliente := TTFTPSend.Create;

// Establecemos los parámetros de conexión
Cliente.TargetHost := Servidor.Text;
Cliente.TargetPort := Puerto.Text;

// ¿Podemos descargar el archivo?
if Cliente.RecvFile(Descargar.Text) then
begin
// Lo guardamos a disco
Cliente.Data.SaveToFile(ExtractFilePath(Application.ExeName) + Descargar.Text);
EMensajeDescarga.Caption := 'OK';
EMensajeDescarga.Font.Color := clGreen;
end
else
begin
EMensajeDescarga.Caption := 'ERROR: ' + Cliente.ErrorString;
EMensajeDescarga.Font.Color := clMaroon;
end;

EMensajeDescarga.Visible := True;

// Liberamos el cliente
Cliente.Free;
end;

He utilizado la etiqueta EMensajeDescarga que por defecto está invisible para mostrar OK o Error en caso de que falle. No hace falta introducir la ruta del archivo en el servidor, sólo el nombre del mismo:

Y otro apartado para subir un archivo al servidor:

El usuario debe pulsar el botón Examinar para volcar el nombre con la ruta al campo TEdit llamado Subir:

procedure TFClienteTFTP.BExaminarClick(Sender: TObject);
begin
if AbrirArchivo.Execute then
Subir.Text := AbrirArchivo.FileName;
end;

AbrirArchivo es un componente TOpenDialog. Al pulsar el botón BSubir comprobamos antes si el archivo es válido:

procedure TFClienteTFTP.BSubirClick(Sender: TObject);
var
Cliente: TTFTPSend;
begin
// Comprobamos si el usuario ha seleccionado el archivo
if Subir.Text = '' then
begin
Application.MessageBox('Seleccione el archivo que desea subir al servidor.',
'Atención', MB_ICONEXCLAMATION);
ActiveControl := Subir;
Exit;
end;

// Comprobamos si el archivo a subir existe
if not FileExists(Subir.Text) then
begin
Application.MessageBox('El archivo que intenta subir al servidor no existe.',
'Atención', MB_ICONEXCLAMATION);
ActiveControl := Subir;
Exit;
end;

// Creamos el cliente TFTP
Cliente := TTFTPSend.Create;

// Establecemos los parámetros de conexión
Cliente.TargetHost := Servidor.Text;
Cliente.TargetPort := Puerto.Text;

// Intentamos subir el archivo
Cliente.Data.LoadFromFile(Subir.Text);

if Cliente.SendFile(ExtractFileName(Subir.Text)) then
begin
EMensajeSubida.Caption := 'OK';
EMensajeSubida.Font.Color := clGreen;
end
else
begin
EMensajeSubida.Caption := 'ERROR: ' + Cliente.ErrorString;
EMensajeSubida.Font.Color := clMaroon;
end;

EMensajeSubida.Visible := True;

// Liberamos el cliente
Cliente.Free;
end;

Como puede verse en ambos casos, el código a escribir para la transferencia de archivos por TFTP entre el cliente y el servidor es extremadamente sencilla. Solo le he encontrado un inconveniente y es que no tiene ningún evento especial para monitorizar la transferencia en tiempo real, algo imprescindible para poner una barra de progreso.

CREAR UN SERVIDOR TFTP

Ahora le damos la vuelta a la tortilla y vamos a implementar el servidor para sustituir tftp32. Aquí la cosa no va tan fácil ya que hay que crear todo el servidor a mano leyendo y procesando los mensajes del mismo. Como es natural, me he basado en las demos que lleva esta librería para crearlo, pasando a español todo lo que he podido de la clase que controla el servidor.

Para empezar creamos un nuevo proyecto con este formulario:


Volvemos a vincular la carpeta \Synapse\source\lib dentro del Search Path de nuestro proyecto y añadimos la unidad FTPTSend.

Le he metido el campo Directorio de la clase TEdit para guardar el directorio por defecto que comparte los archivos. También tiene un componente TMemo llamado Mensajes para monitorizar el estado del servidor.

Por si queremos cambiar el directorio que comparte con los clientes, podemos pulsar el botón BExaminar para elegir otro directorio:

procedure TFServidorTFTP.BExaminarClick(Sender: TObject);
var
sDirectorio: string;
begin
if SelectDirectory('Seleccione un directorio', '', sDirectorio) then
Directorio.Text := sDirectorio;
end;

Ahora vamos a crear el servidor. Para que la aplicación no se quede bloqueada hay que meter el servidor dentro de un hilo de ejecución que procese las peticiones de los clientes. Lo primero es crear la clase que controla al servidor:

type
// Hilo encargado de recibir mensajes de los clientes
THiloServidor = class(TThread)
private
{ Private declarations }
FServidor: TTFTPSend;
FIP: string;
FPuerto: string;
FMensaje: string;
procedure ActualizarMensajes;
protected
procedure Execute; override;
public
constructor Create(sIP, sPuerto: string);
end;

El constructor solo recoge la IP y el puerto:

constructor THiloServidor.Create(sIP, sPuerto: string);
begin
FIP := sIP;
FPuerto := sPuerto;
inherited Create(False);
end;

También va a tener un procedimiento sincronizado para enviar al formulario los mensajes, ya que un hilo no puede manejar componentes VCL directamente:

procedure THiloServidor.ActualizarMensajes;
begin
FServidorTFTP.Mensajes.Lines.Add(FMensaje);
end;

Y ahora viene la parte gorda, la encargada de recibir mensajes de los clientes y enviar o recibir archivos a los mismos:

procedure THiloServidor.Execute;
var
RequestType: Word;
FileName: String;
begin
// Creamos el servidor encargado de escuchar a los clientes
FServidor := TTFTPSend.Create;
FMensaje := 'Servidor arrancado con el puerto ' + FPuerto + '.';
Synchronize(Actualizarmensajes);
FServidor.TargetHost := FIP;
FServidor.TargetPort := FPuerto;

try
// Mientras no termine el hilo de ejecución escuchamos los
// mensajes de los clientes
while not Terminated do
begin
// ¿Ha llegado algún mensaje?
if FServidor.WaitForRequest(RequestType,FileName) then
begin
// Mostramos quién realiza las solicitudes
case RequestType of
1:FMensaje := 'Solicitan lectura de ' +
FServidor.RequestIP + ':' + FServidor.RequestPort;

2:FMensaje := 'Solicitan escritura de ' +
FServidor.RequestIP + ':' + FServidor.RequestPort;
end;

Synchronize(ActualizarMensajes);
FMensaje := 'Archivo: ' + Filename;
Synchronize(ActualizarMensajes);

// Procesa la solicitud
case RequestType of
1:begin // Solucitud de lectura (RRQ)
if FileExists(FServidorTFTP.Directorio.Text + FileName) then
begin
FServidor.Data.LoadFromFile(FServidorTFTP.Directorio.Text +
FileName);

if FServidor.ReplySend then
begin
FMensaje := '"' + FServidorTFTP.Directorio.Text +
FileName + '" correctamente enviado.';
Synchronize(ActualizarMensajes);
end;
end
else
FServidor.ReplyError(1, 'Archivo no encontrado');
end;

2:begin // Solicitud de escritura (WRQ)
if not FileExists(FServidorTFTP.Directorio.Text + FileName) then
begin
if FServidor.ReplyRecv then
begin
FServidor.Data.SaveToFile(FServidorTFTP.Directorio.Text +
FileName);
FMensaje := 'Archivo guardardo en ' +
FServidorTFTP.Directorio.Text + FileName;
Synchronize(ActualizarMensajes);
end;
end
else
FServidor.ReplyError(6, 'El archivo ya existe.');
end;
end;
end;
end;
finally
FServidor.Free;
end;
end;

Por último, al crear el formulario establecemos como ruta de compartición de archivos el mismo directorio del programa y arrancamos el servidor:

procedure TFServidorTFTP.FormCreate(Sender: TObject);
begin
Directorio.Text := ExtractFilePath(Application.ExeName);
HiloServidor := THiloServidor.Create('0.0.0.0', '69');
end;

Ahora ejecutamos el servidor y el cliente, y probamos a enviar y recibir archivos:

Utilizando en nuestro software los clientes y servidores de TFTP podemos hacer una transferencia de información entre programas en una red local, independientemente de la conexión al servidor de bases de datos. La velocidad es muy rápida y fiable, pero como he dicho antes, hecho de menos controlar el progreso de la descarga cuando los archivos son grandes.

Quizás la librería Synapse no sea tan sofisticada como los componentes Indy, pero por lo menos dan ejemplos y documentación bastante asequible. En el próximo artículo seguiré investigando sobre otros protocolos.

Pruebas realizadas en RAD Studio 2007.

07 noviembre 2008

Crea tu propio servidor HTTP (y 4)

Vamos a terminar de ver las características principales del componente TIdHTTPServer viendo como validar a los usuarios del mismo utilizando las cookies del navegador.

Para el que no las conozca, las cookies son archivos temporales que una aplicación web puede crear en el navegador del cliente para guardar estados de sesión. En nuestro caso vamos a guardar el usuario y la contraseña del usuario que ha entrado.

REPROGRAMANDO EL EVENTO ONCOMMANDGET

En el evento OnCommandGet vamos a mostrar en la ventana del servidor las cookies que devuelve el navegador del cliente y también vamos a dar la posibilidad de entrar a la página principal del foro (foro.html) cuando los usuarios sean validados:

procedure TFServidorHTTP.ServidorCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
sDocumento: String;
i: Integer;
begin
if ARequestInfo.Cookies.Count > 0 then
begin
for i := 0 to ARequestInfo.Cookies.Count-1 do
Log.Lines.Add( 'cookie: ' + ARequestInfo.Cookies[i].CookieName + ': ' +
ARequestInfo.Cookies[i].Value );
end;

if ARequestInfo.Document = '/registrar' then
RegistrarUsuario( ARequestInfo, AResponseInfo );

if ARequestInfo.Document = '/entrar' then
Entrar( ARequestInfo, AResponseInfo );

if ARequestInfo.Document = '/enviarmensaje' then
EnviarMensaje( ARequestInfo, AResponseInfo );

if ARequestInfo.Document = '/foro.html' then
begin
Foro( ARequestInfo, AResponseInfo );
Exit;
end;

// ¿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
AResponseInfo.ServeFile( AContext, sDocumento )
else
// No hemos encontrado la página
AResponseInfo.ResponseNo := 404;
end;

AResponseInfo.CloseConnection := True;
end;

He añadido como novedades la posibilidad de ver las cookies que tiene el cliente en el navegador:

if ARequestInfo.Cookies.Count > 0 then
begin
for i := 0 to ARequestInfo.Cookies.Count-1 do
Log.Lines.Add( 'cookie: ' + ARequestInfo.Cookies[i].CookieName + ': ' +
ARequestInfo.Cookies[i].Value );
end;

Y suministrar la página del foro donde se van a escribir los mensajes:

if ARequestInfo.Document = '/foro.html' then
begin
Foro( ARequestInfo, AResponseInfo );
Exit;
end;

Ahora debemos modificar la página de entrada para crear las cookies.

CREANDO LAS COOKIES EN EL NAVEGADOR DEL USUARIO

Vamos a modificar nuestro procedimiento de entrar para que cuando el usuario sea validado cree en el navegador del cliente dos cookies para almacenar el usuario y su contraseña:

procedure TFServidorHTTP.Entrar( ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo );
var
sNombre, sPassword, sError, sUsuarios: String;
Usuarios: TStringList;
begin
sNombre := ARequestInfo.Params.Values['nombre'];
sPassword := ARequestInfo.Params.Values['password'];

// Abrimos la lista de usuarios
sUsuarios := ExtractFilePath( Application.ExeName ) + 'usuarios.txt';
Usuarios := TStringList.Create;

if FileExists( sUsuarios ) then
Usuarios.LoadFromFile( sUsuarios );

// Comprobamos si el usuario ya ha sido dado de alta en la lista
if Usuarios.Values[sNombre] = '' then
sError := '<h3>El usuario no existe.</h3>'
else
if Usuarios.Values[sNombre] <> sPassword then
sError := '<h3>La contraseña es incorrecta.</h3>';

if sError <> '' then
begin
AResponseInfo.ContentText := sError;
AResponseInfo.WriteContent;
end
else
begin
with AResponseInfo.Cookies.Add do
begin
CookieName := 'usuario';
Value := sNombre;
end;

with AResponseInfo.Cookies.Add do
begin
CookieName := 'password';
Value := sPassword;
end;

AResponseInfo.Redirect( 'foro.html' );
AResponseInfo.WriteContent;
end;

Usuarios.Free;
end;

Cuando el usuario ha sido validado le creamos las dos cookies y redireccionamos al cliente a la página del foro.

CREANDO LA PÁGINA WEB DEL FORO

La página web del foro va a ser la siguiente:


La página principal del foro contiene en su parte superior el nombre el usuario que ha entrado y la lista de mensajes que han enviado los usuarios. En la parte inferior muestro también el número de mensajes que hay en el foro y un pequeño formulario para escribir un nuevo mensaje.

Todo esto lo gestiono con un nuevo procedimiento llamado Foro que genera toda la página web:

procedure TFServidorHTTP.Foro( ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo );
var
i: Integer;
Mensajes, Usuarios: TStringList;
s, sUsuario, sPassword, sMensajes, sUsuarios: String;
begin
// Antes de entrar en el foro comprobamos si el usuario está registrado.
// Para ello leemos las cookies del navegador del cliente
i := ARequestInfo.Cookies.GetCookieIndex(0,'usuario');
if i > -1 then
sUsuario := ARequestInfo.Cookies[i].Value;

i := ARequestInfo.Cookies.GetCookieIndex(0,'password');
if i > -1 then
sPassword := ARequestInfo.Cookies[i].Value;

// Cargamos la lista de usuarios y verificamos su acceso
sUsuarios := ExtractFilePath( Application.ExeName ) + 'usuarios.txt';
Usuarios := TStringList.Create;
if FileExists( sUsuarios ) then
Usuarios.LoadFromFile( sUsuarios );

if Usuarios.IndexOf( sUsuario + '=' + sPassword ) = -1 then
begin
AResponseInfo.Redirect( 'index.html' );
AResponseInfo.WriteContent;
Usuarios.Free;
end
else
Usuarios.Free;

sMensajes := ExtractFilePath( Application.ExeName ) + 'mensajes.txt';
Mensajes := TStringList.Create;

if FileExists( sMensajes ) then
Mensajes.LoadFromFile( sMensajes );

s := '<h2>Listado de mensajes</h2><p>';

// Mostramos el usuario logeado

i := ARequestInfo.Cookies.GetCookieIndex(0,'usuario');
if i > -1 then
s := s + '<h2>Usuario: ' + ARequestInfo.Cookies[i].Value + '</h2>';

for i := 0 to Mensajes.Count - 1 do
s := s + Mensajes[i] + '<p>';

s := s + '<h4>Hay un total de ' + IntToStr( Mensajes.Count ) +
' mensajes</h4>';

s := s + '<p><h4>Escribir un nuevo mensaje</h4>';
s := s + '<form name="mensaje" action="enviarmensaje" method="post">';
s := s + '<label for="nombre">Título: </label><br>';
s := s + '<input name="titulo" size="40" type="text"><br>';
s := s + '<label for="nombre">Mensaje: </label><br>';
s := s + '<textarea name="mensaje" rows="3" cols="40"></textarea>
<p><br>';
s := s + '<input value="Enviar" type="submit"></form>';

Mensajes.Free;
AResponseInfo.ContentText := s;
end;

Antes de poder listar los mensajes del foro tenemos que validar al usuario leyendo las cookies y comprobando si esta en la lista de usuarios. Después muestro los mensajes que ha enviado cada usuario.

Aunque con una validación como esta es suficiente para controlar la entrada de usuarios, para crear un foro en condiciones habría que crear más cookies para identificadores de sesión, control del tiempo que lleva el usuario así como un poco de encriptación en las cookies para evitar el robo de las claves mediante sniffers.

También habría que guardar la información del foro en una base de datos fiable como Internase, Firebird, etc. Esto unido a unas buenas hojas de estilo CSS nos permitirá crear unas aplicaciones web pequeñas y potentes.

Y este sería el procedimiento EnviarMensaje:

procedure TFServidorHTTP.EnviarMensaje( ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo );
var
i: Integer;
sTitulo, sMensaje, sError, sMensajes, sUsuario: String;
Mensajes: TStringList;
begin
sTitulo := ARequestInfo.Params.Values['titulo'];
sMensaje := ARequestInfo.Params.Values['mensaje'];

i := ARequestInfo.Cookies.GetCookieIndex(0,'usuario');
if i > -1 then
sUsuario := ' ' + ARequestInfo.Cookies[i].Value;

if sTitulo = '' then
sError := '<h3>Debe introducir el título del mensaje</h3>';

if sMensaje = '' then
sError := '<h3>Debe introducir el mensaje</h3>';

// Abrimos la lista de mensajes
sMensajes := ExtractFilePath( Application.ExeName ) + 'mensajes.txt';
Mensajes := TStringList.Create;

if FileExists( sMensajes ) then
Mensajes.LoadFromFile( sMensajes );

if sError <> '' then
AResponseInfo.ContentText := sError
else
begin
Mensajes.Add( '<h4>' + DateTimeToStr( Now ) + sUsuario + '</h4><p>' +
UpperCase( sTitulo ) + ': ' + sMensaje );
Mensajes.SaveToFile( sMensajes );
AResponseInfo.Redirect( 'foro.html' );
end;

AResponseInfo.WriteContent;
Mensajes.Free;
end;

Este procedimiento es el utilizado por los usuarios para enviar un mensaje al foro.

Con esto termino estos artículos relacionados con el componente TIdHTTPServer aunque no descarto escribir más sobre el mismo si descubro más utilidades interesantes como puede ser por ejemplo la creación de páginas web seguras con SSL.

Pruebas realizadas en RAD Studio 2007.

31 octubre 2008

Crea tu propio servidor HTTP (3)

Si bien hemos aprendido a que el navegador pida nombre de usuario y clave para entrar en nuestras webs privadas, la forma más natural de entrar a una página web suele ser dándose de alta en un formulario HTML para luego entrar con sus datos.

En esta ocasión no vamos a utilizar por ahora las propiedades de autenticación que vimos en artículos anteriores con AResponseInfo.AuthRealm ni vamos controlar la sesión con la clase TIdHTTPSession.

Vamos a comenzar a ver un ejemplo de cómo crear nuestro propio foro. En este artículo vamos a controlar el registro y la entrada de usuarios.

CREANDO LAS PÁGINAS WEB DEL FORO

Para crear un foro necesitamos una página web de entrada que permita hacer login al usuario así como darse de alta en nuestro foro. Esta va a ser nuestra página web de entrada:

El código fuente de la página se puede hacer con el bloc de notas de Windows (clic para ampliar):


La página contiene un formulario que recoge el usuario y su contraseña para luego pulsar el botón Enviar. Esta página hay que guardarla con el nombre index.html.

Cuando el usuario pulse el enlace Registrar entonces saltará a esta otra página:


Cuyo código fuente es el siguiente:


Ahora es cuando tenemos que entrar en faena y programar nuestro servidor HTTP.

CREANDO LA APLICACIÓN SERVIDOR

La ventana del servidor es prácticamente la misma que vimos en los artículos anteriores:


Lo que vamos a cambiar va a ser el evento OnCommandGet del componente TIdHTTPServer:

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

Log.Lines.Add( 'Parámetros: ' + ARequestInfo.Params.Text );

if ARequestInfo.Document = '/registrar' then
RegistrarUsuario( ARequestInfo, AResponseInfo );

if ARequestInfo.Document = '/entrar' then
Entrar( ARequestInfo, AResponseInfo );

// ¿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
AResponseInfo.ServeFile( AContext, sDocumento )
else
// No hemos encontrado la página
AResponseInfo.ResponseNo := 404;
end;

AResponseInfo.CloseConnection := True;
end;

Este evento se compone de las siguientes partes:

1º Mostramos en la ventana del servidor que página web nos solicita el usuario y los parámetros de la misma:

Log.Lines.Add( ARequestInfo.RemoteIP + ': ' +
ARequestInfo.Command + ARequestInfo.Document );

Log.Lines.Add( 'Parámetros: ' + ARequestInfo.Params.Text );

Los parámetros serán los datos que el usuario ha rellenado en el formulario web antes de pulsar el botón Enviar.

2º Si el usuario pulsa los botones de registrarse en la página o hacer login entonces envío a cada uno a su procedimiento correspondiente para dar más claridad al código:

if ARequestInfo.Document = '/registrar' then
RegistrarUsuario( ARequestInfo, AResponseInfo );

if ARequestInfo.Document = '/entrar' then
Entrar( ARequestInfo, AResponseInfo );

3º En caso de que el usuario haya solicitado otra página al servidor se la mandamos normalmente:

// ¿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
AResponseInfo.ServeFile( AContext, sDocumento )
else
// No hemos encontrado la página
AResponseInfo.ResponseNo := 404;
end;

AResponseInfo.CloseConnection := True;
end;

CONTROLANDO EL REGISTRO DE USUARIOS

El procedimiento encargado de dar de alta los usuarios tiene que comprobar si el usuario ya rellenado correctamente su nombre, la contraseña y si las contraseñas coinciden:

procedure TFServidorHTTP.RegistrarUsuario( ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo );
var
sNombre, sPassword, sPassword2, sError, sUsuarios: String;
Usuarios: TStringList;
begin
sNombre := ARequestInfo.Params.Values['nombre'];
sPassword := ARequestInfo.Params.Values['password'];
sPassword2 := ARequestInfo.Params.Values['password2'];

if sPassword <> sPassword2 then
sError := '<h3>Las contraseñas no coinciden.</h3>';

if sPassword2 = '' then
sError := '<h3>Debe repetir la contraseña.</h3>';

if sPassword = '' then
sError := '<h3>No ha introducido la contraseña.</h3>';

if sNombre = '' then
sError := '<h3>No ha introducido el nombre del usuario.</h3>';

// Abrimos la lista de usuarios
sUsuarios := ExtractFilePath( Application.ExeName ) + 'usuarios.txt';
Usuarios := TStringList.Create;

if FileExists( sUsuarios ) then
Usuarios.LoadFromFile( sUsuarios );

// Comprobamos si el usuario ya ha sido dado de alta en la lista
if sError = '' then
begin
if Usuarios.Values[sNombre] <> '' then
sError := '<h3>El usuario ya existe. Elija otro nombre.</h3>';
end;

if sError <> '' then
AResponseInfo.ContentText := sError
else
begin
Usuarios.Add( sNombre + '=' + sPassword );

AResponseInfo.ContentText := '<h3>Usuario registrado correctamente:<p>' +
'Nombre: ' + sNombre + '<p>' + 'Contraseña: ' + sPassword + '<p>' +
'<a href="http://www.blogger.com/index.html">Entrar al foro.</a></p></h3>';

Usuarios.SaveToFile( sUsuarios );
end;

AResponseInfo.WriteContent;
Usuarios.Free;
end;

En caso de error mostraría su mensaje correspondiente:


En el caso de que se hayan escrito bien todos los datos lo que hago es guardar la lista de usuarios y sus contraseñas en un archivo de texto que se llamará usuarios.txt. También compruebo si el usuario ya ha sido dado de alta con anterioridad.

Una vez que ha sido dado de alta lo muestro en pantalla:


A su vez vemos como la ventana del servidor va controlando lo que manda el navegador:


Al pulsar el enlace Entrar al foro volverá a la pantalla principal para hacer login.

CONTROLANDO LA ENTRADA DE USUARIOS

El procedimiento encargado de entrar en nuestro foro es parecido y más sencillo que el de registro:

procedure TFServidorHTTP.Entrar( ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo );
var
sNombre, sPassword, sError, sUsuarios: String;
Usuarios: TStringList;
begin
sNombre := ARequestInfo.Params.Values['nombre'];
sPassword := ARequestInfo.Params.Values['password'];

// Abrimos la lista de usuarios
sUsuarios := ExtractFilePath( Application.ExeName ) + 'usuarios.txt';
Usuarios := TStringList.Create;

if FileExists( sUsuarios ) then
Usuarios.LoadFromFile( sUsuarios );

// Comprobamos si el usuario ya ha sido dado de alta en la lista
if Usuarios.Values[sNombre] = '' then
sError := '<h3>El usuario no existe.</h3>'
else
if Usuarios.Values[sNombre] <> sPassword then
sError := '<h3>La contraseña es incorrecta.</h3>';

if sError <> '' then
AResponseInfo.ContentText := sError
else
AResponseInfo.ContentText := '<h3>Bienvenido al foro ' + sNombre + '<p>' +
'<a href="http://www.blogger.com/index.html">Salir</a></h3>';

AResponseInfo.WriteContent;
Usuarios.Free;
end;

Sólo hay que asegurarse de que el usuario esté dado de alta en nuestra lista y luego lo dejamos pasar:


Todos los usuarios datos de alta quedan almacenados en el archivo usuarios.txt:


En el próximo artículo vamos a implementar el sistema visualización y envío de mensajes al foro por parte de cada usuario.

No he incluido las diferencias respecto a Delphi 7 porque los cambios son los mismos respecto a los anteriores artículos (ServeFile).

Pruebas realizadas en RAD Studio 2007.

14 marzo 2008

Creando aplicaciones multicapa (y VII)

Voy a finalizar este artículo hablando sobre la creación de aplicaciones multicapa utilizando servicios web a través del protocolo SOAP.

Para ello voy a utilizar el servidor Apache para alojar el ejecutable de mi servidor de aplicaciones.

CREANDO EL SERVIDOR DE APLICACIONES

El servidor de aplicaciones va a ser un ejecutable CGI que vamos a situar en el directorio cgi-bin de Apache. Los pasos para su creación son los siguientes:

1. Selecciona File -> New -> Other. Seleccionamos la pestaña WebServices, elegimos SOAP Server application y pulsamos Ok:


Al pulsar Ok nos preguntará el tipo de servidor que vamos a crear:


2. Como quiero que mi servidor web sea un ejecutable independiente voy a seleccionar la segunda opción: CGI Stand-alone executable. Al pulsar Ok nos creará un proyecto nuevo que incluye el siguiente módulo de datos web:

3. Nos preguntará si deseamos crear una interfaz para el módulo SOAP. Pulsamos No.

4. Cuando pulsemos el botón Save All para guardar todo el proyecto, vamos a poner como nombre el objeto TWebModule1 como UServidorSOAP.pas y el nombre del proyecto como ServidorSoap.dpr. El proyecto creado sólo se compone de dos partes; por un lado el archivo DPR del proyecto:


Y una unidad de la clase TWebModule que es la encargada de recoger las peticiones de los clientes:


5. Ahora vamos a añadir un módulo de datos remoto: File -> New -> Other, pestaña WebServices, seleccionamos SOAP Server Data Module y pulsamos Ok.

6. Nos preguntará el nombre del módulo: ModuloSOAP y pulsamos Ok. Lo guardamos también como UModuloSOAP.pas.

7. En este módulo de datos insertamos los componentes de acceso a nuestra base de datos Firebird, al igual que hicimos en artículos anteriores:


8. Compilamos el proyecto y nos generará el archivo ServidorSoap.exe. Si ejecutamos ese archivo nos devuelve una página web con la descripción de nuestros servicios web disponibles. Puede verse abriendo una ventana de comandos y ejecutando:

ServidorSoap.exe > servicios.html

9. Copiamos el ejecutable ServidorSoap.exe al directorio cgi-bin donde tengamos instalado el servidor web Apache. Para comprobar que nuestro servidor de aplicaciones esta operativo, abrimos un navegador web y escribimos la siguiente dirección:

http://127.0.0.1/cgi-bin/ServidorSoap.exe

Nos mostrará la misma página web que hemos guardado en servicios.html:


Con esto ya tenemos creado nuestro servidor de aplicaciones.

CREANDO LA APLICACION CLIENTE

La aplicación cliente va a ser similar a todas las que hemos creado hasta ahora:


Como puede verse en la imagen, he incluido un componente de la clase TSoapConnection de la pestaña de componentes WebServices. Al componente lo he llamado ClienteSoap y he modificado las siguientes propiedades:

URL: http://127.0.0.1/cgi-bin/ServidorSoap.exe/SOAP/
SOAPServeIID: IAppServerSOAP - {C99F4735-D6D2-495C-8CA2-E53E5A439E61}
Connected: True

Y en el componente TClientes (ClientDataSet) modificamos:

RemoteServer: ClienteSOAP
ProviderName: DSPCliente
Active: True

Haciendo esto debe verse la lista de clientes.

Este es el tipo de aplicación multicapa que más me gusta, ya que al trabajar a través del puerto 80 y mediante XML-SOAP no es necesario abrir puertos en los routers ni en los cortafuegos de los antivirus.

Como hemos visto anteriormente al crear el servidor de aplicaciones, también pueden crearse servidores que estén dentro de una librería DLL ya sea para el servidor apache o para el servidor Internet Information Server (IIS) de Microsoft.

Pruebas realizadas con Delphi 7.0, Firebird 2.0 y Apache Server 2.2.8.

07 marzo 2008

Creando aplicaciones multicapa (VI)

Hoy vamos a ver como crear las aplicaciones cliente y servidor utilizando el protocolo HTTP para establecer la comunicación. Para que funcione este método es necesario tener instalado el servidor Internet Information Server de Microsoft.

Si teneis Windows XP profesional se puede instalar de la siguiente manera:

1. Inicio -> Configuración -> Panel de control.

2. Hacemos doble clic sobre el icono Agregar o quitar programas.

3. Seleccionamos Agregar o quitar componentes de Windows.

4. Activamos Servicios de Internet Information Server (IIS).

5. Pulsamos Siguiente y nos pedirá el CD de Windows XP. Lo introducimos y pulsamos Siguiente.

6. Cuando termine la instalación pulsamos el botón Finalizar.

Con esto quedará instalado y ejecutado el servidor web de Microsoft.

CREANDO EL SERVIDOR DE APLICACIONES

La creación del servidor de aplicaciones es idéntica a la creada en el artículo anterior mediante sockets. Queda resumido en estos pasos:

1. Creamos un nuevo proyecto y lo guardamos.

2. Creamos un módulo de datos remoto: File -> New -> Other, pestaña Multitier, seleccionamos Remote Data Module y pulsamos Ok.

3. En la nueva ventana que aparece escribimos en el campo CoClass Name: ServidorHTTP.

4. Guardamos el módulo de datos remoto con el nombre UServidorWeb.pas.

5. Insertamos en el módulo de datos remoto los componentes encargados de conectar con nuestra base de datos. No voy a volver a explicarlo de nuevo ya que son los mismos componentes que he explicado en artículos anteriores:


Prácticamente no cambia nada respecto a hacerlo con sockets o con DCOM. En los tres casos lo que hace el módulo de datos es quedar registrado en Windows mediante un número de interfaz.

CREANDO LA APLICACION CLIENTE

Para crear la aplicación cliente rápidamente, podemos crear un proyecto nuevo y luego nos traemos del artículo anterior el formulario que utilizamos para crear la aplicación cliente, menos el componente de la clase TSocketConnection.

A dicho formulario le añadimos el componente WebConnection (pestaña DataSnap) y le ponemos de nombre ClienteWeb. Quedaría del siguiente modo:


Y aquí es donde empiezan las complicaciones. En primer lugar hay que copiar el archivo httpsrvr.dll que se encuentra en la carpeta bin de Delphi al directorio scripts de donde esté instalado Internet Information Server. Eso en teoria (según la documentación de Borland), porque yo no he encontrado dicho directorio (por lo menos en mi Windows XP Profesional SP2). Lo que vamos a hacer es copiar esa DLL donde esté nuestro servidor de aplicaciones HTTP.

Lo que he hecho en su lugar es ejecutar el programa que administra IIS del siguiente modo:

1. Inicio -> Configuración -> Panel de control.

2. Hacemos doble clic sobre Herramientas Administrativas.

3. Hacemos doble clic sobre Servicios de Internet Information Server.

4. Vamos pulsando el icono [+] del arbol de tenemos a la izquierda hasta acceder al icono Sitio Web Predeterminado. Se puede ver la carpeta scripts donde se pueden añadir directorios virtuales. He probado a añadir la carpeta donde está mi servidor HTTP pero ni caso.

5. Pulsamos con el botón derecho del ratón sobre el icono Sito Web Predeterminado y seleccionamos la opción Nuevo -> Directorio Virtual...


6. Pulsamos el botón Siguiente.

7. En Alias escribimos ServidorHTTP y pulsamos Siguiente.


8. Seleccionamos el directorio donde se encuentra nuestro proyecto del Servidor HTTP y el archivo httpsrvr.dll.

9. Activamos la opción Ejecutar (por ejemplo, aplicaciones ISAPI o CGI) y pulsamos Siguiente.


10. Pulsamos Finalizar.

11. Nos vamos a nuestro proyecto Cliente HTTP de Delphi 7 y seleccionamos el componente WebConnection.

12. En la propiedad ServerName seleccionamos ServidorHTTP.ServidorWeb. Se rellenará automáticamente el campo ServerGUID.

Y cuando parece que va a ir todo perfecto y activamos la opción Connected aparece este mensaje:


Y aquí es donde me he quedado. Por mucho que he dado permisos en IIS y por muchas vueltas que le he dado a los proyectos cliente y servidor no hay manera de evitar dicho mensaje. Y no he encontrado ninguna información útil por la red que solucione el problema (ni en castellano ni en inglés), bueno sí, en ruso, pero...

También he probado a copiar el archivo httpsrvr.dll dentro del directorio cgi-bin del servidor Apache y aunque llega a conectar con el mismo, me devuelve un error 500 que no lo conoce ni su padre.

Por lo tanto, aunque consiga solucionar este problema, no me gusta tener que depender de IIS para crear aplicaciones mediante HTTP. Prefiero utilizar sockets o DCOM.

Si alguien ha solucionado el problema que me lo cuente, porque yo no lo he logrado de ningún modo.

En la siguiente parte del artículo veremos como realizar lo mismo mediante servicios Web utilizando SOAP.

Pruebas realizadas en Delphi 7 y Firebird 2.0.

29 febrero 2008

Creando aplicaciones multicapa (V)

En esta ocasión vamos a crear un servidor de aplicaciones utilizando sockets en lugar de utilizar DCOM. Aunque los pasos son prácticamente los mismos, hay una pequeña diferencia: hay que utilizar un programa de Borland que hace de puente entre nuestro servidor de aplicaciones y las aplicaciones cliente.

Veamos un ejemplo paso a paso. Antes de empezar creamos una carpeta donde alojar el proyecto del servidor de aplicaciones. Por ejemplo:

D:\Desarrollo\DelphiAlLimite\Multicapa\ServidorSockets\

CREANDO EL SERVIDOR DE APLICACIONES

1. Creamos un nuevo proyecto (File -> New -> Application).

2. La ventana principal de la aplicación la llamamos FPrincipal, guardando su unidad con el nombre UPrincipal. Como título de esta ventana podemos poner: Servidor de Sockets.

3. Añadimos al proyecto un módulo de datos remoto: File -> New -> Other. Pulsamos sobre la pestaña Multitier, seleccionamos Remote Data Module y pulsamos Ok. Aparecerá la siguiente ventana:


3. En el campo CoClass Name escribimos Servidor y pulsamos Ok.

4. Guardamos la unidad del módulo de datos remoto creado con el nombre UServidor.

5. Al igual que hicimos en el servidor DCOM vamos a crear los siguientes componentes para acceder a la base de datos Firebird:


Se compone de:

- Un componente IBDatabase llamado BaseDatos (pestaña Interbase).

- Un componente IBTransaction llamado Transaccion (pestaña Interbase).

- Un componente IBQuery llamado TClientes (pestaña Interbase).

- Un componente DataSetProvider llamado DSPClientes (pestaña Data Access).

6. Para el componente IBDatabase modificamos las siguientes propiedades:

DatabaseName: 127.0.0.1:D:\Desarrollo\DelphiAlLimite\Multicapa\ServidorSockets\BASEDATOS.FDB
DefaultTransaction: Transaccion.
LoginPrompt: False
SQLDialect: 3

7. Para el componente IBTransaction ponemos en su propiedad DefaultDataBase: BaseDatos.

8. Para el componente IBQuery llamado TClientes cambiamos las propiedades:

Database: BaseDatos
Transaction: Transaccion
SQL: SELECT * FROM CLIENTES

9. Y en el componente DataSetProvider seleccionamos TClientes en su propiedad DataSet.

Con esto ya tenemos un servidor básico de aplicaciones utilizando Sockets. Como se puede apreciar no hay mucha diferencia respecto al servidor DCOM que creamos con anterioridad. Ahora vamos a ver como crear la aplicación cliente.

CREANDO LA APLICACION CLIENTE

En esta ocasión vamos a crear la aplicación cliente utilizando un módulo de datos remoto normal (no transaccional). Este módulo de datos no tiene la misma seguridad avanzada que un módulo de datos transaccional pero la comunicación es muy rápida, gastando muy poco ancho de banda.

Cuando establecemos una conexión con el servidor de aplicaciones usando sockets la comunicación se realiza a través del protocolo normal TCP/IP, gozando de la ventaja de poder traspasar cualquier red.

Aquí podemos encontrar una diferencia respecto a un cliente DCOM. Cuando conectamos con el servidor debemos tener arrancado un programa llamado Borland Socket Server que es el intermediario entre nuestro servidor y el cliente. Este programa esta en el directorio bin de la carpeta donde esté instalado Delphi. Lo recomendable sería copiar este programa (scktsrvr.exe) al directorio donde tengamos nuestro servidor de aplicaciones.

Para crear la aplicación cliente vamos a seguir los siguientes pasos:

1. Creamos una carpeta para alojar la aplicación cliente. Por ejemplo:

D:\Desarrollo\DelphiAlLimite\Multicapa\ClienteSockets\

2. Creamos un nuevo proyecto (File -> New -> Application) y le damos a la ventana principal el nombre FCliente. La guardamos con el nombre UCliente.pas.

3. En esta ventana vamos a añadir los siguientes componentes:


- Un componente ClientDataSet llamado TClientes (pestaña Data Access).

- Un componente DataSource llamado DSClientes (pestaña Data Access).

- Un componente SocketConnection que vamos a llamar ClienteSocket (pestaña DataSnap).

- Un componente DBGrid llamado ListadoClientes (pestaña Data Controls).

4. Antes de intentar conectar el cliente debemos arrancar el programa de Borland scktsrvr.exe. Al ejecutar este programa se quedará residente en la bandeja del sistema (el icono azul con el rayo amarillo):


Si pulsamos el botón derecho del ratón sobre el icono y seleccionamos Properties podemos ver la su configuración y puertos por defecto (por si nos interesa cambiarlos).

5. Para el componente SockectConnection modificamos sus propiedades:

Address: 127.0.0.1
ServerName: ServidorSockets.Servidor
Connected: True

Al seleccionar ServidorSockets.Servidor rellenará automáticamente el campo ServerGUID. Al activar la propiedad Connected arrancará automáticamente nuestro programa que hace de servidor, al igual que ocurría con nuestro servidor DCOM. Pero recordad que esto no funciona si no esta en ejecución el programa Borland Socket Server (scktsrvr.exe).

6. Para el componente ClientDataSet fijamos las siguientes propiedades:

ProviderName: DSPClientes
RemoteServer: ClienteSocket

7. Seleccionamos TClientes en la propiedad DataSet del componente DSClientes.

8. Y por último vinculamos el componente DBGrid al componente DSClientes a través de su propiedad DataSource.

9. Si ponemos a True la propiedad Active del componente ClientDataSet aparecerán los datos en pantalla en tiempo de diseño:


Si abrimos una ventana de comandos (MS-DOS) y ejecutamos:

netstat -bn (para Windows XP con SP2)

podemos ver las conexiones abiertas tanto en Delphi como en el servidor de aplicaciones:


Como puede apreciarse la comunicación se ha realizado a través del puerto 211, lo que significa que habrá que abrir dicho puerto tanto en nuestros routers como en los cortafuegos si queremos establecer la comunicación correctamente.

En la siguiente parte de este artículo veremos como crear un servidor de aplicaciones y un cliente a través del protocolo HTTP (puerto 80).

Pruebas realizadas con Dephi 7.0 y Firebird 2.0.

Publicidad