Optimización AJAX 4: Caché permanente

En la parte final (por ahora) de esta serie sobre Optimización de respuestas AJAX vamos a revisar cómo utilizar técnicas de caché permanente, o lo que se debería denominar más correctamente caché-que-se-controla-desde-el-servidor — lo que complementa a la parte anterior sobre “caché volátil” o caché-que-se-controla-desde-el-cliente.

Cabeceras de control de caché

Cada vez que el cliente hace una petición al servidor web, éste envía junto al contenido un conjunto de cabeceras, que el navegador interpreta para activar un conjunto de comportamientos. En este sentido, una respuesta AJAX es como cualquier otra por lo que el navegador va a respetar las instrucciones indicadas en las cabeceras; de este modo podemos indicarle que guarde la respuesta en su caché y lea los datos desde el disco local, lo que evita enviar una nueva petición y acelera todo el proceso.

Por ejemplo, al hacer una solicitud a este sitio puedes ver las siguientes cabeceras:

felipe@laptop [08:56:14] [~]
-> % curl -I http://www.yukei.net/
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 28 May 2015 23:56:18 GMT
Content-Type: text/html; charset=UTF-8
Cache-Control: public, must-revalidate, proxy-revalidate
Etag: 53836202edce31eae8555b8b7f0344ad
Last-Modified: Thu, 28 May 2015 23:50:24 GMT
Pragma: public
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: W3 Total Cache/0.9.4.1
X-Pingback: http://www.yukei.net/xmlrpc.php

… lo cual es un set bastante típico y representativo de cabeceras. No son todas las que existen en el universo pero nos basta como muestra.

Existen varias cabeceras que están relacionadas con el control de caché, pero en general las podemos dividir en dos grupos:

  • Las que le indican al navegador que debe almacenar una copia de la página o archivo en el disco local y luego utilizar esa versión local en lugar de volver a solicitar el recurso.
  • Las que indican al navegador que debe utilizar la copia local de un recurso.

Revisemos las del primer grupo.

cache-control

Aunque por el nombre es bastante claro lo que hace esta cabecera, su funcionamiento es un poco menos intuitivo, ya que es suele ser compleja y tener múltiples valores:

  • public indica que la respuesta es válida para cualquier usuario, por lo que si existen cachés intermedios (por ejemplo, un proxy que en una red local también actúa como caché o “acelerador”, como es común encontrar en redes de universidades o grandes empresas) estos pueden guardar la respuesta para ser consumida por otros clientes. Piensa básicamente en cualquier cosa que se ve igual independiente de quién lo consulte; sin iniciar sesión. Lo contrario sería private, que indica que la respuesta es válida solamente para el cliente que hizo esa petición en particular y por lo tanto no se debería guardar en el proxy.
  • must-revalidate indica que cuando el cliente va a solicitar nuevamente el mismo recurso, debe volver a enviar la petición para verificar que el recurso no ha cambiado. Más adelante revisaremos cómo se hace esto con Etags y respuestas 304.
  • proxy-revalidate indica básicamente lo mismo, pero para los cachés intermedios.

Hay muchas otras directivas que se pueden incluir en esta cabecera; una de las más notables es max-age: {segundos} que indica el tiempo de expiración del recurso en segundos a partir de la petición original. Cuando se usa esta directiva no va a estar junto a must-revalidate ya que el servidor le está indicando al cliente que su copia es válida por “x” segundos y que no vuelva a solicitar el recurso hasta pasado ese periodo.

No hay una única versión ideal para la configuración de esta cabecera ya que su configuración va a depender de la naturaleza de la aplicación, los datos que se consultan, etc. Por ejemplo, si es información que se actualiza muy frecuentemente no vas a querer que el usuario quede con una copia antigua; pero si tiene un ritmo de actualización constante pero no tan frecuente y donde no es crítico que el usuario vea las actualizaciones del sitio podrías hacer un buen uso de max-age.

Esta cabecera es el estándar más reciente y reemplaza a las cabeceras que se utilizaban anteriormente.

De todos modos revisaremos algunas de las más comunes.

expires

Es la cabecera que más uso tenía antes de cache-control.

Es mucho más sencilla, ya que indica solamente un dato: la “fecha de vencimiento” del recurso solicitado por el cliente. En este sentido, es algo similar a la directiva cache-control: max-age pero con la diferencia que en lugar de indicar un tiempo de validez, señala una fecha de expiración. Es decir:

  • max-age indica “este contenido que fue solicitado ahora es válido por {xxx} segundos más”
  • expires indica “este contenido es válido hasta el {xxx} de {xxx} a las {xxx}”

