Créer un raccourcisseur d'URL avec FastAPI, Docker et Redis
09/11/2020
Connaissez-vous FastAPI ? Si la réponse à cette question est “non”, alors je vous invite à vous rendre sur leur site web : https://fastapi.tiangolo.com/. FastAPI tire profit de scarlette et de asyncio. On retrouve donc une librairie qui permet de développer des APIs asynchrones et donc, d’augmenter leur capacité de traitement sur certains appels. Ca vous fait penser à un truc? Node.js .. Ah bon 🤣
L’intégralité du projet est accessible sur un de mes dépôts GitHub : https://github.com/JeromeDesseaux/url_shortener
Environnement virtuel
Pour gérer les environnements, j’utilise personnellement pipenv (pip install pipenv
) qui permet de créer simplement des environnements virtuels pour mes projets. Après avoir créé un nouveau dossier, placez-vous dans celui-ci et appelez pipenv :
mkdir url_shortener
pipenv install fastapi pytest redis uvicorn
pipenv shell
On installe les librairies nécessaires au projet et on active l’environnement virtuel dans notre terminal. On est prêts!
Un premier serveur basique
Créons un fichier main.py
dans lequel nous allons placer une première version de notre serveur (assez rudimentaire) simplement pour tester que l’installation fonctionne.
Dans ce fichier, copions :
from fastapi import FastAPI
# Création du serveur principal
app = FastAPI()
@app.get('/')
def root():
""" URL racine sans autre utilité que de dire un petit coucou aux utilisateurs """
return {"message": "Welcome to our URL shortener app"}
A ce stade, si vous lancez votre serveur uvicorn main:app --reload
et accédez à la page http://localhost:8000 vous devriez tomber sur le json : {"message": "Welcome to our URL shortener app"}
Cool! Tout fonctionne. Maintenant, créons la logique de notre raccourcisseur d’URL.
La logique
Si tu es un bon développeur, tu sais qu’on ne code JAMAIS tout son code dans le même fichier. Ca parait bête, mais tout le monde n’est pas au courant … alors histoire de bien faire les choses, on commence par créer un module qui contiendra notre code. Appelons-le routers
. Dans ce dossier, on créé un fichier python qui contient notre code shortener.py
.
Ce que nous voulons, c’est envoyer une URL à notre service, qu’il la stocke en mémoire et nous fournisse une URL plus courte en échange. Lorsqu’on lui renvoie cette URL courte, il nous redirige vers le site en question. Nous avons donc besoin de deux services :
- Création et stockage des URLs longues
- Redirection grâce aux URLs courtes
Créons d’abord une classe qui nous servira à représenter notre modèle auprès de FastAPI grâce à pydantic
. Le truc magique avec FastAPI, c’est qu’il te créé une documentation automatiquement grâce à Pydantic! Alors autant en profiter! Tu vas voir, c’est absolument dingue. On créé cette classe dans un module dédié aux modèles du projet : models/url.py
from pydantic import BaseModel
class Item(BaseModel):
""" Définition de la classe de représentation des URL complètes et raccourcies """
url: str
# stocke l'url longue
custom_target: str = None # stocke l'url raccourcie
Maintenant, on peut créer nos services REST
from fastapi import APIRouter
from models.url import Item
from starlette.responses import RedirectResponse
import redis
import uuid
import os
router = APIRouter()
r = redis.Redis(host='localhost', port=6379)
@router.post('/shortify')
def shorten_url(item: Item):
"""
Raccourci une URL passée en paramètre.
Retourne la valeur contenue dans REDIS si déjà existante,
en créé une nouvelle et la stocke sinon
"""
url = item.url
redis_url = r.get(url)
if redis_url is None:
shorten_url = item.custom_target or str(uuid.uuid4())[-6:]
if r.mset({url: shorten_url}):
return {"url": url, "short": shorten_url}
return {"message": "failed"}
return {"message": "URL already exists", "short": redis_url}
@router.get("/{short}")
def redirect_url(short: str):
"""
Redirige les utilisateurs vers le site initial avant raccourcissement de l'URL
"""
for key in r.keys():
if r.get(key).decode("utf8") == short:
return RedirectResponse(url=key.decode("utf8"))
return {"message": "URL not defined"}
Une pause s’impose ! shorten_url
récupère un Item (et map la requête automatiquement avec le json envoyé au serveur! Encore un truc magique). Si l’URL passée dans l’item est déjà contenue dans Redis, on renvoie sa valeur courte. Sinon, on en créé une nouvelle. En cas de problème côté redis, on retourne une erreur.
redirect_url
quant à elle, récupère simplement l’url longue correspondante dans Redis, et réalise une redirection HTTP vers celle-ci.
On peut maintenant nettoyer notre fichier main.py
:
from fastapi import FastAPI
from routers.shortener import router as shortener_router
# Création du serveur principal
app = FastAPI()
# Ajout des routes du raccourcisseur d'URL
app.include_router(shortener_router)
Exécutons notre projet et testons le !
Les tests
Rendez-vous sur http://localhost:8000/docs et testez directement l’API depuis une interface web Swagger. Sinon, on peut exécuter les commandes via CURL :
curl -X POST "http://localhost:8000/shortify" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"url\":\"https://google.fr\"}"
# réponse :
#{
# "url": "https://google.fr",
# "short": "a62470"
#}
Cette commande doit vous retourner un json contenant la version raccourcie de l’URL. Copiez ce morceau et rentrez l’URL suivante dans votre navigateur :
http://localhost:8000/{URL_COURTE} en replaçant {URL_COURTE} par la valeur de la variable. Dans mon cas : http://localhost:8000/a62470. Si vous êtes redirigé vers google : félicitations, vous venez de coder votre premier raccourcisseur d’URL !
Suite ?
Dans un prochain article, nous parlerons de comment déployer ce projet avec Docker et d’automatiser le processus de création d’image. Nous verrons également comment tester votre projet de façon automatique et d’éviter ainsi les problèmes de régression.
Stay tuned!