Cachear de forma sencilla una página dinámica en PHP

¿Te ha pasado alguna vez que tienes una web desde hace un montón de años, que la hiciste tú y a veces te da problemas de saturación por exceso de visitas? Pues a mi sí y, aunque llevaba tiempo pensando en alguna forma de cachear, que acababa siendo hipercompleja, hoy me ha dado por pensar alguna forma sencilla basándome en unas pruebas que había hecho hacía poco.

La cuestión es que sabía cómo hacerlo, iba por el camino, pero entre unas cosas y otras “nunca encontraba el momento”, hasta ahora. El sistema es bastante simple y en principio se podría aplicar a cualquier sitio. El código sería algo tal que este:

<?php
$md5 = md5($_SERVER["REQUEST_URI"]); // convertimos la URL a único identificador
$file = "cache/".$md5.".html"; // donde se guardará el fichero
$hora = filemtime($file); // comprobamos la hora del fichero si ya existiera
if(time() <= $hora+86400) { // asignamos un tiempo de cache de 86400 segundos
    include($file); // incluimos el contenido del fichero cacheado
    echo "<!-- ".date('YmdHis', $hora)." -->"; // (opcional) añadimos al pie de página la fecha-hora de la caché
    exit; // salimos
}
ob_start(); // abrimos la memoria
?>
AQUI VA LA WEB NORMAL
<?php
$fp = fopen($file, 'w+'); // abrimos el fichero de caché
fwrite($fp, ob_get_contents()); // guardamos el contenido de la página generada en el fichero
fclose($fp); // cerramos el fichero
ob_end_flush(); // devolvemos la página que se ha generado y cerramos la memoria
?>

Con este sistema podremos incrementar una página dinámica tranquilamente entre un 50% y un 1.000% la velocidad, dependiendo de la carga de base de datos o cálculo que tuviera anteriormente.

Presentación sobre SEO / WPO

Hace un par de días tuve, una vez más, la oportunidad de impartir una clase de un par de horas sobre SEO en ESDi. En este caso se planteaba como una clase más práctica para gente que no tiene muchos conocimientos tecnológicos y que probablemente tendrán que decidir a qué empresa de SEO contratar, por lo que me focalicé bastante en ese punto, el de cómo elegir a una empresa, qué ha de aportar esa empresa y cómo se ha de pensar el proyecto antes de lanzarse.

Hoy en día el que quiere lanzar un proyecto en la red y lo quiere lanzar bien ha de pensar en bastantes cosas… para empezar qué quiere conseguir con el proyecto, dónde se va a alojar (si está enfocado a un país o a nivel internacional), cómo evitar que haya posibles saturaciones en el sitio y absorber picos de tráfico… Además comenté algunas diferencias entre hacer la optimización de un proyecto nuevo de uno ya lanzado o de las diferencias entre el “SEO claro” y el “SEO oscuro”.

Además, y a raíz de algunas preguntas que me han hecho en varias ocasiones sobre qué herramientas utilizo para todo el tema de la optimización, hice un resumen de ellas, que básicamente son las propias de los buscadores: Bing, Google, Yahoo! y Yandex.

Puedes descargarte la presentación y comentar qué te ha parecido.

Border Gateway Protocol (BGP) y SEO

Aprovechando la visita de estas semanas atrás de David y Rubén estuvimos hablando de algo de lo que ya he comentado alguna vez y que, al estar aquí, pudimos medianamente probar: el tiempo de respuesta.

Uno de los problemas que habitualmente me encuentro con la gente de Miami es que si ponemos los sitios en Barcelona la conexión es lenta. Claro está, lento es algo muy extraño, porque estamos hablando de 0,1 segundos más por petición, pero claro, sólo una web que tenga 30 peticiones (algo bastante normal) significa que la web puede tardar en cargar 3 segundos más que si estuviera más cerca. Como digo, en principio no parece mucho, pero como haya algo de saturación en la red y en vez de 0,1 sea 0,2 la cosa empeora significativamente.