Por ejemplo, en el sitio de un periódico en línea que se actualiza regularmente a determinadas horas, podrías usar esta cabecera con la fecha y hora de la próxima actualización. Pero ojo, que lo indicado por cache-control tiene más peso.

pragma y vary

Pragma cumplía la función de indicar si correspondía almacenar la respuesta en caché. Sus valores son similares a los que se usan en cache-control: public/private o no-cache

Vary resulta un poco más enigmática al punto que los distintos navegadores solían tener problemas con su implementación. Básicamente, indica para quién es válida la respuesta; algunos de sus valores son:

  • User-Agent, lo que significa que distintos navegadores podían obtener distintas versiones del mismo recurso, por ejemplo, si la versión móvil fuera distinta a la de escritorio.
  • Accept-Encoding se usa para verificar que el cliente reciba los datos en un formato que pueda entender. El caso típico es para determinar si el navegador puede recibir respuestas comprimidas con gzip.

Validación de caché con Etags

La cabecera ETags son un mecanismo para que el navegador determine si el recurso que está solicitando coincide con lo que tiene almacenado en caché, e invalidarlo si corresponde. Es decir, en lugar de comparar por fecha de modificación y expiración del caché, vas a comparar por el contenido de la respuesta, de modo que si el contenido de la respuesta cambia, el navegador debiese obtener inmediatamente la nueva versión.

Obviamente comparar la respuesta con sí misma no tiene mucho sentido, por ello el valor del ETag corresponde a un hash, para el cual no hay un formato estándar.

Cuando una respuesta incluye una cabecera ETag y el cliente decide guardarla en caché, al ejecutar una nueva solicitud a la misma URL el navegador agregará a la petición la cabecera If-None-Match con el valor de la cabecera ETag. Si el valor coincide, el servidor envía una respuesta con código 304 Not Modified, que puede contener cabeceras pero no tiene un cuerpo, ya que el navegador utilizará como contenido lo que tiene almacenado en caché.

Es decir, el diálogo es algo como:

  • CLIENTE: Hola servidor, dame el archivo index.html
  • SERVIDOR: Ok, aquí tienes el archivo. Puedes cachear este archivo con el identificador abcxyz → envía ETag
  • CLIENTE: Ok, lo almacenaré en caché según las órdenes que me has dado.
  • CLIENTE: Hola servidor, necesito nuevamente el archivo index.html; tengo una copia con el identificador abcxyz, ¿aún es válido? Envía If-None-Match
  • SERVIDOR: Sí, usa la copia que tienes. → Envía 304 Not Modified
  • CLIENTE: Ok.

Un ejemplo muy básico sería:

function yukei_in_cache( $etag ){
    // $_SERVER['HTTP_IF_NONE_MACH'] tiene el valor
    // del etag guardado por el navegador
    // si no lo envía, es porque no está en caché
    if ( ! isset( $_SERVER['HTTP_IF_NONE_MATCH']) ) {
        return false;
    }
    if ( $_SERVER['HTTP_IF_NONE_MATCH'] == $etag ){
        return true;
    }
}

function yukei_ajax_get_posts(){
    $paged = absint(  $_GET['paged'] );
    $posts = get_posts([ 'paged' => $paged ]);

    // voy a usar como hash los ids de los posts.
    // esto significa que si se ha publicado un nuevo
    // post, el hash/etag será distinto y se invalidarán
    // los datos en caché
    $ids  = wp_list_pluck( $posts, 'ID' );
    $etag = md5( json_encode($ids) );

    // la respuesta siempre debe incluir cabeceras
    // de control de caché
    header('ETag: '. $etag );
    header('Cache-Control: public, max-age: 3600');

    if ( yukei_ajax_posts_in_cache( $etag ) ) {
        // está en caché y es válido
        header('HTTP/1.1 304 Not Modified');
        exit;
    }

    // indicamos el tipo de contenido de la respuesta
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode( $posts );
    exit;
}

add_action('wp_ajax_yukei_get_posts', 'yukei_ajax_get_posts');
add_action('wp_ajax_nopriv_yukei_get_posts', 'yukei_ajax_get_posts');

Referencias

Puedes encontrar más información en los siguientes enlaces:

Related posts

The post Optimización AJAX 4: Caché permanente appeared first on yukei.net.

Optimización AJAX 3: caché volátil

Poder guardar respuestas en caché nos puede evitar tener que realizar nuevas peticiones a consultas para las cuales ya hemos obtenido los resultados anteriormente.

