5. Filtros

Hasta ahora hemos visto la forma en la que los servlets nos permiten encapsular el mecanismo de petición/respuesta. Se identifica al servlet como un recurso dentro del sitio web, y cuando desde el cliente solicitamos dicho recurso, se ejecutará el código que hayamos definido dentro del método de servicio del servlet.

La limitación de los servlets es justamente esa, que un servlet se invocará sólo cuando solicitemos dicho servlet desde el cliente. Pero, ¿y si queremos procesar cualquier petición que se haga a cierta parte o toda nuestra aplicación web?

Si sólo contamos con servlets, para solucionar esto podríamos optar por alguna de las siguientes opciones por ejemplo:

Como vemos, por ahora este problema no tiene ninguna solución totalmente satisfactoria. Para ello, a partir de la versión 2.3 de servlets, aparecen los denominados filtros.

5.1 ¿Qué es un filtro?

Un filtro es un componente que intercepta cualquier petición que se realice a un determinado grupo de recursos de nuestra aplicación web, y la respuesta que se vaya a devolver al cliente por parte del servidor.

Normalmente los filtros no generarán por si mismos la respuesta, como es el caso de los servlets, sino que simplemente la modificarán si es necesario. Podrán modificar tanto la petición HTTP, como la respuesta o las cabeceras de la misma.

Una ventaja importante de los filtros es que nos ayudarán a modularizar la aplicación, ya que son componentes independientes que actuarán sobre cualquier grupo de recursos, no teniendo dichos recursos porque conocer la existencia de estos filtros. De esta forma este filtrado de las peticiones y respuestas a nuestro servidor se realiza de un forma totalmente transparente en todos los niveles, tanto para el cliente como para los desarrolladores del contenido del sitio web (servlets, JSPs, páginas estática, y cualquier otro recurso).

Esta independencia implica por lo tanto que los filtros podrán ser reutilizados para cualquier elemento del sitio web, sin necesidad de incluir código común en todos los elementos que queramos que realicen dicha funcionalidad.

5.2 Funcionalidades de los filtros

Un filtro podrá acceder a la petición de un determinado recurso antes de que dicho recurso sea invocado, momento en el que podremos procesar o modificar dicha petición.

Una vez se ha invocado la petición, podremos procesar o modificar la respuesta que nos ha devuelto el servidor.

Además, podremos tener múltiples filtros actuando sobre determinados grupos de recursos. De esta forma un recurso podrá no ser filtrado, o ser filtrado por uno o más filtros. Cuando tenemos varios filtros, se organizarán en forma de cadena en el orden que nosotros especifiquemos, y cada uno procesará el resultado del anterior.

5.3 Aplicaciones de los filtros

Hemos descrito lo que es un filtro, pero entenderemos más claramente los filtros si vemos una serie de posibles aplicaciones que les podemos dar:

5.4 Configuración de un filtro

Para que un filtro intercepte las peticiones a determinados recursos, deberemos configurar la aplicación web para que esto sea así. La forma de configurar los filtros es similar a la configuración de los servlets.

Los filtros, al igual que los servlets, serán clases Java que definamos, y que tendremos normalmente en el directorio WEB-INF/classes de nuestra aplicación web, o subdirectorios de este si está en algún subpaquete. La configuración de los filtros deberá establecerse en el fichero de configuración de nuestra aplicación web, WEB-INF/web.xml.

Es importante recordar que en este fichero de configuración, por ser un lenguaje definido mediante un DTD en XML, se debe respetar el orden en el que aparecen los distintos elementos. Los elementos para la configuración de filtros deben ir tras los elementos context-param, y antes de listener y servlet.

Primero deberemos declarar los filtros incluidos en nuestra aplicación web. Para ello deberemos utilizar el elemento filter que se define de la siguiente forma en el DTD:

<!ELEMENT filter (icon?, filter-name, display-name?, 
description?, filter-class, init-param*)>

Un ejemplo de uso de este elemento en el fichero de configuración web.xml es el siguiente:

