Aplicación Web

Introducción

Para finalizar este proyecto, se ha desarrollado una aplicación web, programada en Lua, capaz de gestionar la configuración del router. La idea es tener una base flexible para poder incorporar de forma sencilla futuros protocolos y sus interfaces de configuración.

Jerarquía de directorios

La aplicación se ha distribuido en diferentes directorios con el fin de agrupar los ficheros según su función. La raíz de la aplicación se ha colocado en el directorio /Menu/web, de ahora en adelante, este directorio será tomado como el directorio raíz (/) por mayor simplicidad.

  • /: El directorio raíz contendrá todos los demás directorios, además de las páginas web (ficheros .lua).
  • /img: En este directorio se encontrarán todas las imágenes que se vayan a mostrar en la aplicación.
  • /css: Este directorio contiene las hojas de estilo en cascada (o CSS) necesarias para dar formato a las páginas web.
  • /include: Contiene elementos comunes a todas las páginas, como la cabecera o el menú, que serán explicados más adelante.
  • /config: Este directorio está destinado a almacenar los scripts que gestionarán la configuración del router. El funcionamiento de dichos scripts será explicado en profundidad más adelante.

Elementos comunes

A continuación se muestran los elementos que son comunes a varias páginas, que han sido extraídos en ficheros diferentes para facilitar sus posibles modificaciones futuras. Todos estos elementos se encuentran en el directorio /include.

Cabecera.lua

Este fichero contiene un script que genera un código HTML con la cabecera de la aplicación que se muestra a continuación:

Cabecera

Menu.lua

Este script genera un menú de navegación haciendo uso de otro script llamado Arbol.lua que gestiona la creación de nodos y subnodos y su conversión a código HTML.

Si se desea añadir o modificar un menú se debe editar el script Menu.lua y actualizarlo haciendo uso de la función Arbol:CrearNodo(ID, Nombre, Pagina, IDNodoPadre) para crear los nodos que se deseen y Arbol:ImprimirHTML() para generar el código HTML, ambas funciones están definidas en Arbol.lua

Un ejemplo del script sería el siguiente:

Arbol=require("Arbol");
 
Arbol:CrearNodo("n1","Nodo 1","pagina_del_nodo1.lua",nil);
Arbol:CrearNodo("sn1","SubNodo 1","pagina_del_subnodo1.lua","n1");
Arbol:CrearNodo("sn4","Nodo nivel 3","pagina_del_subnodo4.lua","sn1");
 
Arbol:CrearNodo("n2","Nodo 2","pagina_del_nodo2.lua",nil);
Arbol:CrearNodo("sn2","SubNodo 2","pagina_del_subnodo2.html","n2");
Arbol:CrearNodo("sn3","SubNodo 3","pagina_del_subnodo2.html","n2");
 
Arbol:CrearNodo("n3","Nodo 3","pagina_del_nodo3.html",nil);
 
Arbol:ImprimirHTML();

Y el menú que generaría sería el siguiente:

Menú

Generación de una página web