Por supuesto, no siempre vamos a querer guardar estos resultados: en casos donde es muy probable que la información sea muy dinámica y lo más importante es contar con la información actualizada al segundo lo correcto sería justamente lo contrario, invalidar la caché de modo de siempre contar con datos reales.

Hay básicamente dos puntos donde podemos controlar el almacenamiento en caché de las respuestas AJAX: al momento de realizar la petición o bien al generar y enviar la respuesta. En el primer caso, el control estará de parte del cliente; mientras que en el segundo por la respuesta generada por la aplicación.

Caché volátil

Por caché volátil me voy a referir fundamentalmente al que podemos controlar desde el cliente, y que por lo general está acotado a la duración de la sesión, es decir, dura mientras el documento permanezca abierto — con dos excepciones que mencionaré más adelante.

Control de caché en peticiones AJAX con jQuery

jQuery tiene una opción de configuración en su método AJAX de bajo nivel que puede resultar un poco confusa por lo que es, así que mejor entender qué hace cuando está desactivada:

$.ajax({
    url: 'http://mismodominio.com/ajax.php',
    data: {
       type: 'highway',
       id: 61
    },
    cache: false, // es 'true' por defecto a menos que type sea 'jsonp' o 'script'
    type: 'get',
    success: function(data){
        alert('data');
    }
});
// jQuery envía una petición a:
// /ajax.php?type=highway&id=61&_1430353966067

Es decir, cuando el parámetro cache es falso, jQuery agrega un parámetro '_' a la petición con una marca de tiempo, lo que se conoce como un cache buster. Como este parámetro varía con una precisión de milisegundos, va a forzar al navegador a enviar una nueva petición ya que no coincide con una URL previamente consultada que pudiera estar en el caché del navegador.

Si dejamos como verdadero este parámetro, va a depender tanto del navegador como de las cabeceras de respuesta si la petición se guarda en caché —ojo que el artículo enlazado tiene algunos años, aunque es probable que la situación que describe se mantenga tal cual.

Habiendo despejado cómo evitar el caché de una respuesta, veamos cómo hacer que sí se guarde en caché.

Cachear respuestas en el DOM

Si la respuesta AJAX devuelve contenido HTML, es posible inyectarlo dentro de la página que el usuario está visualizando y detectar si ya existe para evitar volver a realizar la petición.

Un ejemplo común es cargar ciertas secciones de una página con AJAX, como en una interfaz con pestañas. Podríamos tener una estructura básica como:

<!-- pestañas -->
<ul id="tabs">
    <li><a class="active loaded" data-target="#uno" href="ajax.php?tab=uno">Uno</a></li>
    <li><a href="ajax.php?tab=dos" data-target="#dos">Dos</a></li>
    <li><a href="ajax.php?tab=tres" data-target="#tres">Tres</a></li>
</ul>
<!-- área donde se cargan los contenidos -->
<section id="uno" class="show">
    <!-- en la carga inicial este contenido viene precargado -->
</section>
<section id="dos"></section>
<section id="tres"></section>

Podemos utilizar varias técnicas tanto para hacer la relación entre los enlaces y los contenedores que recibirán el contenido como para determinar cuál es la pestaña y el contenido activo, pero para mantener el ejemplo sencillo estoy usando la clase active para indicar la pestaña actual y loaded para indicar si se ha hecho la petición.

Además estoy presuponiendo que el contenido de la primera pestaña viene pre-cargado al ingresar a la URL de la página.

Ahora vamos a la parte interesante:

$('#tabs').on('click', 'a', function( event ){
    // No ir al enlace
    event.preventDefault();        
    
    // Referencia al elemento donde se hizo el click
    var tab = $(this);

    // URL que tiene el contenido que vamos a cargar
    // en la pestaña
    var url = tab.attr('href');

    // El ID del área donde vamos a cargar el contenido
    var target = tab.data('target');

    // Es la misma pestaña activa, no hacemos nada
    if ( tab.hasClass('active') ) {
        return false;
    }

    // La pestaña que antes estaba activa ya no lo está
    $('#tabs').find('a.active').removeClass('active');

    // Si no se ha cargado el contenido de esta pestaña...
    if ( ! tab.hasClass('loaded') ) {

        // ... hacer la petición AJAX
        $.get( url, function( content ){
            // Al obtener la respuesta, inyectar en el
            // contenedor que corresponde
            $( target ).html( content );

            // Indicar que ya cargamos el contenido de esta
            // pestaña para no volver a hacer la petición
            tab.addClass('loaded');
        } );

    }

    // Indicar que ésta es la nueva pestaña activa
    tab.addClass('active');

    // Esconder el contenido que estaba activo...
    $('section.show').removeClass('show');

    // ... Y mostrar el nuevo contenido
    $( target ).addClass('show');
});

