Alojando sitio web estático usando CloudFlare y BackBlaze b2 (parte 3)

jueves, 23 de junio de 2022
Tiempo de lectura 10 minutos

Esta es la tercera y última parte sobre una serie de publicaciones que inicié detallando los pasos que he seguido para publicar un sitio web estático utilizando para ello Cloudflare y B2, junto con los runners de GitLab, que se encargan de construir y subir los ficheros del sitio. Puedes encontrar las otras partes de esta serie en los siguientes vínculos:

En las dos partes anteriores de esta serie de artículos, nos hemos centrado en crear una web estática y hacer que esa web vaya a almacenarse a un Bucket de B2. Como había especificado anteriormente, en esta última parte nos dedicaremos al punto final de la configuración, que es, enrutar todo el tráfico que venga desde nuestro dominio hacia la URL amigable que hemos podido obtener en B2 en la segunda parte de esta serie. A grandes rasgos, lo que haremos será crear un Worker de Cloudflare y utilizarlo para servir nuestros archivos del bucket de B2 desde la raíz de nuestro dominio. Es importante que para este punto, nuestro dominio esté siendo administrado por los servidores de nombres de Cloudflare, de lo contrario es probable que las cosas aquí no funcionen.

Paso 1. Configurar el dominio

Lo primero que hay qué hacer antes de nada, es asegurarnos de que el dominio se encuentra configurado adecuadamente y que existe, al menos, un registro tipo A en la raíz del mismo que apunte a algún sitio. En realidad hacia donde apunte no es importante, ya que nuestro futuro Worker se encargará de interceptar la solicitud de carga del dominio, pero si no configuramos ningún registro principal pasará que el dominio no cargará. Puedes configurar cualquier dirección IP como destino de tu registro tipo A principal, y continuar con lo siguiente.

paso 2. Crear el Worker

Un Worker, utilizando la terminología de Cloudflare, es una forma de crear aplicaciones sin necesidad de un servidor. Estas “aplicaciones” se ejecutarían una vez el usuario ha entrado a algún dominio asociado con algún Worker, y su función principal es la de redirigir, modificar la petición HTTP o la respuesta para que el cliente, que es el usuario que carga la página, vea lo que el desarrollador quiere mostrarle sin necesidad de que el usuario sea redirigido manualmente a ningún sitio. La diferencia de nuestro Worker con un registro DNS tipo CName, por ejemplo, es que al ingresar a nuestro sitio, el worker recuperará el fichero que se solicita por el usuario y no permitirá que este vea que en realidad estamos consultando las URL de nuestro bucket de B2. Esto también es ventajoso para nosotros en un par de otras formas:

  • El tráfico de B2 mediante Cloudflare es gratuito: Normalmente, B2 cobra por almacenar los objetos, por subirlos (si exceden su límite gratuito al mes) y por descargarlos. Debido a que Backblaze y Cloudflare son miembros de la Bandwidth Alliance, esto significa que el tráfico de datos entre ambos proveedores es completamente gratuito para el usuario, tal y como lo anunció B2.
  • privacidad: exponer la URL amigable de un bucket de B2 no es la manera más segura y recomendable de dar acceso a una web a los visitantes, por más estática que esta sea. Mediante esta URL amigable es posible mirar los ficheros de otros buckets públicos, incluso si no son de tu pertenencia. Sin embargo, utilizando el Worker de Cloudflare, será solamente en tu bucket configurado para tu web donde se buscará y consultará por los ficheros a servir.

Para crear tu primer Worker, es necesario acceder al tablero de Cloudflare. Selecciona la opción “Workers” del menú principal, lo que te mostrará la interfaz donde empezaremos a trabajar con nuestro primer Worker.

Para comenzar, Simplemente ubica y activa el vínculo llamado “Cree un servicio”. Esto mostrará un apartado dentro de la web de Cloudflare, donde podemos configurar el nombre del servicio. De forma predeterminada, cada servicio recibe un nombre con caracteres aleatorios y se asigna un subdominio para que puedas probar tu servicio antes de seguir con el despliegue. Puedes cambiar el nombre, si lo deseas, a fin de que sea más fácil recordar de qué se trata cuando lo veas en la lista de Workers, por ejemplo. En mi caso, le he puesto de nombre “manuelcortez-net”.

Después, es momento de seleccionar una plantilla para nuestro servicio. Realmente en este punto puedes seleccionar cualquiera, ya que pronto sobreescribiremos el código que se generará aquí. En mi caso he seleccionado la opción llamada " Controlador HTTP". Finalmente, es hora de ubicar y activar el botón llamado “crear servicio”.

Una vez que el servicio ha sido creado, nos encontraremos en la página del mismo. Aquí se puede consultar información sobre el número de solicitudes que ha recibido, así como otros datos importantes una vez se ponga en funcionamiento. De toda la información, lo que nos interesa en este momento es un vínculo llamado “Edición rápida”, que deberemos activar a fin de mostrar el editor de código que necesitamos para cambiar el funcionamiento de nuestro Worker.

Al cargar la página con el editor de código, es importante prestar atención a lo que se va a hacer aquí. Si usas lector de pantalla, ubica el primero de los campos de edición estando en el principio de la página. Aquí es donde podemos activar el modo foco del lector de pantalla, seleccionar todo el texto que ahí se encuentra, y proceder a eliminarlo. Después, copia el siguiente fragmento de código en un documento de tu preferencia (por ejemplo usando el bloc de notas), ya que lo necesitaremos modificar pronto:

const baseURL = "https://f001.backblazeb2.com/file/manuelcortez-net"

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event))
})


async function handleRequest(event) {
  // only allow get requests
  if (event.request.method !== 'GET') {
    return new Response('Method not allowed', { status: 405 })
  }

  const cache = caches.default
  let cachedResponse = await cache.match(event.request)
  
  const parsedUrl = new URL(event.request.url)
  let path = parsedUrl.pathname
  // check if a file or folder and 301 when tailing slash is missing
  let lastSegment = path.substring(path.lastIndexOf('/'))
  if (lastSegment.indexOf('.') === -1) {
    if (!lastSegment.endsWith('/')){
      return Response.redirect('https://' + parsedUrl.hostname + path + '/', 301)
    } else {
      path += 'index.html'
    }
  }

  // fetch content from B2
  const b2Response = await fetch(`${baseURL}${path}`)
  // add some headers
  const headers = {
    'cache-control': 'public, max-age=14400',
    'content-type': b2Response.headers.get('Content-Type')
  }
  const response = new Response(b2Response.body, { ...b2Response, headers })
  // all is well, return the response
  if (response.status < 400){
    event.waitUntil(cache.put(event.request, response.clone()))
    return response
  } else if (response.status == 404){
    // return error page
    return fetch(baseURL + "/404.html")
  }
  // return minimal error page
  if (response.status > 399) {
    return new Response(response.statusText, { status: response.status })
  }
}

Nota: Modifica la primera línea del fragmento de código y ubica la URL amigable de tu bucket, pero sin incluir el nombre del archivo. Si mi URL amigable era https://f001.backblazeb2.com/file/manuelcortez-net/index.html, solo es necesario quitar el index.html de la URL. El resto de código se puede quedar tal y como está. Una vez modificado el código, vuelve a la página del worker y pega el nuevo código en el editor de código que debería aún tener el foco.

Lo que hace este código es muy simple: para cada solicitud que este worker reciba, siempre que se envíe mediante una petición GET, intentará buscar en nuestro bucket de B2 el archivo que se solicita en la parte de la URL que no pertenece al dominio (por ejemplo, si se busca manuelcortez.net/file.mp3, buscará por el archivo file.mp3). Si no se busca por un archivo, entonces revisará si existe un directorio con ese nombre y si dentro de él está un fichero llamado index.html. Si encuentra algún archivo, lo enviará al navegador del usuario. Si no, tan solo mostrará un error.

finalmente, una vez pegado este código con las modificaciones correspondientes, hay que salir del modo foco del lector de pantalla, y activar el botón llamado “Guardar e implementar”. Si todo ha ido bien, verás una nota donde te dice que el Worker estará disponible en una dirección, generalmente perteneciente al dominio workers.dev. Vuelve a pulsar sobre el botón guardar e implementar, y ahora sí, tu worker debería estar implementado. Antes de continuar, asegúrate que puedes ver la página principal de tu sitio web al cargar este dominio de pruebas en tu navegador. De lo contrario, una vez hecho el despliegue del worker en el dominio principal, este fallará también.

paso 3. Configurar la ruta

Una vez que el Worker está creado e implementado, y que se ha comprobado que funciona, es necesario asignarlo a una ruta. Ahora mismo, para que el Worker funcione, el usuario tendría qué acceder al subdominio que Cloudflare asigna para pruebas. Y eso no se ve para nada como el dominio que alguien usaría para un sitio web. Así que una ruta hará justo eso, asignará una parte de tu dominio (en nuestro caso la parte es el dominio principal, pero esto también se puede hacer con subdominios si lo deseas) a un worker. De ese modo, al cargar el dominio principal Cloudflare redirigirá al usuario hacia nuestro Worker, y el Worker hará el resto del trabajo.

De nuevo en el Tablero de Cloudflare, accede a administrar el sitio donde quieras crear la ruta para el Worker. Una vez dentro de la administración del sitio, en el menú principal, selecciona de nuevo la opción “Workers”. Es necesario que estés dentro de la administración del sitio para ver las opciones de rutas en los Workers.

Una vez mostrada la página, ubica un encabezado llamado “Rutas HTTP” y después un botón llamado “agregar ruta”. Esto mostrará un diálogo desde donde puedes configurar los aspectos importantes sobre la ruta que hemos de crear. De este diálogo solamente hay qué seleccionar dos cosas importantes:

  • ruta: Especifica la ruta que el usuario debe ingresar para poder redirigirlo a nuestro Worker. Puedes usar asteriscos para indicar comodines en las URL. Por ejemplo, si pones dominio.com/* indicas que el worker se utilizará tanto si el usuario coloca dominio.com/hola como si pone dominio.com/saludos.html; pero no si coloca hola.dominio.com.
  • en servicio, utiliza las flechas de cursor para seleccionar el nombre del Worker que has creado, y después simplemente pulsa intro para confirmar la selección.
  • en entorno, selecciona el único que habrá creado por defecto. En mi caso este valor se creó de forma automática con mi primer Worker y es llamado Production.
  • Finalmente, pulsa sobre guardar para implementar el worker en la ruta de tu dominio especificada.

Conclusión

Y hasta aquí emos llegado en esta serie de artículos sobre Cloudflare, B2, y cómo servir tu sitio web sin usar precisamente un servidor. Si todo ha salido bien, el sitio web debería cargar tan pronto ingreses tu dominio principal en el navegador. Como comentaba a lo largo de los artículos, esta es una configuración muy básica y prácticamente la he escrito para evitar olvidar lo que he hecho en caso que me fuera necesario hacerlo de vuelta. Esta configuración está muy orientada hacia mis propias necesidades, pero creo que a quien le interese hacer una configuración general, debería ser capaz de cambiar los componentes aquí expuestos por otros que se adapten mejor a la necesidad particular de cada quién. En general, puedes reemplazar la parte de crear el sitio web (usando Nikola, o algún otro generador estático), subirlo (Usando github actions, algún otro CI, o simplemente una máquina local con aws-cli instalado) y hasta dónde alojarlo (hay muchas ofertas de almacenamiento de objetos tipo S3 hoy en día). Creo que, a grandes rasgos, el experimento de alojar mi web usando servicios aquí mencionados ha sido satisfactorio y se puede alcanzar el mismo nivel de disponibilidad, siempre que el contenido sea estático, que con un servidor web tradicional.

Apuntes Tutoriales

S3 B2 Gitlab Sitio web estático CI/CD