¿Y por qué digo esto? Pues básicamente porque cuando estás al otro lado del Atlántico te das cuenta de que 9.000 kilómetros de cable son muchos kilómetros incluso para la velocidad de la luz y la fibra óptica. La cuestión es cómo solucionar esto y la respuesta estaría en el BGP (que conste que aunque así dicho parezca sencillo, es bastante complejo).

Haciendo cuentas nos encontramos con 4 husos internetianos en los que hay problemas de retardos. Los 4 serían: costa oeste de USA (San Francisco), costa este de USA (Miami), centro de Europa (Frankfurt) y sur de Asia (Singapur). Digamos que con esto cubrimos el 80% del tráfico mundial. Probablemente un siguiente punto estaría en África. Las distancias entre estos puntos son bastante elevadas. San Francisco es un lugar muy bien conectado, por lo que es un punto de referencia interesante. Además, los buscadores tienen sede cerca de la ciudad, por lo que si los robots han de conectarse lo tienen muy fácil. La conexión de toda esa costa es bastante buena si lo centramos todo en esa ciudad. Miami es un punto interesante porque el tráfico de Latinoamérica mayormente pasa por aquí. Aunque ahora ya hay conexión hacia la costa oeste, lo que también es de gran ayuda. Pero el tráfico Europa-Argentina, digamos, acaba pasando por ahí. Sería una buena opción usar Nueva York, pero bueno, es una alternativa. El tráfico europeo acaba pasando por Frankfurt por lo que se convierte en un punto en el que los países tienen un buen tiempo de respuesta. El caso de Singapur es bastante parecido al de Miami… el tráfico de Australia, China, etc, tiene muy buena conexión con la ciudad, por lo que finalmente es otro de los nodos principales.

Está claro que tener repartido todo en esos 4 puntos sólo tiene sentido si tienes una web a nivel internacional, pero… ¿por qué no montar simplemente dos de los notos con algún tipo de sitio? Normalmente el problema se encuentra en las bases de datos. Aunque existen “cloud sql”, la distribución multigeográfica es simplemente penosa por culpa del retardo entre los nodos. Hay soluciones para ello… por ejemplo, si sabes que sólo vas a tener 2 nodos (o 3 o 4) puedes poner que los “autonuméricos” no sean de 1 en 1 sino de varios en varios, de forma que cada minuto o cada 5 minutos “sincronices” las bases de datos y no haya problemas de choque entre los identificadores. No es la mejor solución, pero es lo que tiene Internet, que está las 24 horas del día en algún punto del planeta.

En el caso de bases de datos que no se escriben con mucha frecuencia la cosa es más sencilla, ya que la actualización se puede centralizar cada cierto tiempo. Y esto último es lo que voy a probar en breve, o al menos eso espero… aunque primero he de acabar muchas cosas para que se haga realidad.

Web Performance Optimization, el libro

Aunque ya lo había comentado hace unas semanas por twitter, hoy aparece la que considero la primera versión decente de mi nuevo libro (aunque en esta ocasión estará sólo como libro electrónico) y que se llama Web Performance Optimization. El lema de este libro es: La velocidad es un elemento diferencial; el rendimiento es una nueva oportunidad.

La idea de escribir esto surgió hace un par de meses cuando, debido a mi frase de el SEO ha muerto de hace unos años y que cada vez veo que el SEO de por sí sirve de muy poco, sumado a la presentación que hice en el Search Congress de Bilbao, me hizo ponerme manos a la obra. Además, lo bueno de esto es que además de servir a los buscadores es muy bueno para los usuarios y para la conversión.

Aunque la Guía SEO acabó teniendo Creative Commons con el paso del tiempo, en esta ocasión he considerado interesante lanzarla directamente desde el primer momento con la licencia Reconocimiento – SinObraDerivada 3.0 Unported que permite su distribución libre siempre que no se realicen obras derivadas y se mencione la fuente.