Como se puede ver, la lógica es bastante sencilla; la principal limitación es que esto sirve cuando la respuesta que obtenemos es contenido HTML o texto, pero si estamos trabajando con una API que devuelve JSON probablemente no lo sería tanto… o bien, necesitaríamos de un paso intermedio en el que se genera HTML a partir de los objetos recibidos.

Caché en variables

Una alternativa que puede ser más flexible sería almacenar las respuestas en un objeto. En este caso, necesitaríamos contar con un identificador que utilizaríamos como clave de búsqueda para verificar rápidamente si ya hemos obtenidos los datos de la consulta.

Podemos modificar el ejemplo anterior de la siguiente forma:

// Debe estar fuera de la función que se ejecuta al click
// ... de otro modo siempre estaría vacío
var contents = {};

// En lugar de detectar si el tab tiene la clase...

// Si no hemos cargado los datos de "target"
if ( typeof contents[ target ] === undefined ) {
    $.getJSON( url, function( data ){
        // Añadir la información al objeto
        contents[ target ] = data;

        // Hacer lo que tenga que hacer
        // ...
    } );
}

Hay poca variación ya que conceptualmente el proceso es muy similar:

  • Tener un repositorio donde se guardan los datos y un identificador único que permita diferenciarlos.
  • Debe ser posible consultar si los datos para una consulta ya fueron solicitados.
  • Si ya se hizo una petición para ese identificador, cargar la información desde el repositorio.
  • Si no está la información en el repositorio, hacer la consulta y anexar los datos al repositorio.

En ambos ejemplos nos estamos ahorrando la invalidación del caché, pero con algunas modificaciones más sería posible también considerarlo.

Siempre es posible utilizar métodos más elegantes, lo que es especialmente útil si tu aplicación es más compleja o necesitas desencadenar acciones a partir de varias consultas, etc. Para estos casos es especialmente indicada la utilización de promesas de Javascript.

… utilizando promesas y LocalStorage

La utilización de promesas lleva todo esto a otro nivel de abstracción, que quizás puede ser más complejo de comprender inicialmente pero una vez que puedes apreciar su utilidad se transforma en una herramienta muy poderosa.

Los métodos indicados hasta ahora permiten evitar nuevas consultas iguales durante una carga del documento, y al principio indiqué que hay dos excepciones; pues bien, éstas son:

  • SessionStorage, que se extiende por la duración de la sesión de página; es decir, al cargar una URL en una pestaña y sobrevive a recargas o restauración de la sesión de navegación. A diferencia de una cookie, abrir la URL en otra pestaña o ventana abre una nueva sesión.
  • LocalStorage, que tiene la diferencia de que no tiene expiración; es decir, se mantiene aun tras cerrar el navegador (por lo que probablemente necesitarás alguna forma de invalidar y refrescar los datos cacheados).

Al integrar Promesas (u objetos jQuery.Deferred(), que se comportan de modo similar) podemos obtener datos de forma asíncrona desde una API o desde localStorage de forma transparente y evitar ahogarnos en la “pirámide de la perdición” o el “infierno de callbacks” — o sea, podemos obtener un código más ordenado y simple.

En este caso el ejemplo es un poco más complejo porque me interesa mostrar algo funcional y más completo por lo que sólo comentaré lo esencial, pero puedes consultarlo completo en el gist de Caché de respuestas AJAX utilizando Promesas y localStorage (ojo que para que funcione correctamente debes descargar todos los archivos y ejecutarlo en tu servidor web local).

Vamos al ejemplo, partiendo por lo más familiar:

$(document).ready(function(){
    $('#ajax-nav').on('click', 'a', function(event){
        $('#ajax-nav').find('li.active').removeClass('active');
        $(this).parent().addClass('active');
        var indicador = $(this).data('key');
        var target = $( '#' + $(this).data('target') );
        var data = getData( indicador );

        // Siempre voy a tener como resultado una Promesa
        // y puedo hacer algo con esos datos luego de obtenerlos
        data.then(function(value){
            $('#ajax-container').html( buildDataTable( value ) );
        });
    });
});

