Llamadas AJAX en subdominio diferente (CrossDomain)

Hace no mucho me encontré con la necesidad de montar una API JSON-RPC que sería llamado desde una página web en un subdominio diferente al del API:

  • Página web: www.patatin.com
  • API JSON-RPC: api.patatin.com

Cómo la mayoría ya sabréis, los navegadores limitan las llamadas AJAX al mismo subdominio de la página web que las realiza, no siendo posible realizar llamadas AJAX fuera de este subdominio. Esto se hace por motivos de seguridad. Forma parte de lo que se conoce como same-origin policy.

Con el advenimiento de la nueva Web basada en APIs, es cada vez mas frecuente encontrarse con escenarios en los que la same-origin policy resulta demasiado restrictiva. Es por ello que desde hace algún tiempo vienen utilizándose diferentes técnicas para superar las limitaciones impuestas por la same-origin policy.

Para el caso que nos ocupa, que implica realizar llamadas AJAX desde un subdominio distinto, existen varias técnicas posibles, como JSONP, pero yo me centraré en la que creo que tiene mas futuro, ya que viene impulsada por el W3C : CORS (Cross-origin resource sharing)

CORS consiste en una especificación estandarizada que añade nuevas cabeceras al protocolo HTTP. Estas cabeceras permiten al servidor ofrecer sus recursos a los subdominios de origen permitidos. Los navegadores deben soportar estas nuevas cabeceras y respetar las retricciones establecidas. Para evitar algunos efectos colaterales de utilizar diferentes métodos en las peticiones, el navegador debe comprobar cada petición utilizando una llamada con el método HTTP OPTIONS, y una vez obtenida la aprobación del servidor, enviar la llamada con el método necesario. Así mismo, los servidores también pueden notificar cuando es necesario presentar credenciales para el envío de una petición.

En nuestro caso concreto, queremos utilizar un servicio JSON-RPC ya existente, así que deberemos de parchear en el lado del servidor, asegurándonos de que se envían las cabeceras adecuadas. En PHP:

$referer_parts=parse_url($_SERVER[‘HTTP_ORIGIN’]);
$origin=$referer_parts[‘scheme’].”://”.$referer_parts[‘host’];
header(“Access-Control-Allow-Origin: $origin”);
header(“Access-Control-Allow-Credentials: true”);
header(“Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept”);
header(“Vary: Origin”);

Así mismo, deberemos parchear en el lado del cliente, y asegurarnos de que la llamada AJAX incluya las cabeceras adecuadas. Si se está usando JQuery, deberéis añadir a los parámetros de la llamada AJAX:

$.ajax({

xhrFields: {withCredentials: true },

});

Realmente no termino de entender la necesidad de “withCredentials” en ambos lados (cliente y servidor), ya que no estoy usando ningún tipo de credencial para autentificar al cliente. Sin embargo, no he conseguido que funcione sin ponerlo. Si alguno tiene información al respecto, agradecería que la compartiese.

wkhtmltopdf: Conversión HTML a PDF

Lo se, tiene un nombre impronunciable: wkhtmltopdf

Y probablemente es por este motivo que pocos programadores conocen o usan este magnífico proyecto de software libre que puede simplificarnos la vida en mas de una situación espinosa. A mi me ha resuelto el problema maravillosamente en dos ocasiones, y es por eso que os hablaré hoy de él.

Imaginad que estáis programando un sistema en el que es necesario generar un PDF de cierta complejidad y tamaño, utilizando datos de una BBDD. Por ejemplo, un cliente que os solicita la posibilidad de generar un catálogo en PDF con los productos de la BBDD de su plataforma de ecommerce.

La generación directa de PDF utilizando APIs como FPDF, TCPDF o PDFLib es pesada y engorrosa, sobre todo si el diseño gráfico es importante. Además, es costosa de mantener.

La otra opción que rápidamente nos viene a la cabeza es la de generar en HTML, y convertir luego a PDF. Es una opción que nos permite integrar fácilmente el diseño, además de ser mas fácil de mantener.

Pisaremos un terreno mucho mas confortable (HTML+CSS) y por si fuera poco,  la previsualización del documento será trivial. Esto último es importante. Tened en cuenta que una conversión de HTML a PDF con imágenes de  alta resolución aptas para impresión (300ppp) puede tardar minutos y ocupar muchos MB si el número de imágenes es considerable (>1000), así que conviene tener una buena previsualización antes de darle al botón de generar.

Existen algunos servicios on-line de conversión de HTML a PDF, como  phptopdf.com, pero tienen limitaciones respecto al tamaño de los archivos generados, etc.

Así pues, nos queda el software de conversión propiamente dicho. Por supuesto, tenemos dónde elegir:

  • html2pdf y derivados => el pionero, programado en Perl (que recuerdos!!)
  • mPDF
  • dompdf
  • paradoxpdf
  • etc.

Mi consejo es que no perdáis el tiempo probándolos y uséis diréctamente wkhtmltopdf.

¿Por qué wkhtmltopdf?

  • Es un proyecto maduro, pero relativamente activo. La última versión estable es de Junio de 2014.
  • Utiliza el motor de render WebKit. El mismo que utilizan Safari y Chrome.
  • Generación de Tablas de Contenidos (TOC)
  • Posibilidad de añadir Cabeceras y Pies de Página configurables
  • Soporte nativo de PHP, Python, etc. enlazando con la librería libwkhtmltox
  • Tiene uno de los nombres mas impronunciables que nunca se han concebido 😉

Instalación

Si usáis Debian o Ubuntu:

$ sudo apt-get install wkhtmltopdf

Si usáis Fedora o RedHat/CentOS:

$ wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.1/wkhtmltox-0.12.1_linux-centos6-amd64.rpm

$ rpm -Uhv wkhtmltox-0.12.1_linux-centos6-amd64.rpm

Para otras opciones de instalación: wkhtmltopdf.org/downloads

Primer contacto

La manera mas sencilla de empezar a utilizar wkhtmltopdf es desde la linea de comandos:

$ wkhtmltopdf http://www.ethereum.org ethereum.pdf

Este sencillo comando generara un PDF a partir de una página web on-line.

Ejemplo en PHP

//Obtener Datos de la BBDD …
$data=db_query(“SELECT datos FROM db”);

//Generar archivo HTML usando plantillas …
$buffer=template_proccess($tmpl_file,$data);
file_put_contents($html_file,$buffer);

//Generar archivo PDF …
$global_opts=”-s A4 -B 10 -L 10 -R 10 -T 12″;
$page_opts=”–zoom 1.2 –footer-center ‘[page]/[topage]’ –footer-font-size 6 –no-footer-line”;
$cmd=”wkhtmltopdf $opts page ‘$html_file’ $page_opts ‘$pdf_file'”;
$result=shell_exec($cmd);

if (!file_exists($pdf_file) || filesize($pdf_file)<1000) {
$message=gettext(“PDF generation error”).”: “.$pdf_file.”\n\n$cmd\n$result”;
throw new Exception($message);
}

El ejemplo utiliza algunos de los muchos parámetros que acepta el programa:

  • -s A4 -> utilizar A4 como tamaño de página
  • -B 10 -> Margen inferior
  • -L 10 -> Margen izquierdo
  • -R 10 -> Margen derecho
  • -T 10 -> Margen superior
  • –zoom 1.2 -> Ajusta el zoom del render
  • –foter-xxx -> opciones del pie de página

Para saber mas, ya sabéis, RTFM 😉

$ man wkhtml2pdf

Gracias a Jakob Truelsen y Ashish Kulkarni, los desarrolladores de wkhtmltopdf