Aunque esta primera versión tenga pocas páginas (no llega a las 30) me he limitado a hacer una lista de aquellos elementos a tener en cuenta, a modo de checklist, aunque espero a lo largo de las próximas semanas ampliar con más ejemplos e información la mayoría de los puntos.

Por supuesto, si alguien tiene alguna sugerencia, duda, o quiere hacer algún apunte sobre alguno de los temas que se tratan, será bienvenido cualquier contacto.

Visitar la página oficial o descargar directamente el PDF.

Cómo cargar JavaScript

Como ya he comentado alguna otra vez, el JavaScript es uno de los elementos que bloquean la carga de los sitios web. Para evitar este bloqueo podemos usar algunos métodos creados con otro código de JavaScript que nos servirá para cualquier fichero externo que queramos cargar.

Lo bueno de estos sistemas es que permiten cargar en el sistema no sólo JavaScript sino que se podría abrir hasta CSS. Los códigos son bastante sencillos:

function loadScript(url, callback){
  var script = document.createElement("script")
  script.type = "text/javascript";
  if (script.readyState){ // Internet Explorer
    script.onreadystatechange = function(){
      if (script.readyState == "loaded" || script.readyState == "complete") {
        script.onreadystatechange = null;
        callback();
      }
    };
  } else { // Otros navegadores
    script.onload = function(){
    callback();
  };
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}

Con esta función podemos cargar un script dentro del elemento head. Y con lo siguiente hacemos la llamada desde el código en el momento que queramos:

<script type="text/javascript" src="http://www.loquesea.ext/funcion.js"></script>
<script type="text/javascript">
loadScript("http://www.loquesea.ext/fichero1.js", function(){ });
loadScript("http://www.loquesea.ext/fichero2.js", function(){ });
</script>

Combinar y reducir JavaScript

En muchas ocasiones me encuentro que tengo varios JavaScript en una página y, al final, se hace bastante pesado tener que gestionar múltiples ficheros. Además, otra cosa que me gusta es la de reducir al máximo el tamaño del fichero, y el hecho de poder combinarlos también permite reducirlos…

Es por esto que existe para PHP una pequeña biblioteca de funciones llamada JSmin-php que ayuda a gestionar esta situación tanto la de combinar como de minimizar.

Básicamente lo que hace esta biblioteca es leer todos los ficheros JS de una carpeta, combinarlos, comprimirlos y generar un fichero único cacheado.

require_once("jsmin.php");
$files = glob("/carpeta/js/*.js");
$js = "";
foreach($files as $file) {
  $js .= JSMin::minify(file_get_contents($file));
}
file_put_contents("/carpeta/combinado.js", $js);

En el caso en que no queramos leer todos los ficheros de una carpeta y sólo incluior unos pocos, podemos cambiar la línea por algo al que así:

$files = array("/carpeta/js/1.js", "/carpeta/js/2.js", "/carpeta/js/3.js");

De esta forma generaremos un único fichero. Personalmente creo que esto habría que ejecutarlo una vez de forma externa, y usar el fichero generado a mano… a menos que montemos un sistema que verifique las cachés o que permita cambiar este fichero sin que ocurra nada… que luego ya se sabe, cambios de versiones, etc.

Data URI mejor que CSS Sprites

Una de las cosas que más a bombo y platillo se nos ha intentado meter en la cabeza en los últimos tiempos es que era mejor usar los CSS Sprites que no un montón de imágenes. Y es cierto, es mejor lo primero que lo segundo… ¿pero es lo óptimo? No.

En alguna ocasión he hablado ligeramente sobre las peticiones HTTP y lo que afectan en cuanto a la velocidad de carga de un sitio; una de esas cosas que comenté en su momento fue la de usar los Data URI. Y es que el uso de los CSS Sprites está muy bien si hablamos de navegadores como Internet Explorer 6 o 7, pero desde que tenemos los Internet Explorer 8, Firefox 3 u Opera 9 podemos ir a por un nivel más.

Hoy en día es mucho más óptimo reducir el número de peticiones que no el hecho del tamaño de los elementos en sí. la velocidad de conexión media ha aumentado de tal forma que no es un problema que los ficheros ocupen mucho, sino que haya muchos ficheros distintos, y esto es lo que consiguen los Data URI. Este método básicamente permite insertar imágenes directamente en el código de ficheros CSS y HTML codificando los elementos en Base64.

Teniendo en cuenta los navegadores que dan soporte a los Data URI, tenemos una buena forma de actuar:

  • Firefox 2+
  • Opera 7.2+ (no más de 4.100 caracteres)
  • Chrome
  • Safari
  • Internet Explorer 8+ (menor de 32 KB)

¿Y cómo es un Data URI? Pues algo tans encillo como esto:

data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge
8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1h
LnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g
77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7

Este elemento, convertido en un CSS podría ser algo tal que así:

.icono {
  background: url(data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS
  /7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AA
  ARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguW
  w6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7) no-repeat;
}

Para aquellos que quieran automatizarlo, existen una opción en PHP que lo hace… algo tal que así:

<?php
echo base64_encode(file_get_contents("icono.gif"));
?>

Y ya tenemos más optimizadas las peticiones HTTP de nuestro sitio… menos peticiones, mayor velocidad (sobre todo teniendo en cuenta que los CSS se cachean si se organizan bien y se reducen también las peticiones para verificar imágenes actualizadas).

CSS eficientes, según Mozilla

Desde hace ya un tiempo que vengo revisando con frecuencia mi forma de escribir los CSS y comparándolo con las cosas que se comentan en el artículo Writing efficient CSS donde se habla de cómo crear CSS eficientes.

Para empezar, existen 4 tipos de identificadores: ID, class, tags y universales. Cada uno de ellos tienen ciertas particularidades… y se pueden anidar. Así que según vayamos anidando y se vaya haciendo más complejo, la lectura y desarrollo también lo será.

Hacer algo de este estilo no sería muy útil:

table .fila td #enlace { ... }

Lo idel sería intentar reducir al máximo esta cadena a la menor cantidad posible de identificadores. A veces es mejor crear un class que no aprovechar la anidación de tags.

Regla 1: hay que intentar reducir la complejidad de los identificadores y evitar descendientes

Una vez se tienen claras estas cosas, también hay que tener presente otro detalle: cuanto más concreto sea un cambio en el CSS, mejor. Esto significa que hemos de intentar evitar tocar los identificadores universales ya que hacen cambiar todos los elementos, lo que implica una ralentización.

[hidden="true"] { ... }

Regla 2: hay que evitar cambiar los identificadores universales

Otra cosa importante a recordar es que los ID son elementos únicos por página, lo que significa que no tiene sentido que aparezcan al final de una serie de identificadores. Para poner un ejemplo:

table .fila td #enlace { ... }

sería lo mismo que esto:

#enlace { ... }

ya que sólo puede haber un #enlace por página…

Regla 3: los identificadores ID no han de tener padres en los CSS ya que son elementos únicos

Otra cosa a tener en cuenta es la nomenclatura de los identificadores. Normalmente las class e ID se utilizan para dar formato a la página, y esto podría llevarnos a utilizar palabras como “rojo” o “azul” cuando realmente queremos decir “alerta” o “información”. Si en un futuro se decide que una alerta no es de color rojo sino naranja, podría llevar a confusión a la hora de rediseñar el sitio.

Regla 4: los identificadores deben tener nombres semánticos y flexibles

A parte de estas recomendaciones, hay otras tantas que pueden ser de utilidad, y que son las que normalmente revisan los sistemas de compresión de CSS, como por ejemplo CSS compressor. Eliminar espacios innecesarios, reducir los colores web, ordenar los elementos…

Regla 5: reducir el tamaño de los CSS hace que se carguen y apliquen antes

En fin… unos simples consejos a tener en cuenta cuando se crean CSS desde cero o vamos a revisar algunos que ya hayamos hecho anteriormente.

Evita, con PHP, ataques XSS y SQL injection

Una de las cosas que normalmente no revisamos cuando creamos un sitio web es la vulnerabilidad que se tiene a ataques por URL por cosas como XSS e incluso a ataques a la base de datos por una mala configuración. Para esto normalmente se usa una revisión y se ejecuta, con PHP, la función htmlentities() que, gracias a eliminar el código HTML puede filtrar cosas como los <script>. Para solventar esto existe una cosa llamada Genius Open Source Libraries que, con unas simples funciones, permiten hacer una megalimpieza contra ataques de todo tipo.


require_once 'Core/sgConfig.inc.php';
// Output an unsafe string, presumably user input
$xss = '<script>alert(\'oh snap\');</script>';
echo 'If your entered your name as ' . $xss . ', we\'d be in trouble.<br>' . "\n";
// Sanitize that string, and output it safely
$htmlContentContext = sgSanitizer::sanitizeForHTMLContent($xss);
echo "But if we sanitize your name, " . $htmlContentContext . ", then all is well.<br>\n";
echo '<h2>HTML Attribute</h2>';
// We can also safely sanitize it for an HTML attribute context
$htmlAttributeContext = sgSanitizer::sanitizeForHTMLAttribute($xss);
echo 'Tainted strings can also be used in an <a href="http://google.com" title="' . $htmlAttributeContext . '">HTML attribute</a> context.<br>' . "\n";
echo '<h2>JavaScript string</h2>';
// And we can even make strings used in JavaScript safe
$jsString = '\';alert(1);var b =\'';
echo '<script type="text/javascript">
var a = \'' . $jsString . '\';
var aSafe = \'' . sgSanitizer::sanitizeForJS($jsString) . '\';
</script>';

En este sistema de limpieza descargable como proyecto Genius encontramos algunas funciones como sanitizeForHTMLContent (que limpia el contenido teniendo en cuenta que es código HTML), sanitizeForHTMLAttribute (que limpia como atributo de HTML, por ejemplo el “alt” de una imagen) o sanitizeForJS (que dejaría el código arreglado para poder ejecutarse como JavaScript).

Sin duda una biblioteca de funciones interesante a la hora de aumentar la seguridad de un sitio web.

CSS hacks para Internet Explorer 6 y 7

En muchas ocasiones nos encontramos que necesitamos aplicar algún tipo de estilo específico a Internet Explorer 6 o 7, que son los navegadores que menos funcionalidades CSS soportan pero que aún tienen una cantidad elevada de usuarios.

En estos casos, y debido a algunos errores en el funcionamiento del navegador, tenemos la posibilidad de ejecutar un pequeño hack que no afecta al resto.

El sistema es sencillo. Internet Explorer tiene un fallo y es que los atributos de los CSS permiten los símbolos “*” y “_” delante. Tomando de ejemplo este código a continuación, podemos ver algunos detalles:

.fondo {
  background: Green; /* Todos los navegadores */
  *background: Yellow; /* Sólo Explorer 6 y 7 */
  _background: Red; /* Sólo Explorer 6 */
}

¿Qué significaría esto? Pues que, todos los navegadores excepto Internet Explorer sólo leerían la primera línea, que es la única correcta. En el caso de Internet Explorer 8, también sólo leería la primera (las otras dos ya no, porque son erróneas). En el caso de Internet Explorer 7, como lee en orden y prima la última cosa correcta, de las 3 líneas leería hasta la segunda, porque el 7 no entiende la tercera, claro está.

En el caso de Explorer 6 es bastante similar… va leyendo en orden, y como entiende las 3 líneas, se queda con la tercera “porque es la última”… De esta forma, si queremos jugar con algunos pequeños hacks para Internet Explorer 6, 7 y 8 podemos hacerlo mediante este curioso sistema (gracias a los fallos que llevan las versiones anteriores)…