Los cambios más notables son:

  • Modularizamos la obtención de datos a una función, para evitar repetir código y tener algo más ordenado
  • La variable data recibe un objeto Deferred en todos los casos, de modo que le es transparente obtener información desde una consulta AJAX o localStorage
  • … y como es una promesa, podemos realizar acciones de modo seguro luego de obtener los datos sin recurrir a hacks o pasar callbacks de un lado a otro

Ahora examinemos la obtención de datos:

var getData = function( key ){
    var localData = JSON.parse( window.localStorage.getItem( key ) );
    if ( ! localData || isExpired( localData ) ) {
        // jQuery envuelve la llamada AJAX como un objeto
        // Deferred que es similar a una promesa
        return $.ajax({
            url: 'data-'+ key +'.json',
            dataType: 'json',
            type: 'GET',
            cache: false
        }).done(function(value){
            // ya se resolvió la promesa, retorno datos
            return saveData( key, value );
        });
    } else {
        // Creo un objeto de Deferred que se resuelve
        // con los datos obtenidos desde localStorage
        var promise = new $.Deferred();
        promise.resolve( localData.content );
        return promise;
    }
};
  • Como jQuery ya agrega los métodos de una promesa a la petición AJAX, podemos utilizar done() para guardar los datos de la petición en localStorage. La función saveData() debe devolver el valor de modo que podemos ejecutar otras acciones tras resolver la promesa
  • En caso de tener los datos en caché, creamos un objeto Deferred y le indicamos que la promesa se debe resolver inmediatamente utilizando los datos almacenados

Para controlar la expiración de los datos, la función saveData() guarda una marca de tiempo junto con el contenido de la respuesta; y la función isExpired() comprueba la diferencia en base a un plazo fijo.

Related posts

The post Optimización AJAX 3: caché volátil appeared first on yukei.net.

Laboratorio de Gobierno, los nuevos desafíos para el diseño de experiencia en Chile

El jueves pasado asistimos al lanzamiento del Laboratorio de Gobierno, evento que planteó la pregunta ¿cómo estamos innovando en el Estado? como punto de partida de la conversación. En la presentación, expusieron diversas personalidades políticas entre las cuáles destacan el ministro de Economía Luis Felipe Céspedes, el vicepresidente ejecutivo de Corfo, Eduardo Bitran y el alcalde de Recoleta, Daniel Jadue.

La conversación fue guiada por Juan Felipe López, director de la iniciativa, y se enfocó en los actuales modos para innovar en el país, la participación del Gobierno y las entidades privadas en estos procesos y la creación del Laboratorio de Gobierno como agente de innovación en base a la colaboración y co-diseño.

¿De qué se trata?

Esta iniciativa propone trabajar interdisciplinariamente para abordar el rediseño de los servicios públicos del país. Además de armar un equipo de diversos profesionales (diseñadores, arquitectos, sociólogos y desarrolladores, entre otros) propone un trabajo colaborativo entre entidades como el Ministerio del Interior, a través de la Subdere, Hacienda, Segpres, Economía, Desarrollo Social, Corfo y el Servicio Civil.

Este sistema le entrega libertad para moverse y relacionarse con un mayor grupo de actores relevantes al momento de re-diseñar o re-pensar los servicios.

¿Cómo lo quieren hacer?

Como el diseño de servicio predica, la mejor forma de optimizar e innovar es en base a la colaboración y co-creación con el público-usuario, además de tomar en cuenta las opiniones de las autoridades e instituciones en el tema.

Para lograr esto, se trabaja muchísimo en terreno, acercando a la sociedad civil, sus problemas e inquietudes, para proponer mejoras en la experiencia de uso de los servicios. De este modo se permeabiliza el trabajo y las soluciones ya no sólo se generan en los escritorios, distantes a la realidad de los ciudadanos.

¿Por qué es relevante para nosotros?

En IDA diseñamos servicios digitales. Que exista este Laboratorio como agencia dentro del Gobierno, velando por mejorar la experiencia de los usuarios, habla de una maduración de nuestra disciplina en el país y, por qué no decirlo, de una validación de lo que hacemos y proponemos a diario.

La experiencia que las personas tienen con los servicios es fundamental, hace la diferencia y el Estado finalmente lo sabe.

En el Reino Unido lo vienen haciendo hace un tiempo. El sitio web de su gobierno, es una plataforma corporativa funcional, usable y accesible, cuyo objetivo es direccionar a todos los servicios públicos de sus cuatros países. Sus funciones incluyen desde certificados de nacimientos hasta renovaciones de pasaportes, además del manual para crear nuevos servicios con el estándar “digital por defecto” (qué envidia, ¿no?).

