Menú

   

Actualizado

 

Ampliación de Programación - Haskell

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.

  1. 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].

  2. 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

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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