Sylvain BRUAS

Accueil

Optimiser le comportement d'Amazon Cloudfront pour des sites web via des Cloudfront Fonctions (application avec Gatsby)

6 min read

Partager sur :

  • Gatsby est un outil permettant de créer rapidement des sites webs en utilisant le langage React, du javascript ou le langage TypeScript.

    Cet outil permet de facilement déployer le site sur Amazon S3 et d'ainsi réduire la complexité de maintenance et les coûts. Pour assurer une distribution du contenu rapide et efficace, nous utilisons Amazon Cloudfront. User request flow

    Installer le plugin Gatsby S3

      > yarn add gatsby-plugin-s3
    ou
      > npm install gatsby-plugin-s3
    

    Modifier gatsby-config.js

    Il faut ajouter la configuration du plugin dans ce fichier. Il est nécessaire de préciser à minima le bucket de destination.

        plugins: [
          {
            resolve: `gatsby-plugin-s3`,        options: {
            bucketName: "myBucket",        },
          },
        ]

    Problème identifié

    Si nous tentons de nous connecter directement sur une page autre que la page d'accueil, nous obtenons le résultat suivant :

    wget https://sylvain.bruas.fr/blog/aws-eks-contrainte-ip/
    --2023-10-30 15:18:32--  https://sylvain.bruas.fr/blog/aws-eks-contrainte-ip/
    Résolution de sylvain.bruas.fr (sylvain.bruas.fr)52.222.144.106, 52.222.144.45, 52.222.144.68, ...
    Connexion à sylvain.bruas.fr (sylvain.bruas.fr)|52.222.144.106|:443… connecté.
    requête HTTP transmise, en attente de la réponse… 403 Forbidden
    2023-10-30 15:18:32 erreur 403 : Forbidden.

     

    La solution proposée par Gatsby est d'utiliser S3 avec l'option Static Website Hosting et configurer le bucket en public-read. Dans cette configuration, le bucket peut être accédé directement, en HTTP si l'on trouve son URL.

    Une autre solution est disponible depuis la mise en place des Lambda@Edge (2016) et des Cloudfront functions (2021). En utilisant l'un de ces deux outils, nous pouvons manipuler la requête HTTP entrante, et si l'on identifie une URL terminant par un slash (/) ou qui ne contient pas d'extension nous ajoutons à la fin '/index.html'.

    Lambda@edge et Cloudfront Functions

    Nous allons donc déployer le code suivant, tiré de la documentation AWS.

    function handler(event) {
      var request = event.request
      var uri = request.uri
    
      // Check whether the URI is missing a file name.
      if (uri.endsWith("/")) {
        request.uri += "index.html"
      }
      // Check whether the URI is missing a file extension.
      else if (!uri.includes(".")) {
        request.uri += "/index.html"
      }
    
      return request
    }

     

    Il ne reste plus qu'à choisir entre Lambda@Edge et Cloudfront Functions. L'article annoncant la mise à disposition de Cloudfront fonction nous renseigne clairement sur les différences principales entre ces 2 services.

    CloudFront Functions Lambda@Edge
    Runtime support JavaScript Node.js, Python
    Execution location Edge Locations (~220) Regional Edge Caches (~10)
    CloudFront triggers supported Viewer request, Viewer response Viewer request,Viewer response,Origin request, Origin response
    Maximum execution time 1 millisecond 5 seconds (viewer triggers)
    Total package size 10 KB 1 MB (viewer triggers)
    Maximum memory 2MB 128MB (viewer triggers)
    Network access No Yes
    File system access No Yes
    Access to the request body No Yes
    Pricing Free Tier* + $0.10 per 1 million invocations $0.60 per 1M requests + $0.00005001 for every GB-second

    Free Tier pour CloudFront Functions : 2,000,000 Invocations

    Notre fonction javascript est très simple et légère. Elle s'exécute rapidement et ne consomme que de la mémoire, et en quantité limitée. Notre fonctionnalité peut donc être déployée sur les 2 services. C'est donc l'aspect financier qui va faire la différence.

    Que ce soit pour les petits sites qui profiterons de l'offre gratuite, ou le prix à la requête pour les sites ayant un grand nombre de visite, Amazon Cloudfront Functions reste la solution la plus efficiente économiquement.

    La suite de l'article et les exemples proposés se concentreront donc sur Amazon Cloudfront Functions.

    Nous allons utiliser Terraform pour déployer notre fonction.

    Déploiement de la solution

    1. Nous créons un fichier index.js dans le chemin functions/cloudfront du projet.

      function handler(event) {
        var request = event.request
        var uri = request.uri
      
        // Check whether the URI is missing a file name.
        if (uri.endsWith("/")) {
          request.uri += "index.html"
        }
        // Check whether the URI is missing a file extension.
        else if (!uri.includes(".")) {
          request.uri += "/index.html"
        }
      
        return request
      }

       

    2. Nous ajoutons dans le répertoire iac un nouveau fichier appelé cloudfront_functions.tf. Voici son contenu :

      resource "aws_cloudfront_function" "redirect-index" {
        name    = "redirect-index"
        runtime = "cloudfront-js-1.0"  comment = "add missing index.html at the end of url"
        publish = true  code    = file("../functions/cloudfront/index.js")}

      Il faut bien veiller à préciser le moteur (runtime) à utiliser, publier la fonction et modifier si nécessaire le chemin vers notre code.

       

    3. Ajouter le code suivant dans la définition terraform de votre distribution cloudfront, dans la section default_cache_behavior

      default_cache_behavior = {
          function_association = {      viewer-request = {          function_arn = aws_cloudfront_function.redirect-index.arn      }    }
          ...

       

    4. terraform plan

      ...
      
      Terraform used the selected providers to generate the following execution plan. Resource actions are indicated
      with the following symbols:
        + create
      
      Terraform will perform the following actions:
      
        # aws_cloudfront_function.redirect-index will be created
        + resource "aws_cloudfront_function" "redirect-index" {
            + arn             = (known after apply)
            + code            = <<-EOT
                  function handler(event) {
                      var request = event.request;
                      var uri = request.uri;
      
                      // Check whether the URI is missing a file name.
                      if (uri.endsWith('/')) {
                          request.uri += 'index.html';
                      }
                      // Check whether the URI is missing a file extension.
                      else if (!uri.includes('.')) {
                          request.uri += '/index.html';
                      }
      
                      return request;
                  }
              EOT
            + comment         = "add missing index.html at the end of url"
            + etag            = (known after apply)
            + id              = (known after apply)
            + live_stage_etag = (known after apply)
            + name            = "redirect-index"
            + publish         = true
            + runtime         = "cloudfront-js-1.0"
            + status          = (known after apply)
          }
      
      Plan: 1 to add, 0 to change, 0 to destroy.

       

    5. Terraform apply

      L'affichage sera le même qu'à l'étape précédente. Une invite va apparaitre pour vous demander de valider que vous êtes prêt à appliquer ces modifications. Après quelques minutes d'attente, la fonction sera déployée.

       

    6. Résultats

      Avant la mise en place de la fonction, nous obtenions le résultat suivant, une erreur 403 :

      wget https://sylvain.bruas.fr/blog/aws-eks-contrainte-ip/
      --2023-10-30 15:18:32--  https://sylvain.bruas.fr/blog/aws-eks-contrainte-ip/
      Résolution de sylvain.bruas.fr (sylvain.bruas.fr)52.222.144.106, 52.222.144.45, 52.222.144.68, ...
      Connexion à sylvain.bruas.fr (sylvain.bruas.fr)|52.222.144.106|:443… connecté.
      requête HTTP transmise, en attente de la réponse… 403 Forbidden
      2023-10-30 15:18:32 erreur 403 : Forbidden.

       

      Avec la fonction Cloudfront nous obtenons :

      wget https://sylvain.bruas.fr/blog/aws-eks-contrainte-ip/
      --2023-10-30 15:46:41--  https://sylvain.bruas.fr/blog/aws-eks-contrainte-ip/
      Résolution de sylvain.bruas.fr (sylvain.bruas.fr)52.222.144.68, 52.222.144.56, 52.222.144.45, ...
      Connexion à sylvain.bruas.fr (sylvain.bruas.fr)|52.222.144.68|:443… connecté.
      requête HTTP transmise, en attente de la réponse… 200 OKTaille : 108929 (106K) [text/html]
      Sauvegarde en : « index.html »
      
      index.html                 100%[=============================================>] 106,38K  --.-KB/s    ds 0,06s
      
      2023-10-30 15:46:42 (1,72 MB/s) — « index.html » sauvegardé [108929/108929]

       

      La requête est donc traitée ainsi :

      User request flow

    Conclusion

    Nous avons grace à Cloudfront functions mis en place une solution de réécriture d'url s'appliquant à toutes les urls de ce site. Cela permettra par exemple aux moteurs de recherche d'indexer toutes les pages, ce qui n'aurait pas été possible sans la fonction de réécriture des URLs.

    Vous pouvez retrouver tous les exemples de code précédents sur le dépôt github suivant : https://github.com/sylvainbruas/demo

Sylvain BRUAS

Sylvain BRUAS

Je suis architecte solution AWS depuis plus de 8 ans. J'interviens dans les grands groupes pour les aider dans leur transformation vers le cloud AWS.

Mes compétences principales sont les fondations AWS, le Dev(SecFin*)Ops et les containers.

AWS Community Builder et AWS Authorized Instructor depuis 2024