La proyección

El Laboratorio de Gobierno se enfrenta a grandes desafíos y nuestras expectativas son altas. Estamos ansiosos por empezar a ver resultados y casos de éxito. Queremos que el diseño centrado en el usuario sea el estándar, que las herramientas y técnicas del co-diseño se usen para pensar en los servicios públicos de nuestro país y que el valor esté en la experiencia.

Esperamos ver innovación digital y no solamente la solución de problemas básicos. Es más, nos gustaría ser parte de esta transformación, colaborando con nuestros conocimientos y expertise. En este escenario, ¿será un buen momento para que las partituras de interacción (PiX) se instauren como lenguaje de co-diseño? Estaremos atentos a las oportunidades que abrirá esta iniciativa.

La entrada Laboratorio de Gobierno, los nuevos desafíos para el diseño de experiencia en Chile aparece primero en Agencia digital IDA Chile | Estrategia para el éxito de tu negocio.

Optimización AJAX 2: compresión GZIP

Habilitar la compresión de la respuesta puede reducir la cantidad de datos enviados alrededor de un 70%. La mayoría de los navegadores soporta recibir contenidos, e informan de esta capacidad al servidor al realizar una petición.

Es posible agregar la indicación de comprimir la respuesta del servidor en su configuración de acuerdo al mime-type de la respuesta: text/html, text/plain, application/json, application/xml (y en general, es conveniente activarlo para cualquier lenguaje basado en texto, como text/html, text/css, application/javascript, application/json).

Habilitar compresión en Apache y nginx

En Apache puedes habilitar la compresión gzip añadiendo las siguientes líneas a la configuración del servidor o el archivo .htaccess relevante:

<ifModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file .(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</ifModule>

En el caso de nginx puedes activarlo dentro del bloque http de la configuración de modo que quede disponible para todos los virtual hosts configurados o bien por bloques server o incluso location.

En Ubuntu estos parámetros suelen venir en la configuración por defecto pero comentados.

gzip on;
# deshabilitar para IE 4 a IE6
gzip_disable "msie6";

gzip_vary on;
# des/habilitar compresión para respuestas de las nginx hace de proxy
gzip_proxied any;
# nivel de compresión, de 1 a 9
gzip_comp_level 4;
gzip_buffers 16 8k;
gzip_http_version 1.1;
# los tipos de respuesta que se enviarán comprimidos. siempre incluye text/html
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

Habilitar compresión desde PHP

También es posible activar la compresión de la respuesta en la misma aplicación; por ejemplo en PHP esto se puede lograr de forma muy sencilla con ob_gzhandler:

/**
 * Si el navegador no soporta Gzip,
 * la condición retorna falso y se abre un buffer normal
 */
if ( ! ob_start('ob_gzhandler') ) ob_start();
echo $out;
ob_end_flush();
exit;

Cómo comprobar si la respuesta fue comprimida

Puedes comprobar si la respuesta se está enviando efectivamente comprimida con las herramientas de desarrollo de Chrome (debes habilitar la opción use large request rows, junto a la opción para mostra los filtros de contenido).

2b90b5d301En la columna Size, puedes ver dos cifras: la primera corresponde al tamaño del cuerpo de la respuesta, mientras que la segunda a los datos transferidos. Si la cantidad de datos transferidos es menor al tamaño de la respuesta, significa que la compresión está funcionando.

Related posts

The post Optimización AJAX 2: compresión GZIP appeared first on yukei.net.

Optimización AJAX 1: elección del método HTTP

Si bien las interacciones en AJAX nos permiten obtener información de forma dinámica desde el servidor de un sitio web o aplicación, existen varias formas de aumentar la velocidad de estas respuestas para mejorar la experiencia de nuestros usuarios al cargar información bajo demanda, obtener datos de búsquedas u otras interacciones.

Más allá de la parafernalia tecnológica que supone la implementación de una respuesta AJAX (por supuesto, reducida al mínimo con el uso de un framework adecuado), ésta sigue siendo una respuesta HTTP, por lo que se pueden aplicar los mismos principios para mejorar su performance.

En éste y los siguientes posts intentaré revisar la teoría y práctica de las técnicas fundamentales para mejorar la performance de respuestas AJAX y la experiencia de tus usuarios.

Elección del método de respuesta

La primera regla es (o debería ser) casi obvia, por lo que vamos directo al grano: al ejecutar una petición AJAX, debes hacerlo con el método (verbo) HTTP adecuado.

GET para obtener, POST para enviar

Veamos un poco de background: las peticiones HTTP se realizan a través de distintos métodos, llamados también verbos debido a que determinan la acción que se realizará en la petición.

Los verbos más conocidos y que se encuentran implementados de forma más extendida son:

  • GET (obtener) se utiliza para solicitar “la representación de un recurso”. Una petición GET debería obtener data sin ningún otro efecto.
  • POST se utiliza para enviar información al servidor, tal como la información de un formulario de contacto o una nueva entrada de una base de datos.

Existen otros métodos tales como PUT, DELETE, HEAD, OPTIONS, etc; que a pesar de ser parte del estándar no siempre están implementados en alguna parte del stack (servidor web, framework) o son más propensos a ser interceptados por filtros de seguridad, por lo que se suele preferir los dos anteriores — por ello es común que para remediar esto algunas APIs implementen los restantes métodos a través de la cabecera X-HTTP-Method-Override.

Por lo tanto, enviar una petición por GET o POST tiene que ver en primer lugar con una cuestión semántica ya que indica el tipo de acción que vamos a realizar.

Por otra parte, en las peticiones GET toda la información va codificada como parte de la URL, por lo que existe un límite práctico dado por la longitud máxima aceptad por los navegadores. Como es de esperar, Internet Explorer es el que impone el mínimo denominador: 2 Kb.

Finalmente, en términos puramente de performance, en las peticiones POST los navegadores hacen un envío en dos partes: en primer lugar, las cabeceras de la petición y luego los datos; mientras que con GET lo que se envía es solamente la petición, que en la URL incluye todos los parámetros.

Con jQuery tenemos varias formas de implementar los distintos métodos de petición, por ejemplo, utilizando las APIs de más alto nivel, podemos ejecutar directamente uno de ellos:

// Cargar información por GET
$.get( ajaxurl, { action: 'get_latest_comments' }, function(data){
	$('#recent-comments').append( data );
}, 'html');

// Enviar un formulario con POST
$('#contact-form').on('submit', function( event ){
	// Podemos utilizar serialize para recoger toda la información
	// correspondiente a los campos del formulario
	var formdata = $(this).serialize();
	$.post( ajaxurl, formdata, function( reply ){&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;
		$('#contact-form').prepend( reply.status_message );
	}, 'json' );
	event.preventDefault();
});

Y también podemos utilizar la API de más bajo nivel, que nos permite especificar manualmente el tipo de petición:

$('#contact-form').on('submit', function(event){
	var formdata = $(this).serialize();
	$.ajax({
		type : 'POST',
		data : formdata,
		dataType: 'json',
		success: function( response ){
			$('#contact-form').prepend( response.status_message );
		},
		error: function( response ){
			alert( response.status_message );
		}
	});
	event.preventDefault();
});

Al utilizar jQuery.ajax() tienes la ventaja adicional que puedes indicar qué hacer en caso de errores.

Related posts

The post Optimización AJAX 1: elección del método HTTP appeared first on yukei.net.

PiX: Partituras de Interacción como lenguaje y software

Las partituras de interacción nos ayudan a modelar procesos donde interactúan personas y sistemas. Ya hemos escrito sobre ellas en artículos anteriores, explicando la importancia de visualizar la interacción, describiendo las 3 capas que las componen (Acciones del usuario – Contacto directo – Procesos internos), mostrando su lenguaje iconográfico y orientando su diferentes formas de uso en proyectos reales.

En esta oportunidad les contamos que durante el 2014 se produjeron una serie de modificaciones y actualizaciones en las partituras de interacción, ahora también llamadas PiX. Toda la información básica sobre PiX se puede encontrar en su sitio, donde se explica cómo usar el lenguaje, se presentan los íconos para las diferentes capas y algunos ejemplos de partituras. Además, se da acceso a una aplicación online para poder trabajar y crear partituras directamente desde el navegador (sólo funcional en Firefox por el momento).

Las partituras ahora tienen una nueva familia iconográfica que espera ser más completa y eficiente que la anterior, abordando el espectro de posibilidades que se requiere en entornos digitales. Todos los nuevos íconos de PiX (los PiXogramas) se pueden encontrar en el siguiente repositorio, donde pueden ser descargados y usados offline.

Las nuevas partituras de interacción ponen al alcance de todas las personas involucradas en proyectos digitales un lenguaje capaz de modelar el valor de un servicio, mediante el diseño de experiencias de calidad para los usuarios. PiX representa el lenguaje común entre gerentes, diseñadores, desarrolladores y un amplio etcétera de actores involucrados.

El diseño de interacción (IxD), diseño para la experiencia (UX) y diseño de servicios cuentan ahora con un lenguaje y un software para seguir potenciando su valor en los proyectos digitales. En IDA usamos partituras de interacción hace años y estamos ansiosos de poner en práctica PiX para sacar lo mejor de nuestros proyectos.

partitura de interacción gmail

La entrada PiX: Partituras de Interacción como lenguaje y software aparece primero en Agencia digital IDA Chile | Estrategia para el éxito de tu negocio.

¿Cómo convertimos mapas de navegación en wireframes?

En los procesos de diseño de la arquitectura de información hay dos transiciones complejas e importantes. La primera corresponde al paso de los contenidos sueltos a la creación de un mapa de navegación. La segunda es el salto desde ese mapa a una propuesta de wireframes.

Lo sé, parece haber un abismo desconocido en medio de estos dos entregables, así que en este artículo trataremos de construir un puente entre ellos.

Mapa de contenidos

Imagen 1: Ejemplo de mapa de navegación inicial

Metodología para crear wireframes

La metodología para traducir la información del mapa de navegación a los esquemas de página o wireframes está estructurada en 4 pasos:

Diferenciación

Tenemos nuestro mapa de navegación y conocemos sus contenidos. Ahora debemos notar que hay páginas que son más importantes que otras, ya sea por su contenido o porque tienen necesidades especiales, como que requiere un formulario. Estas páginas generalmente las nombramos plantillas. Por ejemplo, una página simple de texto será una plantilla que se usará en varias ocasiones en el sitio.

Entonces, en esta etapa diferenciamos los tipos de página que componen nuestro mapa. Al final de este paso deberíamos tener una lista con las plantillas para hacer en wireframes, ojalá con anotaciones sobre  las cualidades que las distinguen entre sí.

Identificación de páginas principales en un mapa de contenidos

Imagen 2: Identificación de plantillas claves del mapa

Verificación

Lo siguiente es hilar más fino. Ahora debemos distinguir qué otras páginas usarán las plantillas que identificamos en el paso anterior. En este paso recomendamos imprimir los mapas, discutir, rayar y acordar con el equipo. La importancia de este paso es que nos permite asegurarnos de que estamos diseñando lo justo y necesario. Si no especificamos esto, podemos terminar usando mucha energía en diseñar wireframes muy parecidos e innecesarios.

Antes de terminar, hay que verificar que todas las páginas del mapa tengan una plantilla asignada. Si no lo hacemos, algunas páginas no serán diseñadas y probablemente tenga que ser resuelto a última hora por el desarrollador. Debemos evitar que cosas de diseño de interacción se decidan por los programadores al final del proyecto. ¡Para eso estamos nosotros!

Asignación de plantillas a todas las páginas del mapa

Imagen 3: Asignación de plantillas a todas las páginas del mapa

Diagramación

Para comenzar a diagramar los primeros wireframes, se recomienda siempre partir trabajando en grupo en papel o pizarra. Así se podrá discutir sobre los puntos claves de las plantillas, en base a las anotaciones previas sobre sus necesidades específicas.

Estos wireframes de baja intensidad nos ayudarán a enfrentar la intimidante página en blanco inicial con confianza y certeza de que no se está dejando nada importante afuera. Cabe destacar que en esta etapa debemos concentrarnos en definir jerarquías del contenido a nivel micro, detallando funcionalidades, títulos, ubicación de fotografías, texto e iconografía relevante para el proyecto.

Wireframe de baja intensidad en pizarra

Imagen 4: Wireframe de baja intensidad en pizarra

Digitalización

Luego de decidir en grupo los grandes rasgos de nuestras plantillas, podemos comenzar a digitalizar. En este paso se diseñan todas las minucias de nuestra propuesta, con la precisión del pixel en la pantalla. Existe mucha información acerca de cómo crear wireframes, debes escoger las herramientas análogas y digitales que te servirán para estos últimos 2 pasos.

Wireframe digital

Imagen 5: Wireframe digital

Al abordar de forma metodológica y en equipo los saltos de un estado del proyecto a otro, podemos apoyarnos e ir perfeccionando nuestro trabajo en conjunto, para enriquecer la entrega final de los wireframes. Para esto es fundamental mantener siempre el foco en que estamos diseñando la interacción entre persona y sistema, por lo tanto estamos diseñando para la experiencia.

La entrada ¿Cómo convertimos mapas de navegación en wireframes? aparece primero en Agencia digital IDA Chile | Estrategia para el éxito de tu negocio.