martes, 10 de abril de 2007

Descargar XLS y PDF sin abrirlos en el navegador

Más de uno ha querido solucionar este problema: un link en un archivo para descargar un XLS o PDF (o DOC) sin que se abra en el navegador. Pues aquí tengo la solución (con PHP)

Primero, los enlaces deberían como estos:

<a href="download.php?link=Plantilla.xlt">XLT</a>
<a href="download.php?link=Libro.pdf">PDF</a>

Y el archivo download.php es el siguiente:

<?php $doc=$_GET["link"];
header('Content-Type:application/octet-stream');
header('Content-Disposition:attachment; filename="'.$doc.'"');
readfile($doc);
?>

Realm con ActiveDirectory

Después de revisar varios ejemplos, probar y probar, logré encontrar una configuración para usar Realm con el ActiveDirectory de Windows. Esta es la configuración que usé:
<Context path="/ldap" >
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://med_spdom01" debug="99"
userPattern="{0}@meduca.gob.pe"
roleBase="OU=Politicas,DC=meduca,DC=gob,DC=pe"
roleName="cn"/>
</Context>

Como se ve en userPattern, le estoy poniendo el dominio del usuario.
Primero había probado logearme con ese formato en la ventana de inicio de sesión del windows. Al momento de escribir el árroba (@) en el nombre de usuario, la lista de dominios se me desactiva. Ya no hacía falta especificar el dominio. Con esa premisa fue que intenté utilizar el mismo formato para el Realm en el Tomcat

Anular caché de los navegadores

Los navegadores guardan todos los archivos que se han accedido. En internet explorer se llama "archivos temporales de internet"... lo cuál se refiere al caché de internet. (tanto nombre!)

Pero si estamos en una página que requiere autenticarse, y al deslogear regresa a la página autenticada, pues el navegador mostrará la página por más que el usuario haya cerrado la sesión. Lo mejor es que cada página que el navegador no se guarde en el caché.

Para ello colocaremos las siguientes sentencias (ya sea en jsp o en un servlet):

response.setHeader("Cache-control","no-cache");
response.setHeader("Pragma","no-cache");
response.setDateHeader ("Expires", 0);


Y Listo

Generar XLS

Para generar XLS generalmente se usa el Jakarta POI (en java), pero para web es mejor engañar al navegador.

El navegador recibe como cabecera el tipo del archivo (mime-type) que está recibiendo y sabrá qué programa abrir. Si es de tipo text/html, abrirá el mismo navegador, pero si es un video tendrá otro tipo y le pedirá al sistema operativo abrir el reproductor de vídeo correspondiente.

Para el caso de XLS es lo mismo, y como el Excel puede abrir hasta html, entonces lo engañaremos con más facilidad:

Al inicio del JSP colocaremos esta línea:

<%@page contentType="application/vnd.ms-excel"%>


Con esto, cuando se acceda al .jsp abrirá el contenido con el excel. Pero si se estuviese usando el Internet Explorer, el xls saldrá incrustado en el navegador. Esto puede ser molestoso.

Lo que podemos hacer es que se le pregunte al navegador si desea abrirlo o descargarlo. Colocaremos las siguientes líneas:

<%@page contentType="application/vnd.ms-excel"%>
<%response.setHeader("Content-Disposition",
"attachment; filename=\"Archivo.xls\""); %>


Luego, en el contenido del jsp pondremos tablas y eso se mostrará en el excel.

Configuración de Tomcat (conectando al Apache utilizando AJP)

El Tomcat utiliza - por omisión - el puerto 8080 para mostrar los contenidos web. Se puede cambiar el puerto de salida en el archivo $CATALINA_HOME/conf/server.xml de 8080 a 80, para que el acceso sea más simple y evitar escribir un "apellido" como http://midominio:8080/sistemaweb/.

Pero quizás en nuestro host también estemos utilizando el apache para publicar otras aplicaciones que no son necesariamente jsp/servlet.