Como se ha indicado anteriormente, la idea es que todas las páginas web estén codificadas en Lua, y de forma que el código HTML que generen se envíe por el socket hacia el cliente (que previamente ha sido redirigido a la entrada y salida estándar, como especifica el estándar de CGI.

Además, las páginas creadas en Lua pueden hacer uso de las bibliotecas explicadas en el punto anterior (Desarrollo del servidor web).

Un ejemplo simple del funcionamiento sería la página principal (index.lua):

#!/bin/lua
 
__EnableSession=true;
__EnablePOST=true;
require("LuaWeb");
 
SetHeader();
 
io.write([[
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//ES">
<html>
<head>
	<title>Atlas 50</title>
	<link rel="stylesheet" href="css/styles.css">
</head>
<body>
]]);
 
require("Cabecera");
require("Menu");
 
io.write([[
	<p class="texto">
		P&aacute;gina principal de la interfaz de configuraci&oacute;n del router Atlas 50
		<br />
		<br />
		Mediante esta interfaz es posible configurar los protocolos mostrados en el men&uacute; de la izquierda
		de forma r&aacute;pida y sencilla.
		<br />
		<br />
		Actualmente el n&uacute;mero de protocolos y opciones de configuraci&oacute;n est&aacute; restringido a un
		conjunto m&iacute;nimo, pero se espera mejorarlo en el futuro.
	</p>
</body>
</html>
]]);
 
EndResponse();

El código anterior genera la siguiente página web:

index.lua

Interfaz de configuración

El objetivo de esta aplicación web es poder gestionar la configuración de todos los aspectos y protocolos del router.

En este proyecto se ha desarrollado una base que será común a todas las páginas web de gestión de configuración. Se ha dedicado mucho esfuerzo a este punto para poder partir de una base estándar en el futuro y poder implementar los futuros protocolos de forma fácil y sencilla.

Este módulo desarrollado, llamado ConfiguraciconGenerica.lua, se encarga de aislar la interfaz web y la gestión de la configuración, de forma que el desarrollador de futuros ficheros de configuración pueda hacerlo de forma más clara.

A continuación se muestra un diagrama de cómo se consigue esto y se explicará cada uno de los elementos:

Diagrama configuración

  • elemento.lua: Código HTML de la página (no incluye el código de los formularios, que será generado automáticamente por ConfiguracionGenerica.lua según las especificaciones del siguiente módulo). En este script se colocará lo particular de cada página, como: instrucciones, comentarios o cualquier código necesario para ampliar la funcionalidad de esta biblioteca.





  • config_elemento.lua: Indica cuál es el fichero en el que se guarda la configuración. Especifica la estructura de dicho fichero y las definiciones de los campos que contiene. Además, es donde se encuentran las funciones específicas, tales como: eventos de los botones de los formularos, reglas de validación de los formularios, opciones sobre cómo mostrar los datos.



  • ConfiguracinGenerica.lua: Crea el código HTML que se muestra en la página, edita el fichero de configuración, actúa de interfaz entre la página web y el script de configuración. Define varias operaciones estándar que pueden ser sobrescritas en el script de configuración.





  • elemento.conf.lua: Almacena el estado de la configuración en una tabla Lua. Esto quiere decir que no se realiza ningún tipo de parsing más allá del que realiza el intérprete de Lua para recuperar el estado de la configuración.

Elemento.lua

Donde Elemento es el nombre del aspecto del router que se quiere configurar, un posible ejemplo del nombre de este script sería IPSec.lua si gestiona la configuración de IPSec.

Ésta va a ser la página web que permitirá la configuración de un elemento concreto. En este script se especificará el código HTML de la página, principalmente aspectos de diseño (estructura, estilos, imágenes…) e información para el usuario (instrucciones sobre cómo realizar el proceso de configuración, advertencias…).

Además, se puede incluir cualquier código necesario para ampliar la biblioteca, como por ejemplo un botón para ejecutar la configuración, como se puede ver en el ejemplo de IPSec más adelante.

Es necesario hacer referencia al módulo que especifica la configuración en el punto en el que se quiere que se incruste el código HTML de la misma, ya que los sucesivos módulos generan el código directamente al cliente.

Ejemplo

El siguiente ejemplo muestra una página sencilla de configuración de IPSec:

IPSec.lua
#!/bin/lua
 
__EnableSession=true;
__EnablePOST=true;
require("LuaWeb");
 
SetHeader();
 
io.write([[
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//ES">
<html>
<head>
	<title>Atlas50 - IPSec</title>
	<link rel="stylesheet" href="css/styles.css">
</head>
<body>
 
]]);
 
require("Cabecera");
require("Menu");
 
require("ConfIPSec");
 
io.write([[ 
</body>
</html>
]]);
 
EndResponse();

Config_elemento.lua

Donde Elemento es el nombre del aspecto del router que se quiere configurar, un posible ejemplo del nombre de este script sería Config_IPSec.lua si gestiona la configuración de IPSec.

En este script se encontrarán los aspectos específicos de la configuración que se desea gestionar. Ejemplos de estos aspectos son:

  • Opciones de configuración sobre los ficheros de datos y el HTML generado.
  • Descripción de campos del fichero de configuración.
  • Validación de los formularios de datos.
  • Resto de funcionalidad que se necesite incorporar.

Nota: Este módulo debe incluir la configuración genérica mediante require(“ConfiguracionGenerica”);. Además, debe llamar explícitamente a la función de load Conf_Load(); (definida en la configuración genérica) como última acción en el script (debe ser la última línea).

Opciones de configuración

Pueden aparecer dos variables globales (se espera que haya más en el futuro) que utilizará el módulo de configuración genérica, estas son:

  • RutaConf (obligatorio): contiene un string con la ruta del fichero donde se guarda la configuración.
  • Conf_ImprimirHTMLEnTabla (opcional): contiene un valor booleano que indica si la configuración se muestra en la página web en forma de tabla (cuando el valor de la variable es verdadero) o como formulario (cuando es falso).

DescripcionCampos

Se deberá definir una variable global, llamada DescripcionCampos, que contenga la información explicada más adelante.

Lo común en un fichero de configuración es que haya un sólo tipo de estructura, pero para hacerlo más flexible se ha incorporado la posibilidad de definir varios tipos, ya que habrá casos en los que sea necesario (por ejemplo en el ejemplo de IPSec existen dos tipos: uno es la definición de un túnel, y otro es la definición de las listas de acceso, y ambos tipos se almacenan en el mismo fichero).

La variable será de esta forma:

DescripcionCampos={
	IDTipo=ValorTipo
	[,IDTipo=ValorTipo ...]
  }

IDTipo ::= Este es el identificador del tipo. Cada ID es un identificador único.

ValorTipo ::= <lista de Entrada>

Entrada ::= {Campo | __Acciones = <lista de Acciones> | __Especiales = <lista de Especiales>}

Campo ::= {IDCampo, NombreCampo, ClaseCampo, OpcionesCampo}

IDCampo ::= Este es el identificador del campo, debe ser único y contener sólo caracteres alfanuméricos (A-Za-z).

NombreCampo ::= Nombre del campo, se admite cualquier string.

ClaseCampo ::= {texto | opcion} 
               Indica si el campo va a ser un campo de texto libre o un desplegable.
             
OpcionesCampo ::= Son los parámetros de configuración para el campo, estos parámetros cambiarán en función de ClaseCampo:
                  Si ClaseCampo = texto: OpcionesCampo será un entero representando el tamaño del campo
                  Si ClaseCampo = opcion: OpcionesCampo será una lista de elementos que contendrá el desplegable de la forma: 
                        {nombre1, clave1, nombre2, clave2 ...}.

Acciones ::= {IDCampo, NombreCampo, Evento, Visibilidad}. Son los botones (eventos) que contendrá cada formulario.

Evento ::= Es una función Lua definida por el usuario que se invocará cuando se pinche en el botón correspondiente.
           Dicha función no recibirá ningún parámetro explícitamente, sino que deberá recoger los valores a través del método POST.
           Se han creado dos prototipos genéricos para estos eventos en el módulo ConfiguracionGlobal.lua, que son los siguientes:
               Conf_ModificacionElemento: Para crear/modificar un elemento.
               Conf_EliminacionElemento: Para eliminar un elemento.

Visibilidad ::= Indica en qué condiciones será visible el botón.
                La visibilidad se puede especificar mediante un valor booleano, o mediante una función que devuelva un booleano.
                Si se ha especificado con una función, se invocará a ella una vez por formulario (más una vez para el formulario vacío 
                en caso de haberlo, ver más adelante), con los siguientes parámetros:
                     IDCampo: El IDCampo especificado.
                     Indice: El índice de los datos en la tabla Conf_Datos (ver módulo ConfiguracionGenerica.lua).
                          NOTA: en caso de formularios vacío contendrá un string
                     Datos: El contenido de los datos (equivale a Conf_Datos[Indice])

Especiales ::= Parametro = Valor. Donde los valores de Parametro y Valor pueden ser los siguientes:
               PermitirCrearNuevo = (booleano): Indica si se admite la creación de un nuevo formulario. Si se especifica como True, se 
                                                creará un formulario vacío adicional a los demás.
                                                
               TextoTablaCreacion = (string): Es el texto que aparecerá en el formulario vacío (ver opción anterior). Sólo se tendrá en 
                                              cuenta si se ha definido la variable Conf_ImprimirHTMLEnTabla coomo verdadero.
                                              
               CamposTablaHTML = (lista de string): Este campo contendrá una lista de IDCampo que se mostrarán como valor de fila de cada
                                                    formulario. Sólo se tendrá en cuenta si se ha definido la variable 
                                                    Conf_ImprimirHTMLEnTabla coomo verdadero.





Un ejemplo de la configuración utilizada en IPSec es:

function VisibilidadBoton(IDCampo, Indice, Datos)
        --Si el tipo de Indice no es número es un formulario para crear
	if(IDCampo=="Crear") then
		return (type(Indice)=="string");
	else
		return (type(Indice)=="number");
	end
end
 
DescripcionCampos={
	tunel={
		{"Identificador","Identificador","texto",5},
		{"Inicio","Inicio del t&uacute;nel","texto",15},
		{"Fin","Fin del t&uacute;nel","texto",15},
		{"Metodo","M&eacute;todo","opcion",{"ESP","esp","AH","ah"}},
		{"SPI","SPI","texto",7},
		{"Encriptacion","Encriptaci&oacute;n","opcion",{"Triple DES","3des-cbc","DES","des-cbc","Blowfish","blowfish-cbc"}},
		{"ClaveEnc","Clave","texto",80},
		{"Autenticacion","Autenticaci&oacute;n","opcion",{"SHA1","hmac-sha1","MD5","hmac-md5"}},
		{"ClaveAut","Clave","texto",80},
		__Acciones={
			{"Crear","Crear",Conf_ModificacionElemento,VisibilidadBoton},
			{"Modificar","Modificar",Conf_ModificacionElemento,VisibilidadBoton},
			{"Eliminar","Eliminar",Conf_EliminacionElemento,VisibilidadBoton},
		},
		__Especiales={
			PermitirCrearNuevo=true,
			TextoTablaCreacion="Definir un nuevo t&uacute;nel",
			CamposTablaHTML={"Identificador","Inicio","Fin"},
		}
	}
};

Validación de formularios

El módulo ConfiguracionGenerica.lua invocará una función llamada Conf_Validacion (en caso de existir) con los siguientes parámetros:

  • Indice: El índice de los datos en la tabla Conf_Datos (ver módulo ConfiguracionGenerica.lua).
  • Entrada: El contenido de los datos (equivale a Conf_Datos[Indice]).

Esta función deberá devolver un valor booleano que será verdadero si la validación ha sido satisfactoria y falso en caso contrario.

NOTA: El módulo de ConfiguracionGenerica.lua no mostrará ningún mensaje de error si la validación es incorrecta, por lo que se deberá enviar el mensaje desde esta función mediante io.write.

Ejemplo

Este es un ejemplo completo del fichero de configuración Conf_IPSec.lua:

Conf_IPSec.lua
require("ConfiguracionGenerica");
RutaConf="config/IPSec.conf.lua";
Conf_ImprimirHTMLEnTabla=true;
 
function VisibilidadBoton(IDCampo, Indice, Datos)
	if(IDCampo=="Crear") then
	    --Si el tipo de Indice no es número es un formulario para crear
		return (type(Indice)=="string");
	else
		return (type(Indice)=="number");
	end	
end
 
DescripcionCampos={
	tunel={
		{"Identificador","Identificador","texto",5},
		{"Inicio","Inicio del t&uacute;nel","texto",15},
		{"Fin","Fin del t&uacute;nel","texto",15},
		{"Metodo","M&eacute;todo","opcion",{"ESP","esp","AH","ah"}},
 
		{"SPI","SPI","texto",7},
		{"Encriptacion","Encriptaci&oacute;n","opcion",{"Triple DES","3des-cbc","DES","des-cbc","Blowfish","blowfish-cbc"}},
		{"ClaveEnc","Clave","texto",80},
		{"Autenticacion","Autenticaci&oacute;n","opcion",{"SHA1","hmac-sha1","MD5","hmac-md5"}},
		{"ClaveAut","Clave","texto",80},
		__Acciones={
			{"Crear","Crear",Conf_ModificacionElemento,VisibilidadBoton},
			{"Modificar","Modificar",Conf_ModificacionElemento,VisibilidadBoton},
			{"Eliminar","Eliminar",Conf_EliminacionElemento,VisibilidadBoton},
		},
		__Especiales={
			PermitirCrearNuevo=true,
			TextoTablaCreacion="Definir un nuevo t&uacute;nel",
			CamposTablaHTML={"Identificador","Inicio","Fin"},
		}
	},
	lista={}
};
 
function Conf_Validacion(Indice, entrada)
	local texto="";
	if not CheckIP(POST.Inicio) then
		texto = texto .. iif(texto=="","","<br />") .. "Direcci&oacute;n IP de inicio incorrecta.";
	end
	if not CheckIP(POST.Fin) then
		texto = texto .. iif(texto=="","","<br />") .. "Direcci&oacute;n IP de fin incorrecta.";
	end
	if texto=="" then
		return true;
	else
		texto=[[<p class="error"><strong>Se han detectado los siguientes errores:</strong><br />]] .. texto .. "</p>";
		io.write(texto);
		return false;
	end
end

ConfiguracionGenerica.lua

Este módulo define varias funciones que actúan de interfaz entre la aplicación web y la entidad que se quiere configurar. De esta forma, se encarga de gestionar la recuperación y la modificación de datos del fichero de configuración, de gestionar los eventos provenientes a través del método POST (definidos en el módulo anterior) y de generar el código HTML que se mostrará en la página web resultante.

Este módulo dispone de varias funciones genéricas, como se ha dicho anteriormente, que pueden ser redefinidas en el módulo de configuración (visto anteriormente) en caso de necesitarse. Puede hacerse de la siguiente forma, por ejemplo con Conf_Load (definida más adelante):

--Redefinimos en el script Conf_IPSec.lua la función Conf_Load definida en ConfiguracionGenerica.lua
Conf_Load_Old = Conf_Load;
function Conf_Load()
        ... (código nuevo) ...
 
	Conf_Load_Old(); --Invocamos a la función original (si es necesario)
 
        ... (código nuevo) ...
end




Las funciones de las que dispone este módulo son las siguientes:

Conf_Load()

Es la única función llamada explícitamente desde el módulo de configuración.

Invoca a las funciones Conf_CargarConf(), Conf_GenerarEventos() y Conf_ImprimirHTML()

Conf_CargarConf() / Conf_GuardarConf()

Estas funciones cargan y guardan los datos de configuración, respectivamente, en el fichero especificado por la variable global RutaConf que ha de definirse en el módulo de configuración, como se ha visto anteriormente.

Ambas funciones utilizan la funcionalidad del módulo externo table_save para guardar los datos o recuperarlos en formato de tabla Lua.

Conf_GenerarEventos()

Comprueba si se ha producido un evento (click en un botón) de entre los definidos en la variable DescripcionCampos en el módulo de configuración, de ser así invoca a la función de tratamiento de evento definida en dicha variable.

La función encargada de manejar el evento no recibirá ningún argumento, todos la información deberá obtenerla a través del método POST. La información que se enviará a través de POST será la siguiente:

  • Conf_target: Indice del botón que ha generado el evento (relativo a DescripcionCampos.<tipo>.__Acciones).
  • Conf_argument: Indice del formulario (relativo a DescripcionCampos.<tipo>).
  • Conf_type: Nombre del tipo de los datos que gestiona el formulario.
  • Una entrada por cada campo definido en DescripcionCampos.<tipo>, donde la clave será el IDCampo definido.



Por ejemplo, definiendo los siguientes campos:

DescripcionCampos={
	tunel={
		{"Inicio","Inicio del t&uacute;nel","texto",15},
		{"Fin","Fin del t&uacute;nel","texto",15},
		{"Metodo","M&eacute;todo","opcion",{"ESP","esp","AH","ah"}},
		__Acciones={
			{"Invisible","Invisible",nil,false},
			{"Modificar","Modificar",Conf_ModificacionElemento,VisibilidadBoton},
		}
	}
};



Se genera el siguiente formulario.

Suponiendo que el usuario ha introducido los datos mostrados en dicho formulario, se recibirán los siguientes datos en la variable POST:

POST = {
  Conf_target=2, --El segundo botón de la lista
  Conf_argument=1, --El primer formulario
  Conf_type="tunel",
  Inicio="192.168.1.1",
  Fin="192.168.1.2",
  Metodo="esp"
}

Conf_ModificacionElemento() / Conf_EliminacionElemento()

Funciones genéricas que gestionan los datos del fichero de configuración.

La función ModificarElemento() se encarga tanto de la creación como de la modificación. Invoca a la función Conf_Validacion que debe definir el usuario (si es necesario) de la forma que se ha explicado anteriormente en Validación de formularios.

Esta función tiene acceso a los mismos valores de la variable POST descritos en el apartado anterior.

Conf_ImprimirHTML()

Genera el código HTML y se lo envía al cliente siguiendo estas especificaciones:

  • Para cada tipo donde se haya especificado la opción DescripcionCampos.<tipo>.__Especiales.PermitirCrearNuevo=true en DescripcionCampos, se generará un formulario vacío.
    • Si se ha especificado la opción DescripcionCampos.<tipo>.__Especiales.TextoTablaCreacion se colocará ese texto a modo de título, en caso contrario se utilizará un texto genérico.
  • Si se ha declarado Conf_ImprimirHTMLEnTabla=false, o no se ha declarado: se colocarán los formularios uno a continuación de otro.
  • Si se ha declarado Conf_ImprimirHTMLEnTabla=true:
    • Se genera una tabla donde cada fila es un desplegable que contiene un formulario.
    • El texto de cada fila dependerá de lo que se haya declarado en DescripcionCampos.<tipo>.__Especiales.CamposTablaHTML, en caso de no haber especificado nada se utilizará un texto genérico.
  • Para cada dato de configuración que se vaya a mostrar:
    • Se creará un formulario con un identificador único.
    • Se crearán tres campos predefinidos en el formulario, que estarán disponibles cuando se produzca un POST:
      • Conf_target: Indice del botón que ha generado el evento (relativo a DescripcionCampos.<tipo>.__Acciones).
      • Conf_argument: Indice del formulario (relativo a DescripcionCampos.<tipo>).
      • Conf_type: Nombre del tipo de los datos que gestiona el formulario.
    • Se creará un campo por cada campo definido en DescripcionCampos.<tipo>.
    • Para cada acción en DescripcionCampos.<tipo>.__Acciones:
      • Se comprueba la visibilidad (ya sea un valor booleano o una función que devuelve un valor booleano) definida en DescripcionCampos. En caso de no ser visible se salta al siguiente elemento.
      • Se creará un botón que generará un evento mediante POST.
 
proyectos/teldatsi/aplicacion_web.txt · Última modificación: 2012/10/08 17:58 (editor externo)
 
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki