CloudFront es un servicio de AWS por medio del cual se puede entregar -de manera segura- data, videos, aplicaciones y API a nuestros clientes de manera global con baja latencia y alta velocidad. Muchas aplicaciones modernas ejecutan algún tipo de lógica en el borde (o “edge”), para ofrecer así la menor latencia posible y una experiencia personal al usuario. Usualmente para ello se puede hacer uso de Lambda@Edge, que es un servicio que esta presente desde 2017.

En mayo de 2021, AWS lanzó un nuevo servicio que es CloudFront Functions. Esta es una nueva plataforma de scripting serverless que nos permite ejecutar código JavaScript en más de 225 CloudFront edge locations, con tiempos de arranque de submilisegundos, capaz de escalar inmediatamente para soportar millones de peticiones por segundo. Y una ventaja importante, es que es más económico que @Edge.

El objetivo de este artículo es explorar este nuevo servicio y hacer algunas pruebas básicas con el mismo. De acuerdo a la documentación de CloudFront Functions, este resulta ideal en casos de uso como:

  • Normalización de llaves para un cache. Pues podemos transformar los atributos de una petición de HTTP y asi crear una llave de cache que sea óptima
  • Manipulación de Encabezados. Podemos insertar, modificar o borrar encabezados de HTTP en una petición o respuesta.
  • Redirects o Reescrituras de URL. Podemos redirigir a los usuarios a páginas basados en la información de la petición o reescribirlas de un path a otro.
  • Autorización de peticiones. Podemos validar tokens de autorización, como JSON web tokens.

Cuando asociamos una función a una distribución de CloudFront, CloudFront puede interceptar tanto las peticiones como las respuestas que llegan a un edge location y pasarla a nuestra función. Por tanto, los dos tipos que existen son viewer request y viewer response respectivamente.

Toda función que escribamos va a tener siempre la siguiente definición:

  • Handler es el punto de entrada a cualquier función.
  • Este recibe un sólo argumento llamado event, el cual es enviado por CloudFront. Y este event es un JSON que contiene la representación de la petición HTTP (y la respuesta si es que la modificamos).

Algo importante a tomar en cuenta: si nuestra función es de tipo viewer request cada vez que CloudFront recibe una petición, nuestra función será ejecutada. Incluso antes de validarse si el objeto solicitado está o no en el cache de CloudFront. Una vez que se ejecuta continua el flujo normal de CloudFront; es decir, revisar si el objeto esta en el cache y retornarlo o bien; ir al origin para obtener el objeto solicitado.

Es posible generar una respuesta directamente desde nuestra función sin que CloudFront le de procesamiento adicional. Por ejemplo: si queremos redirigir la petición a un nuevo URL, o si no esta autorizado retornar un código 401 o 403.

Ahora bien, el ejemplo que vamos a usar para ilustrar el uso es el siguiente: tenemos un sitio que ha sido sujeto a un rediseño y los links que tenian más visitación han cambiado a otros URL. Dado lo anterior, vamos a desplegar una función en CloudFront que sea capaz de detectar estos URLs viejos y redirigir al usuario a las nuevas páginas. En un ambiente más tradicional, esta labor se podría realizar en los servidores de HTTP para escribir ahi las reglas necesarias.

Pruebas

El primer paso es dirigirnos a la consola de CloudFront y seleccionar Functions y dar click en Create Funtion; en nuestro caso la nombre como RedirectOriginalRequest.

Crear Funcion

Esto nos muestra un ejemplo básico de una función, ahora vamos a escribir el siguiente código que simplemente valida si el URI que estamos visitando es uno de los que debemos redirigir. Opte por un simple switch, pero un caso productivo es posible que se requiera de mayor elaboración en el proceso de selección.

function handler(event) {
    var request = event.request;
    var newUrl;
    var match = false;
    var host = request.headers.host.value;
    switch (request.uri) {
        case '/area1/index.html':
            newUrl =`https://${host}/newArea1/index.html`;
            match = true;
            break;
    }

    if (match) {
      var response = {
          statusCode: 302,
          statusDescription: 'Found',
          headers: {
              'location': { value: newUrl }
          }
      };
      return response;
    }
     return request;
}

Debemos ver que si el URI tiene el patrón /area1/index.html lo vamos a redirigir al url nuevo /newArea1/index.html; esto por medio de un redirect (código HTTP 302). Si el URI no corresponde, continuamos con el flujo normal de CloudFront.

El resultado que tenemos en la consola sería:

Ejemplo de codigo

Escrito el código procedemos a darle Save Changes. Ahora, debemos probar la función antes de enviarla a publicar. Esto se hace por medio del tab de Test; y aca debemos indicar el tipo de evento (Viewer Request) y el stage de ‘Development’. Además indicamos un URL de prueba y agregamos el encabezado Host.

Como vemos en la siguiente imágen.

Prueba

Al dar click en Test function, veremos la siguiente respuesta; donde en efecto tenemos una redirección.

Resultado

Se preguntaran que significa ese 29 en Compute Utilization. Este corresponde a un valor entre 0 a 100 que indica la cantidad de tiempo que la función demoró en ejecutarse como un porcentaje del tiempo máximo permitido. En este caso tomo un 29% del máximo de tiempo permitido.

Ahora, si ponemos otro URL diferente, como /area2/index.html veremos que no se realizó el redirect y siguio el camino normal.

Resultado

Una vez que hemos validado nuestra función, llega el momento de desplegarla usando la función Publish function.

Publicar

Y luego debemos asociarla a una de nuestras distribuciones; indicando cual es, el tipo de evento (Viewer en este caso) y la política de cache. Esto puede demorar algunas minutos mientras se despliega totalmente.

Asociar

Una vez desplegado, si hacemos una prueba con un curl; veremos que efectivamente se comporta como esperamos.

Asociar

Conclusión

Espero que este artículo les haya sido de utilidad y consideren esta nueva herramienta de AWS dentro de nuestro arsenal para poder atender diversos casos de uso que podemos enfrentar en el día a día.