Es posible hacer que todas las peticiones que reciba el apache a un determinado "directorio" sea redireccionado al tomcat, para que este lo atienda y envie su respueta al cliente através del Apache.

Hay dos maneras para lograr esto: con el ProxyHTTP y con el conector AJP. En este mensaje mostraré cómo se utiliza el AJP.

En este diagrama se muestra como funciona el AJP. Por omisión utiliza el puerto 8009 localmente.

Paso 0: descargar el conector:
El conector se puede encontrar en el sitio web de Tomcat. Debemos escoger el jk1.2( el jk2 es obsoleto). Si el Apache está en Window$ , entraremos al enlace de los binarios compilados y utilizaremos la versión que le corresponda. Tenemos que leer bien las instrucciones que se muestran antes de descargar el archivo.

Paso 1: instalando el módulo jk:
Si estamos en linux necesitaremos el Apache apxs. Si su Apache fue instalado con RPM, necesitará instalar el paquete httpd-devel.
Luego bajaremos el código fuente del conector, lo desempaquetamos, compilamos e instalamos:

$su -
#tar xvzf jakarta-tomcat-connectors-1.2.x-src.tar.gz
#cd jakarta-tomcat-connectors-1.2.x/jk/native
#./configure --with-apxs=`which apxs`
#make
#make install


Esto hará que se cree el módulo mod_jk.so y lo copiará en el directorio respectivo del Apache.


Paso 2: Configurando el apache:
Editaremos el archivo de configuración del apache httpd.conf y agregaremos las siguientes líneas:

