Programación de Servidores
WEB
En esta práctica estudiaremos una biblioteca
para programar servidores WEB utilizando el
protocolo CGI (Common Gateway Interface).
Para profundizar sobre el tema puedes consultar:
Ejemplo
#! c:\archiv~1\hugs98\runhugs
import AP.Cgi
page :: Html ()
page =
hHtml $
do { hHead $ hTitle $ hText "Hola"
; hBody $
hCenter $
do { hB $ hText "Hola Mundo"
; hBr
; hBr
; withCss azul $
hSpan $
do { hText "Página generada con "
; hI $ hText "Haskell y AP.Cgi"
}
}
}
azul :: [Attr]
azul = [ pColor =: vWhite
, pBackgroundColor =: vBlue
, pFontFamily =: value "Arial"
, pFontSize =: percent 60
, pFontWeight =: vBold
]
main :: IO ()
main = runCgi (tell page)
Ejercicios
Para esta práctica, como mínimo debes entregar los apartados 1 a 8. A la hora de corregir la práctica el profesor
valorará, además del comportamiento del programa, la calidad y claridad del código de tu programa. En concreto
se considerará la descomposicón del programa en funciones de modo adecuado, la reutilización de código y el uso de características
de Haskell como las funciones de orden superior (
map ,
filter ,
sequence ,
sequence_ ,
mapM ,
...), las listas por compresión, etc..
El objetivo de este
ejercicio es programar un Cgi para realizar consultas
sobre la base de datos de una tienda de informática. Para ello el
Cgi deberá mostrar al usuario un formulario como el siguiente para interrogar
la base de datos:
y deberá mostrar los resultados de la consulta utilizando una tabla:
Tienes una versión compilada del ejercicio en el fichero "buscador.exe" de los
ejemplos de la biblioteca para que te hagas una idea del funcionamiento deseado.
Existirá una base de datos de categorías de productos y otra de productos. En la primera
aparecerán las distintas categorías de productos (Monitores, Disco duros, etc ...). Cada categoría
tendrá un código único asociado (un número natural). Para ello el programa usará las siguientes definiciones de tipo:
-- Categorías de productos
type CodCat = Int -- código
type DescCat = String -- descripción de categoría
-- Una categoría
data Cat = C CodCat DescCat deriving (Show,Read)
-- La base de datos de Categorías
type BDCat = [ Cat ]
Existirá una categoría especial denominada "Todos" cuyo código será 0 y que tendrá un significado especial.
En la base de datos de productos se almacenarán los distintos productos, cada uno con el código de la categoría
a la que pertenece, su descripción y su precio de venta (en euros). Para ello usaremos los siguientes tipos:
type DescProd = String -- descripción del producto
type PvpProd = Float -- precio del producto
-- Un producto
data Prod = P CodCat DescProd PvpProd deriving (Show,Read)
-- La base de datos de productos
type BDProd = [ Prod ]
Cada base de datos residirá en un fichero de texto en el servidor, de modo que contendrá una categoría (o un artículo) por
línea de texto. Desde [aquí] puedes descargar un fichero ejemplo para las categorías
y desde [aquí] otro con los productos.
-
Comienza definiendo una función leerBDCat :: FilePath -> IO BDCat que, dado un nombre
de fichero que contiene una base da datos de categorías, lea este fichero y devuelva la lista de categorías correspondiente. Define
también una función leerBDProd :: FilePath -> IO BDProd para leer una base de datos de productos. Recuerda que puedes pasar
de una cadena a un valor de tipo Cat o Prod usando la función read y que puedes obtener las distintas
líneas de un fichero usando lines
:: String -> [String] .
-
Define una función perteneceA tal que xs `perteneceA` ys devuelva True si
la cadena de caracteres xs aparece en la cadena ys . Por ejemplo,
"Hitachi" `perteneceA` "CRT 17 Hitachi 615" =>> True
-
Define una función tablaProd :: BDProd -> BDCat -> Html () que, dadas una base de
datos de productos y otra de categorías, devuelva una tabla HTML con tantas filas como productos tenga la
primera base de datos. Cada fila de la tabla contendrá la descripción de la
categoría del producto (cuidado, NO su código), la descripción del producto y su precio. La tabla debe también
incluir una primera fila que haga de cabecera. En definitiva, la tabla debe ser similar a la que
aparece en la figura de la imagen anterior.
Puedes consultar el fichero Tabla.hs en los
ejemplos de la librería para ver como se pueden crear tablas HTML.
-
Define una función buscarProd :: String -> BDProd -> BDProd que, dadas una palabra
y una base de datos de productos, devuelva una nueva base de datos en la que solo aparezcan los
productos que contengan la palabra en su descripción.
-
Define un formulario que contenga una caja de texto (un elemento hTextInput ) y un botón
(un elemento hButtonInput ), de modo que, al pulsar el botón, se devuelva una página HTML con
una tabla que
contenga todos los elementos de la base de datos de productos que contienen la palabra introducida como
parte de su descripción. Si no hay ningún resultado en la búsqueda se deberá mostrar el correspondiente
mensaje. Ten en cuenta que el modo mayúscula/minúscula del texto introducido no tiene que coincidir
con la descripción del producto, es decir, si buscas "amd" y la descripión de un producto
contiene la palabra "AMD", dicho producto debe formar parte del resultado
de la búsqueda.
Puedes consultar el fichero Controles.hs en los
ejemplos de la librería para ver como se usan los distintos tipos de controles que pueden aparecer en un formulario.
Para poder usar la función leerBDProd :: FilePath -> IO BDProd dentro de un do con tipo
Html puedes usar la función unsafeIO :: IO a -> Html a , de modo que
unsafeIO $ leerBDProd prodPath tiene tipo Html BDProd .
-
Mejora tu programa para que el formulario muestre además una lista
de selección (un elemento hSelectInput ) con todas las categorías
de la base de datos de categorías. Si se selecciona una categoría distinta
a "Todos", la búsqueda debe restringirse a los artículos que pertencen
a la categoría seleccionada. Las distintas opciones de la lista de categorías
deben obtenerse del fichero que almacena la base de datos de categorías, de modo que
no sea necesario modificar el programa si se cambia el contenido del fichero base de datos.
-
Mejora tu programa para que el formulario permita introducir más de una palabra en la
caja de texto. Además, deberá mostrarse una nueva lista de selección que permita que la
búsqueda devuelva solo los artículos que contienen todas las palabras introducidas en su
descripción o tan solo alguna de las palabras. Para ello, utiliza la siguiente definición de tipo
en tu programa:
data Condición = Alguna | Todas deriving (Eq, Show, Read, Enum)
instance CgiInput Condición
Puedes descomponer la cadena de entradas en las palabras que la forman usando la función predefinida
words
:: String -> [String] . Se valorará positivamente el uso de una función de orden superior y las funciones predefinidas
all y
any para resolver
este apartado.
-
Mejora tu programa para que aparezca una nueva lista de selección en el formulario
que permita que la tabla con el resultado de la búsqueda esté ordenada por descripción o precio. Para ello, utiliza la siguiente definición de tipo
en tu programa:
data Orden = Descripción | Precio deriving (Eq, Show, Read, Enum)
instance CgiInput Orden
Se valorará positivamente el uso de la función de orden superior
sortBy :: (a -> a -> Ordering) -> [a] -> [a] de
la librería List para resolver este apartado. Esta función toma como parámetro una función de comparación para los
elementos de
la lista que indica como ha de ordenarse. La función de comparación
compare :: (Ord a) => a -> a -> Ordering está
predefinida para los tipos simples.
-
Opcionalmente puedes añadir al programa alguna mejora que se te ocurra. Una interesante es devolver
la tabla de resultados poco a poco (en varias páginas), limitando el número de resultados de cada página tal como hacen los buscadores de Internet. Cada página
debería incluir un botón para ver los siguientes resultados de la búsqueda.
Otra ampliación sería
añadir al formulario un nuevo botón que nos lleve a otro formulario que proporcione una interfaz
de usuario para añadir o eliminar registros
de los ficheros de base de datos, es decir que permitan mantener las bases de datos en el servidor desde un cliente. Protege la entrada
en esta parte de la aplicación pidiendo una contraseña. Puedes usar el elemento hPasswordInput (ver ejemplo
en Controles.hs ).
También es interesante que el buscador cree un fichero log en el que queden
almacenadas todas las consultas que se realizan (solo el texto a buscar y las condiciones, junto
con la fecha y hora de la consulta). Usa para ello la librería
Time de Haskell y la función
unsafeIO . Puedes incluso obtener la dirección IP del cliente que realiza la
consulta, para almacenarla en el log, unsando unsafeIO $ getEnv remoteHost , donde
getEnv
es una función definida en la librería
System
|