<filter>
<filter-name>Filtro de ejemplo</filter-name>
<filter-class>FiltroEjemplo</filter-class>
<init-param>
<param-name>fichero_log</param-name>
<param-value>log.txt</param-name>
</init-param>
</filter>

Es muy similar a la forma de declarar un servlet. Asignamos un nombre al filtro, que será asociado a la clase en la que está implementado dicho filtro. En este caso la clase es FiltroEjemplo, por lo que tendremos que tener el fichero FiltroEjemplo.class en el directorio WEB-INF/classes de nuestra aplicación.

A continuación podemos declarar una serie de parámetros de entrada para el filtro, de forma que para variar estos datos no tengamos que modificar y recompilar la clase del filtro, sino que simplemente deberemos modificar el valor del parámetro en este fichero de configuración. Podremos no tener ningún parámetro, tener uno, o tantos como queramos.

Una vez declarados los filtros deberemos mapearlos a los recursos. Las peticiones que se hagan al servidor a estos recursos, serán interceptadas por nuestro filtro. Podemos mapear filtros a recursos de distintas formas, con la etiqueta filter-mapping que se define de la siguiente forma en el DTD:

<!ELEMENT filter-mapping (filter-name, (url-pattern | servletname))>

Ejemplos de utilización de las dos formas posibles de mapeado son los siguientes:

<filter-mapping>
<filter-name>Filtro de ejemplo</filter-name>
<servlet-name>Servlet interceptado</servlet-name>
</filter-mapping>

<filter-mapping>
<filter-name>Filtro de ejemplo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

La primera forma nos sirve para mapearlo a un servlet, dado el nombre del servlet al que lo vamos a asociar. La segunda forma asocia el filtro a todos los elementos cuya URL cumpla el patrón dado:

