Un raccourcisseur d'URL sans serveur
Tu as déjà partagé un lien raccourci — un bit.ly glissé dans un message, une URL à rallonge réduite à cinq caractères. Mais qu'est-ce qui se passe vraiment quand on clique dessus ? C'est précisément ce qu'on va construire.
Imagine la scène. Tu tombes sur un article qui vaut le détour, tu veux l'envoyer à quelqu'un, mais l'URL est un monstre de deux cents caractères bourré de paramètres de suivi. Tu la colles dans un raccourcisseur, et d'un coup elle devient court.ly/aZ3kP : propre, court, partageable. Des semaines plus tard, des milliers de personnes auront cliqué sur ce lien sans jamais se douter de ce qui se joue derrière ces cinq caractères.
Ce « derrière », c'est tout l'objet de ce cours. Et il faut le dire tout de suite, parce que ça va te rassurer : le cœur du problème est d'une simplicité désarmante. Au fond, un raccourcisseur ne fait que tenir un carnet d'adresses. Quand tu raccourcis une URL, il note quelque part que aZ3kP correspond à ton adresse longue. Quand un visiteur ouvre le lien court, il relit cette note et l'envoie vers la vraie destination. Voilà. Tout le reste — la sécurité, la montée en charge, la surveillance — n'est que de l'ingénierie soigneusement disposée autour de cette idée minuscule.
Alors pourquoi y consacrer un cours entier ? Parce que la façon dont tu héberges ce carnet d'adresses change absolument tout. L'approche classique consiste à louer un serveur qui tourne en permanence, attend les requêtes, et que tu paies même à trois heures du matin quand personne ne crée le moindre lien. Ça marche — mais ça te laisse une machine sur les bras : à entretenir, à mettre à jour, à surveiller. On va emprunter le chemin inverse, celui du serverless.
Le nom est trompeur : il y a évidemment des serveurs, mais c'est le fournisseur cloud qui s'en occupe entièrement. Toi, tu écris ton code et tu décris ta configuration ; tu ne provisionnes aucune machine, tu n'en patches aucune, et tu paies à la requête plutôt qu'à l'heure. Quand rien ne tourne, tu ne paies rien.
Pour bâtir notre raccourcisseur en serverless, on va faire travailler ensemble cinq services AWS. Chacun a un seul métier, et ils se passent le relais comme les postes d'une chaîne de montage. Le plus simple, pour les avoir en tête dès maintenant, c'est de voir d'un coup d'œil qui s'occupe de quoi :
| Service | Son métier dans le projet |
|---|---|
| DynamoDB | Tient le carnet d'adresses : quel code court mène à quelle URL longue |
| IAM | Donne au code le droit de toucher la base, sans jamais manipuler de mot de passe |
| Lambda | Exécute la logique : fabriquer un code, rediriger un visiteur |
| API Gateway | La porte d'entrée publique : elle route, vérifie et limite le trafic |
| CloudWatch | Garde la trace de tout ce qui se passe, pour comprendre quand ça coince |
Quand tu raccourcis une URL, ta requête traverse cette chaîne de gauche à droite : elle entre par la porte, déclenche le code, qui écrit dans le carnet d'adresses.
Pour ancrer ce trajet avant d'ouvrir chaque service un par un, le simulateur ci-dessous fait voyager une requête dans toute la chaîne. Lance les deux scénarios et regarde où passe l'information.
Clique sur un scénario et suis la requête à travers le système :
DynamoDB, la table de hachage du cloud
Notre raccourcisseur n'a qu'une seule question à poser à sa base, encore et encore : « à quelle URL mène ce code ? ». Pour un besoin aussi étroit, sortir une base SQL reviendrait à louer un camion pour transporter une lettre.
Avant d'écrire la moindre ligne de logique, il faut décider où notre raccourcisseur va ranger ses correspondances. Et pour bien choisir, il suffit de regarder honnêtement ce qu'on lui demandera. La réponse est presque décevante de simplicité : on lui posera toujours la même question — « à quelle URL longue mène le code aZ3kP ? » — et on attendra toujours le même genre de réponse. Jamais de jointure, jamais de filtre alambiqué. Juste une clé, et la valeur qui va avec.
Face à un besoin aussi resserré, une base relationnelle classique est franchement surdimensionnée. Elle t'obligerait à provisionner un serveur, à gérer un pool de connexions, et à régler la facture même les nuits où pas une seule URL n'est raccourcie. On ne cherche pas une base universelle ; on cherche un bon vieux dictionnaire, mais hébergé dans le cloud et capable d'encaisser n'importe quelle charge.
Une base NoSQL entièrement gérée par AWS. Le plus simple est de l'imaginer comme une table de hachage — un
HashMap — devenue géante et persistante : chaque élément est rangé sous une clé unique, et le retrouver par cette clé prend de l'ordre de la milliseconde, que la table contienne mille entrées ou un milliard.
Là où une base SQL t'impose des tables aux colonnes figées, reliées par des clés étrangères, DynamoDB se contente de stocker des items repérés par leur clé. Chez nous, cette clé sera tout naturellement le code court : c'est ce qu'on appelle la clé de partition, et c'est elle qui rend la lecture instantanée.
Sur notre cas précis, le contraste avec une base relationnelle est net :
| Besoin | Base SQL | DynamoDB |
|---|---|---|
| Lire par clé | Possible, mais via un serveur | Natif, ~1 ms |
| Serveur à gérer | Oui | Aucun |
| Coût au repos | Tu paies en continu | 0 en mode on-demand |
| Montée en charge | Manuelle, délicate | Automatique |
Dans le TP, tout cela tient en quelques gestes : tu crées une table dédiée aux liens, tu désignes le code court comme clé de partition, et tu actives la capacité on-demand. À partir de là, cette table devient la source de vérité de toute l'application — c'est elle qui sait, et elle seule.
Le simulateur ci-dessous donne à voir cette table de hachage. Insère quelques liens, puis cherches-en un par son code : la lecture par clé tombe immédiatement, sans avoir à parcourir les autres lignes.
Insère des liens, puis retrouve-en un par son code court :
IAM : un badge, jamais un mot de passe
Maintenant qu'on tient une base, une question gênante surgit : qui a le droit d'y écrire ? La tentation de glisser une clé secrète dans le code est forte — et c'est précisément le piège qu'AWS te permet d'éviter.
On tient notre base de données. Reste une question qu'on préfère souvent repousser, et qu'on a tort de repousser : qui, au juste, a le droit d'y toucher ? Dans un instant, notre code va lire et écrire dans DynamoDB, et il va devoir prouver qu'il en a l'autorisation.
Le réflexe du débutant — on est tous passés par là — consiste à écrire directement une clé d'accès et une clé secrète AWS dans le code. Ça tient le temps d'une démo. Puis le code finit dans un dépôt Git public, dans un log, sur une capture d'écran partagée trop vite, et la clé part avec lui. À cet instant, ce n'est plus ta petite fonction qui est compromise : c'est ton compte AWS tout entier.
Cette manière, c'est IAM, le service qui gère l'identité et les accès dans AWS — son service de sécurité, en somme. Et l'outil qui nous intéresse ici porte un nom simple : le rôle.
Ce n'est pas un compte, ni une personne : c'est un badge. Un ensemble de permissions qu'un service peut « endosser » le temps d'agir. Quand ta fonction endosse un rôle, AWS lui remet des identifiants temporaires qui l'autorisent à faire exactement ce que le rôle prévoit — et rien de plus.
L'image du badge n'est pas qu'une jolie métaphore, elle est exacte jusque dans le détail. Un visiteur dans une entreprise ne reçoit pas la combinaison du coffre ; on lui tend un badge qui ouvre uniquement les portes dont il a besoin, et qui cesse de fonctionner le soir venu. Ta fonction vit la même chose : elle ne connaît aucun secret, elle porte un badge, et AWS la laisse passer aussi longtemps que ce badge le permet.
Un rôle se garnit de policies, ces documents qui énumèrent les actions permises — par exemple dynamodb:PutItem pour écrire, ou dynamodb:GetItem pour lire. Dans le TP, tu crées un rôle dédié à tes fonctions, tu y attaches une policy qui ouvre l'accès à DynamoDB, et tu assigneras ce rôle aux fonctions au moment de les créer.
Le simulateur ci-dessous rend ce principe tangible. Décide des permissions que contient la policy, puis demande à la fonction d'agir : elle ne réussit que si son rôle l'y autorise vraiment.
Règle les permissions du rôle, puis tente une action :
Lambda : du code qui ne tourne que sur demande
Il reste à écrire le code qui fait réellement le travail. Un serveur qui tourne jour et nuit ferait l'affaire — mais pourquoi payer une machine à ne rien faire pendant les heures où personne ne crée de lien ?
Notre base attend, son gardien est en place ; il manque encore l'essentiel : le code qui fait le travail. On pourrait suivre la voie classique et démarrer un serveur web — un Flask, un Express — qui guette les requêtes. Mais ce serveur tournerait sans interruption, à brûler de l'argent à trois heures du matin alors que pas une âme ne raccourcit d'URL. Ce qu'on veut, c'est exactement l'inverse : du code qui ne s'exécute qu'à l'instant précis où une requête arrive, et qui s'efface aussitôt après.
Le service de calcul serverless d'AWS. Tu y déposes ton code, et Lambda se charge de tout le reste : il le réveille quand un événement le déclenche, l'exécute, puis l'éteint dans la foulée. Tu ne paies que les millisecondes réellement consommées, et la montée en charge est automatique — d'un utilisateur à un million, AWS lance autant de copies de ton code qu'il en faut pour suivre.
Pour notre raccourcisseur, deux fonctions suffisent, et on a tout intérêt à les garder séparées : chacune n'a ainsi qu'une seule raison d'exister.
| Fonction | Déclencheur | Ce qu'elle fait |
|---|---|---|
| create | POST /shorten | Reçoit une URL, fabrique un code unique, l'enregistre |
| redirect | GET /{code} | Cherche le code, renvoie une redirection vers l'URL |
La fonction create cache une difficulté qu'on ne voit pas au premier abord. Imagine deux personnes qui raccourcissent une URL au même millième de seconde, et dont les codes générés se télescopent : comment garantir que chaque code reste unique, sans poser de verrou ni monter une transaction compliquée ? L'astuce consiste à confier la vérification à la base elle-même.
On formule l'écriture comme une condition adressée à DynamoDB : « enregistre cet item seulement si ce code n'existe pas encore » (
attribute_not_exists). Si le code est déjà pris, la base refuse net et signale l'erreur ; la fonction l'attrape, tire un nouveau code et retente. L'unicité est garantie, sans le moindre risque de collision silencieuse.
La fonction redirect, elle, est presque triviale — mais c'est la plus sollicitée des deux. Elle cherche le code dans la base, et si elle le trouve, elle ne renvoie surtout pas une page de texte : elle répond par un code HTTP de redirection, ce petit signal qui dit au navigateur de filer sans attendre vers l'URL d'origine.
301 Moved Permanently est mis en cache par le navigateur : les visites suivantes sautent droit à la cible sans repasser par ta fonction — très rapide, mais tu perds le comptage des clics. Un 302 Found force le navigateur à repasser à chaque fois : tu peux alors compter les visites, au prix d'un appel Lambda systématique. Le bon choix dépend de ce que tu veux mesurer. La mise en cache d'un 301 par les navigateurs et les CDN est un sujet à part entière, traité dans le cours Reverse Proxy, CDN & Cache.
Le simulateur reproduit la fonction create et sa parade aux collisions. Pour que les conflits soient visibles à l'œil nu, l'espace des codes y est volontairement minuscule ; dans la réalité, sept caractères en base 62 ouvrent plus de trois mille milliards de combinaisons, et deux codes ne se télescopent pour ainsi dire jamais.
Raccourcis des URLs et observe la gestion des collisions :
Le réseau d'un raccourcisseur
Un raccourcisseur, c'est du web sous sa forme la plus pure : une requête, une réponse, une redirection. Avant de poser la porte d'entrée, regardons ce qui voyage vraiment sur le fil.
Tout ce qu'on a bâti jusqu'ici — la base, le rôle, les fonctions — n'a qu'un but : répondre à des requêtes HTTP. Avant d'ouvrir la porte au public, ça vaut la peine de regarder ce qui circule vraiment sur le réseau, parce qu'un raccourcisseur est un concentré de web : une requête, une réponse, et surtout une redirection.
Le web repose sur un échange simple et à sens unique de l'initiative : ton navigateur envoie une requête, le serveur renvoie une réponse, et c'est terminé jusqu'à la prochaine. Chaque requête annonce une méthode qui dit ce qu'on veut faire. Notre service n'en emploie que deux :
| Méthode & route | Intention |
|---|---|
POST /shorten | Créer un lien court |
GET /{code} | Suivre un lien court |
Chaque réponse s'ouvre sur un nombre à trois chiffres qui résume le sort de la requête. Quatre familles à retenir :
2xx ça a marché, 3xx va voir ailleurs (redirection), 4xx la requête est en tort, 5xx le serveur a fauté.
Notre raccourcisseur en parle un petit dialecte bien à lui, que tu as déjà croisé au fil des sections :
| Code | Quand il apparaît |
|---|---|
201 Created | Le lien court vient d'être créé |
301 / 302 | Redirection vers l'URL longue |
404 Not Found | Code court inconnu |
409 Conflict | Code déjà pris (écriture conditionnelle) |
429 Too Many Requests | Débit dépassé, bloqué par la porte |
Le cœur réseau d'un raccourcisseur, c'est la redirection. Quand un visiteur demande GET /aZ3kP, le serveur ne lui renvoie pas une page : il répond 301 accompagné d'un en-tête Location: contenant l'URL longue. Le navigateur lit cet en-tête et refait de lui-même une seconde requête vers la vraie adresse. Deux allers-retours, totalement invisibles pour l'utilisateur.
301 est permanent, donc mémorisable : navigateurs et CDN le retiennent, et les visites suivantes sautent droit à la cible sans même toucher ton serveur. C'est ce qui rend un raccourcisseur si bon marché à grande échelle — mais c'est aussi pourquoi tu perds le comptage des clics, comme on l'a vu côté Lambda.
Cette montée en charge invisible, et la façon dont les caches et les CDN s'intercalent devant ton service, ont leur propre cours : Reverse Proxy, CDN & Cache. Et pour comprendre comment HTTP lui-même a évolué — méthodes, statuts, versions 1.1 à 3 —, va voir HTTP · De 1.1 à 3.
https:// : du HTTP chiffré par-dessus TLS. Sans ça, n'importe qui sur le réseau pourrait lire — voire réécrire — la redirection. Cette couche de sécurité est l'objet du cours TLS/HTTPS.
Ce modèle « le client demande, le serveur répond » est parfait ici : un visiteur veut une redirection, rien de plus. Mais il a une frontière nette — le serveur ne peut jamais parler le premier. Le jour où tu veux pousser des données sans qu'on te les réclame (une messagerie, des notifications), il faut une connexion permanente. C'est exactement le contraste exploré dans le cours Chat temps réel serverless sur AWS.
Le simulateur ci-dessous rejoue ces échanges. Déclenche chaque scénario et lis la requête envoyée et la réponse renvoyée — jusqu'à la redirection que le navigateur suit tout seul.
Lance un scénario et observe l'échange HTTP :
API Gateway : le portier de l'API
Exposer une fonction nue sur Internet, c'est tendre une perche au premier bot venu. API Gateway s'interpose en videur : il vérifie qui frappe à la porte, et combien de fois, avant même que ton code ne s'éveille.
Nos deux fonctions sont prêtes — et c'est précisément là qu'un danger apparaît. Tant qu'elles restaient à l'abri, tout allait bien ; dès qu'on les expose au monde, plus rien n'empêche un robot malveillant de marteler l'adresse de création un million de fois par minute. Le résultat serait double, et également désagréable : ta base s'effondre sous la charge, et ta facture AWS s'envole.
Il manque donc quelqu'un à l'entrée pour décider qui peut appeler, et à quelle cadence. Ce videur, c'est API Gateway.
Un service géré qui se poste en contrôleur de trafic devant tes fonctions. Toutes les requêtes passent d'abord par lui : il s'occupe du routage, de l'authentification, et surtout des plans d'usage qui fixent les limites.
Dans le TP, tu déclares une API REST avec deux routes — une pour créer un lien, une pour rediriger —, tu attaches un plan d'usage à la route de création, et tu génères une clé d'API pour tes clients.
Vois-le comme un contrat passé avec chaque client. Il fixe deux plafonds complémentaires : combien de requêtes par seconde sont tolérées — le throttling, ou limitation de débit — et combien au total sur la journée — le quota.
Les deux plafonds ne protègent pas contre la même menace, et c'est pour ça qu'ils se complètent :
| Throttling | Quota | |
|---|---|---|
| Mesure | Requêtes par seconde | Requêtes par jour |
| Protège contre | Les pics soudains | L'abus sur la durée |
| Réponse si dépassé | 429 Too Many Requests | 403 Forbidden |
Quant à la clé d'API, c'est le mot de passe attribué à chaque client. À chaque requête, API Gateway pose deux questions : la clé est-elle bien là, et n'a-t-elle pas dépassé ses limites ? À la moindre réponse négative, il bloque sur-le-champ. Ce mécanisme de débit et de quota a son propre cours : Rate Limiting.
Le simulateur ci-dessous est un limiteur de débit miniature. Envoie des requêtes en rafale, puis ralentis : tant que tu respectes la cadence et le quota, elles passent ; au-delà, API Gateway te renvoie sèchement — sans jamais réveiller Lambda.
Envoie des requêtes — vite, puis lentement — et observe les limites :
CloudWatch : la boîte noire du système
Ton code s'exécute sur des machines que tu ne verras jamais. Alors quand un lien tombe en panne au beau milieu de la nuit, où vas-tu chercher la raison ? CloudWatch est l'endroit où tout est consigné.
Le système tourne enfin dans le cloud — et un dernier problème, plus sournois, pointe le bout de son nez : on n'y voit plus rien. Sur ta machine, quand un programme plante, l'erreur s'affiche sous tes yeux dans le terminal. Avec Lambda, ton code s'exécute sur des serveurs AWS auxquels tu n'as pas accès. Un visiteur clique sur un lien, ça échoue, et te voilà dans le noir : impossible de savoir pourquoi sans un endroit où regarder.
Cet endroit, où atterrissent les traces de tous tes services, c'est CloudWatch.
Le service d'observabilité d'AWS. Il rassemble trois types d'informations : les logs (les lignes que ton code écrit au fil de son exécution), les métriques (des chiffres : nombre d'invocations, durée, erreurs) et les événements. C'est la boîte noire de ton application — l'équivalent de l'enregistreur de vol d'un avion, inutile tant que tout va bien, et irremplaçable le jour de l'incident.
Le mécanisme est d'une discrétion totale. Chaque ligne que ta fonction écrit — un simple print, un console.log — CloudWatch la capte au vol. Chaque requête qui échoue, il l'enregistre. Le tout est rangé par log group, un groupe par fonction, pour que tu retrouves la trace de l'une sans fouiller dans celles des autres.
logs:PutLogEvents. C'est elle, et elle seule, qui autorise Lambda à écrire dans CloudWatch. Coupe-la, et le silence se fait. Les briques que tu assembles se tiennent par la main — c'est tout l'intérêt.
En pratique, dès que tu testes tes fonctions, leur activité et leurs erreurs filent toutes seules dans les logs. Tu ouvres le log group de la fonction, et tu lis exactement ce qui s'est passé — l'occasion, par exemple, de vérifier que ta logique de collision (le fameux 409 de la section Lambda) se déclenche bien quand elle le doit.
Le simulateur ci-dessous imite un log group en direct. Déclenche des invocations, réussies ou fautives, et regarde les lignes s'empiler avec leur horodatage pendant que les métriques se mettent à jour. C'est très exactement ce que tu fixes des yeux quand quelque chose cloche en production.
Invoque la fonction et regarde les logs et métriques arriver :
Assembler les cinq briques
Cinq services, cinq métiers. Pris séparément, tu les connais désormais ; reste à voir comment ils s'emboîtent — et à quel moment ce modèle serverless est réellement le bon choix.
Tu as vu chaque pièce fonctionner pour son compte. Le moment est venu de prendre du recul et de les regarder s'emboîter. Si tu ne devais retenir qu'une chose de chacune, ce serait celle-ci :
| Service | Sa responsabilité, en une phrase |
|---|---|
| DynamoDB | Tient le carnet d'adresses code → URL, lisible par clé en ~1 ms |
| Lambda | Porte la logique : fabrique les codes, gère les collisions, redirige |
| IAM | Sécurise les accès entre services par des rôles temporaires |
| API Gateway | Expose le système au public et le protège (clé, débit, quota) |
| CloudWatch | Observe l'ensemble : logs, métriques, erreurs |
Et quand une requête de création arrive, elle les traverse tous dans l'ordre, chacun jouant sa partition au passage :
C'est en suivant ce trajet du regard que la beauté du modèle saute aux yeux : nulle part tu n'as eu à gérer un serveur, ni à anticiper le moindre pic de charge. Chaque brique est gérée par AWS, indépendante de ses voisines, et facturée à l'usage réel. Le système te coûte zéro quand il dort la nuit, et encaisse un afflux soudain de visiteurs sans que tu lèves le petit doigt.
Tu ne repars pas seulement en sachant comment marche un raccourcisseur d'URL. Tu repars avec le schéma mental d'une architecture serverless complète : un stockage clé-valeur, du calcul qui ne s'éveille qu'à la demande, une sécurité fondée sur des rôles plutôt que sur des secrets, une porte d'entrée qui filtre le trafic, et de l'observabilité d'un bout à l'autre.
La suite logique, c'est le capstone : reconstruire le raccourcisseur entier à partir de zéro, en câblant toi-même les cinq services. Tu as désormais la carte en tête — il ne reste plus qu'à faire le voyage.