# El archivo workers que indica donde estarán los módulos webs del tomcat
JkWorkersFile "/etc/httpd/conf/workers.properties"
# La ruta de un log de actividad
JkLogFile "/etc/httpd/logs/mod_jk.log"
# Qué va a mostrar en el log
JkLogLevel info
#el formato del log
JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
#El módulo web que deberá cargar
JkMount /sistemaweb/* wrk1


Ahora, crearemos el archivo workers.properties que debe ubicarse en la misma ruta que se especificó en el archivo httpd.conf. Este archivo tendrá lo siguiente:

worker.list=wrk1
worker.
wrk1.port=8009
worker.
wrk1.host=localhost
worker.
wrk1.type=ajp13
worker.
wrk1.cachesize=10
worker.
wrk1.cache_timeout=600
worker.
wrk1.socket_timeout=300

La primera línea es importante, ya que dice cuales son los workers que se van a manejar. Un worker está asociado a un host en particular utilizando un puerto. En este caso buscará un tomcat en el localhost, y utilizará el puerto 8009 para conectarse a su AJP.

Paso 3: configuración el Tomcat:
En este paso no se hace nada, ya que por omisión el tomcat viene activado el AJP en el puerto 8009.

Probaremos llamando a una dirección similiar:
http://midominio.com/sistemaweb/

Nota: aun no sé por qué se tiene que terminar con un signo "/", si se le quita no lo encuentra.


Apache - Tomcat 1:N
En un ambiente óptimo (lo que implica tener más $$), es posible tener un host con el Apache (que podría ser el portal de una organización) y otros hosts por cada oficina. Se puede deducir que en el host del portal de la organización no tiene por qué estar instalado el Tomcat. La figura sería algo como esta:


Supongamos que el área de ventas tenga un módulo web llamado "wventas" en el host con ip 10.1.1.10, y el área de "atención al cliente" tenga un módulo web llamado "wclientes" en el host con ip 10.1.1.11.

Primero debemos editar el archivo workers.properties para que tenga el siguiente contenido:

worker.list=wrk10,wrk11
worker
.wrk10.port=8009
worker.
wrk10.host=10.1.1.10
worker.
wrk10.type=ajp13
worker.
wrk10.cachesize=10
worker.
wrk10.cache_timeout=600
worker.
wrk10.socket_timeout=300

worker.wrk11.port=8009
worker.
wrk11.host=10.1.1.11
worker.
wrk11.type=ajp13
worker.
wrk11.cachesize=10
worker.
wrk11.cache_timeout=600
worker.
wrk11.socket_timeout=300

(Coloqué el nombre de los workers wrk10 y wrk11 haciendo referencia a los ips de los hosts 10.1.1.10, y 10.1.1.11 respectivamente).

Luego necesitamos editar el archivo httpd.conf para que tenga estas líneas

JkMount /wventas/* wrk10
JkMount /wclientes/* wrk11

Reiniciamos el servicio del apache y podremos entrar a las direcciones:
http://midominio.com/wventas/
http://midominio.com/wclientes/

Nota: Los nombres son tal cual los nombres de los módulos web en el Tomcat.

Configuración de Tomcat (módulos web)

"Normalmente", cuando un newbie en desarrollo hace su aplicación web y lo quiere publicar, suele colocarlo junto con los documentos del servidor web. Por ejemplo, si se tratase de PHP en Apache, lo coloca dentro de DocumentRoot; y si es Tomcat, en $CATALINA_HOME/webapps. Esto es normal, pero es algo desordenado tener la aplicación dentro del sistema que lo ejecuta. (Recuerdo que los perfiles de usuario en Windows NT estaban dentro del directorio Windows, si se instalaba nuevamente el sistema operativo, todos los perfiles se borraba. A partir del Windows 2K, los perfiles se guardan en un directorio a parte llamado "c:\Document and Settings").

Esto se puede hacer en Tomcat. Si deseamos cambiar de contenedor web (a una versión superior, o cambiar de propietario), los módulos web seguirán en el mismo sitio.

  • Lo principal es tener todos los módulos web en un directorio aparte, que lo podamos identificar tan fácilmente como el "Document and Settings" del Windows.
  • Crear un archivo .xml en el directorio $CATALINA_HOME/conf/[enginename]/[hostname]/ (generalmente enginename=Catalina y hostname=localhost, se puede crear otros enginename y otros hostname editando el archivo server.xml)
  • Este archivo .xml tiene un solo elemento llamado <Context />, el cual tiene los siguientes atributos.







AtributoDescripción
docBase
Directorio del módulo web. También conocido como ContextRoot. Es la ruta absoluta o relativa donde se ubica fisicamente los archivos del módulo web.
path
Nombre con que se publicará en el contenedor web. Por ejemplo: http://yourdomain.com:8080/moduloweb. En mi experiencia, el valor de este atributo debe coincidir con el nombre del archivo .xml
reloadable
Si se pone en true, Catalina estará revisando a cada momento los directorios WEB-INF/classes y WEB-INF/lib. Si encuentra un nuevo archivo, se recargará el contexto. Esto es recomendable para la etapa de desarrollo.

Esta sería la configuración principal básica para separar el módulo web del contenedor web.
La relación completa de los atributos para <context /> se encuentra en http://tomcat.apache.org/tomcat-5.5-doc/config/context.html.

Tiles en Struts 1.3

Comencé a migrar una aplicación hecha con Struts 1.2 para que utilizará Struts 1.3
Simplemente (pensé) sería copiar el contenido del struts-config.xls (manteniendo el DTD), obviamente usar los .jar respectivos, utilizar el mismo tiles-defs.xml, pero... no funcionó. Respondía el error 404 (no encuentra página).

Después de un día perdido,revisé la documentación

http://struts.apache.org/1.3.8/struts-tiles/installation.html

y pues la solución era sencilla (algo nuevo con respecto al struts 1.2):
Agregar el siguiente parámetro de inicialización del Action Servlet.
<init-param>
<param-name>chainConfig</param-name>
<param-value>org/apache/struts/tiles/chain-config.xml</param-value>
</init-param>

hora, todo funciona como debería ser.
... no olvidar, siempre leer la documentación.