/*                  Se asocia con todos los elementos de nuestra aplicación web.
/zona_restringida/* Se asocia con todos los elementos en el directorio de nombre
zona_restringida, y con los de sus subdirectorios.
/web/* Se asocia con todos los elementos en el directorio de nombre
web, y con los de sus subdirectorios.
...

Podemos asociar varios filtros a un mismo recurso, si dicho recurso aparece mapeado para varios filtros. En este caso tendremos una cadena de varios filtros cuando se produzca una petición a este recurso.


Figura 1. Encadenamiento de filtros

5.5 Implementación básica de un filtro

Los filtros se definen mediante la interfaz Filter, contenida en el paquete javax.servlet. Por lo tanto, para crear un filtro deberemos crear una clase que implemente dicha interfaz:

import javax.servlet.*;
import javax.servlet.http.*;


class MiFiltro implements Filter {
FilterConfig config;

Dentro de este clase, el método básico que deberemos implementar será el método doFilter, al que se llamará cada vez que dicho filtro intercepte una petición a recursos:

	public void doFilter(ServletRequest request, 
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
...

Vemos que a este método se le pasa como parámetro la petición y la respuesta, de forma que podamos procesarlas o modificarlas según la funcionalidad que queramos que implemente el filtro. Hemos de fijarnos que toma una petición y respuesta genérica, no se limita únicamente a peticiones y respuestas HTTP.

Además también se nos proporciona un objeto que representa la cadena de filtros. Con él podremos pasar la petición y la respuesta interceptadas al siguiente filtro de la cadena, o bien al recurso destino en caso de que ya no hubiese más filtros. Esto lo haremos con una llamada a:

		...

chain.doFilter(request, response);
... // En este punto el servidor ya habrá producido
//la respuesta en response

}

Justo después de haber llamado a este método, ya se habrá producido la respuesta, ya que con él estamos indicando que se ejecuten todos los filtros que siguen al nuestro en la cadena, y en último lugar el recurso solicitado.

Por lo tanto, todas las modificaciones que queramos hacer en la petición que va a llegar al recurso las deberemos hacer antes de la llamada a este método, mientras que todo procesamiento que queramos hacer de la respuesta se hará después de esta llamada, que será cuando se haya generado.

También podemos hacer que no se llegue a llamar, si queremos que nuestro filtro de la respuesta por si solo, sin acceder al recurso que se había pedido. Esto lo haremos por ejemplo cuando queramos prohibir el acceso a un recurso.

Otros métodos que debemos definir en un filtro son:

	public void init(FilterConfig config) throws ServletException {
// Código de inicialización del filtro
this.config = config;
...

}

public void destroy() {
// Libera recursos del filtro
config = null;
...
}

...

}
Que serán llamados en la inicialización y en la destrucción de este componente respectivamente.

5.6 Acceso al contexto

Acabamos de ver que cuando se inicializa el filtro se llama a su método init. En esta llamada se proporciona un objeto FilterConfig que contiene información sobre los parámetros del filtro, que vimos en el apartado de configuración, y además nos permite acceder a la información global de contexto.

Para leer los parámetros del filtro especificados en el descriptor de despliegue de la aplicación web (fichero web.xml en Tomcat como hemos visto), este objeto proporciona el siguiente método:

String valor = config.getInitParameter(nombre_param);

Esta llamada nos devolverá una cadena con el valor del parámetro, o null en el caso de que el parámetro indicado no existiese. Si queremos obtener la lista de parámetros definidos en el descriptor de despliegue, podemos usar el siguiente método:

Enumeration parametros = config.getInitParameterNames();

Con esto obtendremos una enumeración de todos los nombres de parámetros definidos.

Este objeto también nos permite obtener el nombre del filtro, que se habrá definido en el descriptor de despliegue, con el método:

String nombre = config.getFilterName();

Este objeto además nos permitirá acceder al objeto de contexto global del contenedor de servlets, mediante el método:

ServletContext context = config.getServletContext();

Obtenemos este objeto con el cual podremos acceder a los atributos globales definidos en nuestra aplicación web, y además nos proporciona una serie de métodos que nos permitirán realizar en filtros las mismas operaciones que podíamos hacer en los servlets.

Será importante acceder a este objeto desde los filtros, ya que si queremos realizar redirecciones, o acceso a recursos estáticos por ejemplo, necesitaremos contar con dicho objeto.

5.7 Ciclo de vida de un filtro

Justo después del despliegue de la aplicación web, y antes de que se produzca cualquier petición a un recurso, el contenedor localizará los filtros que deben ser aplicados a cada recurso. Instanciará los filtros que hayamos declarado, y tras ello llamará al método init de cada filtro para inicializarlo.

Si hacemos que este método init lance una excepción UnavailableExeption estaremos indicando que el filtro no puede funcionar correctamente. Esta excepción tiene un método isPermament que indicará si el fallo es permanente o puede recuperarse pasado un tiempo. De no ser permanente el contenedor intentará volver a instanciar el filtro más adelante. Podemos establecer en la excepción un tiempo estimado que puede tardar en estar disponible, para informar al contenedor de cuando puede volver a intentar instanciarlo.

Al método init se le proporcionará el objeto FilterConfig, con la información de los parámetros y nombre del filtro obtenidos del descriptor de despliegue, además de una referencia al objeto ServletContext de la aplicación web, como hemos visto en el apartado anterior.

Una vez terminada la fase de inicialización, el servidor ya podrá empezar a recibir peticiones. Cuando se produzca una petición, el contenedor localizará el primer filtro asociado a dicho recurso, y llamará a su método doFilter proporcionando los objetos ServletRequest, ServletResponse, y FilterChain. Una vez hecho esto será responsabilidad de nuestro filtro tratar estos objetos, y decidir si pasar el procesamiento al siguiente filtro de la cadena.

Cuando lleguemos al ultimo filtro de la cadena, al llamar a doChain se invocará directamente el recurso que se solicitaba en la petición.

Si durante doFilter lanzamos una excepción UnavailableException, el contenedor no intentará seguir procesando la cadena de filtros. Si hemos indicado que es no permanente, tras un rato reintentará procesar la cadena entera.

Antes de poder hacer que el filtro deje de estar en servicio, llamará a su método destroy para que libere los recursos que sea necesario.