Programación web en Prolog
Como ya hemos visto con anterioridad, Prolog es un muy buen lenguaje para ciertos problemas. Hoy en día, el lenguaje que se creía que sería el futuro está prácticamente olvidado. No obstante, existen algunas personas que se han esforzado para que Prolog siga siendo un lenguaje útil. ¿Y qué requisito es básico para un lenguaje útil? ¡Que pueda usarse para hacer un servidor web claro! Es por eso que decidí emprender una aventura quijotesca y ver hasta donde era capaz de llegar Prolog en el desarrollo web. Para ello voy a usar SWI Prolog.¿Suena alocado? Pues ya lo estás usando, el servidor de gestión de imágenes de Adrianistán (https://files.adrianistan.eu) está hecho en Prolog.
Handlers y configuración
Como todo buen web framework, en este debe poder configurarse unas rutas (handlers) y algunos parámetros tales como el puerto, número de hilos (Prolog por defecto usará varios hilos para manejar las peticiones web en paralelo) y más cosas. Este código es el más simple que nos permite acceder a una página y mostrar un hola mundo.
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/html_write)).
:- use_module(library(http/http_unix_daemon)).
:- http_handler(/,index,[]).
index(_Request) :-
format('Content-Type: text/plain~n~n'),
format('Hola Mundo').
:- http_daemon([port(4777),fork(false)]).
Si ejecutamos con SWI Prolog, veremos que el servidor arranca:
Como vemos, el código es super simple. Centrémonos más en la parte de los handlers. Es tan simple que parece que faltan cosas, ¿cómo definir rutas con parámetros? ¿y la distinción entre GET y POST? Todo es muy sencillo como veremos.
El predicado http_handler tiene aridad 3. En primer lugar la ruta, en segundo lugar el predicado que responderá a la petición y en tercer lugar una lista con las opciones.
En primer lugar las rutas se definen con algo denominado FileSpec, que es una construcción de Prolog que permite expresar rutas relativas a partir de un alias absoluto. No solo se usa aquí, sino en todas partes. Fíjate en como usamos use_module. Tenemos library y dentro una ruta. library representa un path absoluto con ese alias y dentro se representa el resto de la ruta. Otro alias predefinido y que podemos usar en el contexto de una aplicación web es root. root(.) es equivalente a /. Si queremos soportar paths variables, ¡usamos variables de Prolog! Luego nos aseguramos que que esas variables también esten presentes en el predicado de la respuesta.
Aquí alguien se puede extrañar ya que este predicado no tiene la misma aridad arriba que abajo, aparentemente. En realidad como http_handler siempre añade la variable Request, Prolog no tiene ningún problema.
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/html_write)).
:- use_module(library(http/http_unix_daemon)).
:- http_handler(/,index,[]).
:- http_handler(root(user/User),user(User),[method(get)]).
index(_Request) :-
format('Content-Type: text/plain~n~n'),
format('Hola Mundo').
user(User,_Request) :-
format('Content-Type: text/plain~n~n'),
format('Usuario: '),
format(User).
:- http_daemon([port(4777),fork(false)]).
Por otro lado en las opciones podemos definir gran variedad de ajustes, entre ellos el método de la petición. Como bien es sabido en Prolog, Prolog unifica, ¡incluso en una aplicación web! Así que la clave es cuanto más explícitos seamos, en menos ocasiones unificará el handler.
Ficheros, HTML y JSON
Ya hemos visto como funcionan los handlers pero hemos estado generando las respuestas con unas llamadas a format un poco cutres. Por fortuna, Prolog nos hace la vida más fácil si vamos a devolver un fichero, un HTML dinámico o un archivo JSON.
Para ficheros, es trivial, simplemente respondemos con el predicado http_reply_file.
Para HTML hay dos maneras de hacerlo. Una es usando el DSL, otra es usando plantillas. Aquí vamos a ver el DSL ya que es más estilo Prolog. Para responder usando este método usamos el predicado reply_html_page. Este toma un head y un body. Este DSL es muy intuitivo y permite escribir estructuras anidadas así como atributos. Este DSL además permite ser usado con DCG, así que se puede escribir en un predicado aparte y se pueden hacer includes de forma cómoda. Además se pueden pasar variables, aunque en este ejemplo no lo hago.
Por último, para responder un JSON usamos el predicado reply_json. SWI Prolog admite varias sintaxis para representar JSON. Podemos usar dentro un término json la sintaxis Clave-Valor, Clave=Valor o Clave(Valor). Esta última es la que he usado. Además a partir de SWI Prolog 7 se pueden usar los diccionarios de Prolog también.
Finalmente aquí un ejemplo con los tres predicados en uso:
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/html_write)).
:- use_module(library(http/http_json)).
:- use_module(library(http/http_unix_daemon)).
:- http_handler(/,index,[]).
:- http_handler(root(user/User),user(User),[method(get)]).
:- http_handler('/foto.jpg',static,[]).
:- http_handler('/api',api,[]).
index(_Request) :-
reply_html_page(
title('Prolog WebApp'),
[\index_body]).
index_body -->
html([
h1('Prolog WebApp'),
p([style('color: red'),id('parrafo')],'Hola Mundo'),
ul([
li('Alonso Quijana'),
li('Sancho Panza')
])
]).
api(_Request) :-
reply_json(json([
name('Alonso Quijana'),
email('quijana@mail.xp')
])).
user(User,_Request) :-
format('Content-Type: text/plain~n~n'),
format('Usuario: '),
format(User).
static(Request) :-
http_reply_file('burgos.jpeg',[],Request).
:- http_daemon([port(4777),fork(false)]).
Peticiones POST
Vamos a ver como leer esos formularios desde Prolog. En primer lugar para obtener un listado de todas las variables POST usamos el predicado http_read_data. Después simplemente buscamos en la lista con el archiconocido predicado member, unificando con una variable libre para obtener ya el dato directamente.
form(Request) :-
http_read_data(Request,Data,[]),
member(email=Email,Data),
format('Content-Type: text/plain~n~n'),
format('El correo indicado es: '),
format(Email).
Autenticación Basic
Un ejemplo más elaborado es la autenticación HTTP. Aquí defino un predicado auth que va a contener la validación.
auth(Request) :-
Realm = 'Prolog WebApp',
(
string_codes("123456789",Password),
member(authorization(Header),Request),http_authorization_data(Header,basic(admin,Password)) -> true
;
throw(http_reply(authorise(basic, Realm)))
).
Para usarla, simplemente hay que poner el predicado auth en la primera línea de cualquier predicado de handler.
Conclusiones
El código final está en el repositorio de ejemplos del blog. Respecto a la base de datos. No lo he comentado porque sería otro asunto, además Prolog ya es una base de datos gracias a assert y retract. SWI Prolog incluye además una muy buena implementación de RDF. Espero que con este post os haya abierto la mente a un tipo de programación diferente, la lógica, que no solo sirve para resolver problemas "raros" sino para aplicaciones web de forma extremadamente sencilla (¡una vez comprendamos Prolog claro!). Se trata de un enfoque muy interesante y con el que se puede llegar a ser muy productivo. Yo me esperaba algo mucho peor, pero la gente detrás de SWI Prolog se ha esforzado para que todo sea simple y claro.