
Voici un petit article lié à l'expérience développeur (DX), sujet auquel nous sommes constamment confrontés en tant qu'entreprise de développement et ingénierie logiciel. Tout comme l'aspect expérience utilisateur (UX) pour les utilisateurs de vos sites et applications, l'expérience développeur permet de gagner temps et de rendre plus efficace le travail des développeurs.
Dans cet article, nous aborderons un point de friction de plus en présent dans nos workflow : la prise en compte et le tests des webhooks dans le développement local. De nos jours, les applications sont de plus en plus interconnectées et forcément cela passe par des appels API mais aussi par des webhooks pour écouter et réagir lorsqu'une donnée ou un évènement change dans une application tierce.
Qu'est ce qu'un webhook
Imaginez attendre un colis très important. Au lieu d'appeler le livreur toutes les cinq minutes pour savoir s'il est arrivé, nous lui donnons le numéro de sonnette. Dès que le livreur arrive avec le colis (c'est l'événement), il sonne (il nous envoie une notification).
Un webhook, c'est le même principe mais entre deux applications ou services sur internet :
Par exemple, la solution de paiement en ligne Stripe veut prévenir notre application web que quelque chose de spécifique s'est produit : un client a effectué un paiement.
Notre application web fourni une URL spécifique à Stripe. On l'appelle l'URL du webhook.
Quand un évènement se produit sur Stripe, celle-ci envoie automatiquement un message contenant les détails de l'événement à cette URL spéciale de l'application web. Par exemple, lorsqu'un client effectue un paiement, Stripe envoie les détails de la transaction sur l'URL de webhook.
L'application web, qui écoute à cette URL, reçoit ces informations et peut alors réagir immédiatement en conséquence. Par exemple, l'application marque la commande comme payée et envoie un mail de confirmation au client.
Le problème des webhooks en local
Pour faire simple, gérer les webhooks en développement local est compliqué principalement à cause d'un problème fondamental : l'accessibilité de l'ordinateur local depuis internet. En effet, le poste du développeur ne dispose pas d'une URL spécifique permettant à n'importe qui sur internet de communiquer avec lui.
Le service externe (Stripe, dans notre exemple précédent) qui envoie des notifications sur une URL de webhook a besoin d'une adresse publiquement accessible sur internet pour envoyer sa notification. L'adresse locale de l'application sur le poste du développeur n'est pas publique.
Notre solution via Ngrok
Chez SaaS Production, nous avons opté pour une solution basée sur un service de tunnelisation nommée Ngrok. Gratuite dans son plan de base, cet outil s'intègre très bien dans nos stacks techniques le plus souvent basées sur Docker. Elle s'utilise toutefois très facilement sans Docker également mais l'URL change à chaque fois ce qui demande une modification d'une variable dans votre programme dans ce cas.
L'idée est qu'une connexion est ouverte sur votre poste de développement et reliée aux serveurs de Ngrok. Une URL publique vous sera fournie lors de l'ouverture de la connexion et redirigera tout le trafic sur votre propre ordinateur, à l'URL privée que vous aurez choisie :
ngrok http --host-header=rewrite custom-url.loc:443
Cette commande démarre le tunnel vers votre ordinateur :
Session Status online Account [email protected] (Plan: Free) Version 3.22.1 Region Europe (eu) Latency 5ms Web Interface http://127.0.0.1:4040 Forwarding https://e11a-90-19-213-214.ngrok-free.app -> https://custom-url.loc:443 Connections ttl opn rt1 rt5 p50 p90 1 0 0.01 0.00 0.01 0.01 HTTP Requests ------------- 08:35:52.324 CESTGET / 200 OK
Dès lors, toute requête envoyée vers l'URL publique (https://e11a-90-19-213-214.ngrok-free.app
) sera redirigé vers votre URL locale (https://custom-url.loc:443
). Très pratique pour travailler avec les webhooks ! Mais il faut constamment gérer cette URL publique qui change à chaque lancement de la commande, hormis en prenant un plan payant de Ngrok.
Au sein d'une stack Docker toutefois, il est possible de se soustraire de cette limitation facilement avec le container officiel ngrok/ngrok:latest
. Voici ci-dessous un exemple d'une configuration compose.yaml
pour un container web exécuté en passant par un reverse proxy Traefik. C'est une bonne pratique que nous appliquons à tous nos projets, Traefik permet de gérer les terminaisons SSL et de gérer de multiples URL locales beaucoup plus aisément. Nous vous présenterons un article sur le sujet prochainement.
x-network:
- &network
networks:
- app_net
services:
app_web:
container_name: app_web
build:
context: .
target: dev
depends_on:
- app_reverse_proxy
volumes:
- .:/app:cached
labels:
- "traefik.enable=true"
- "traefik.docker.network=app_net"
- "traefik.http.routers.app_web.rule=Host(`custom-url.loc`)"
- "traefik.http.routers.app_web.tls=true"
- "traefik.http.services.app_web.loadbalancer.server.port=80"
<<: *network
app_tunnel:
container_name: app_tunnel
image: ngrok/ngrok:latest
command:
- "http"
- "--host-header=custom-url.loc"
- "app_web:80"
environment:
NGROK_AUTHTOKEN: **********
ports:
- 4040:4040
<<: *network
app_reverse_proxy:
restart: no
image: traefik:latest
container_name: app_reverse_proxy
security_opt:
- no-new-privileges:true
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./docker/traefik/config.yml:/etc/traefik/config.yml:ro
- ./docker/traefik/certs:/etc/certs:ro
<<: *network
networks:
app_net:
external: true
Lors du lancement de l'image app_tunnel
, une URL publique est mise à disposition par Ngrok comme précédemment, mais de manière transparente dans son propre container Docker. Dans votre container applicatif, il suffit par la suite de faire un appel à l'API de votre container Ngrok sur l'URL suivante : http://app_tunnel:4040/api/tunnels
. La réponse contiendra la liste des tunnels établis avec l'URL publique dédiée.
Pour illustrer ces propos, voici un exemple de service dans un projet Symfony pouvant faire office de passerelle avec votre container Ngrok :
<?php
/**
* (c) SAAS-Production <http://www.saas-production.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace App\Service\ThirdParty\Ngrok;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class NgrokService
{
public function __construct(
private readonly HttpClientInterface $httpClient,
) {
}
/**
* Get the exposed URL of the currently running docker ngrok client.
*
* @return ?string
*/
public function getExposedUrl(): ?string
{
$exposedUrl = null;
$response = $this->httpClient->request('GET', 'http://app_tunnel:4040/api/tunnels');
if ($response->getStatusCode() === 200) {
$responseData = $response->toArray();
if (!empty($responseData['tunnels'])) {
$exposedUrl = $responseData['tunnels'][0]['public_url'];
}
}
return $exposedUrl;
}
}
Ce service peut ensuite être utilisé pour les API tierces nécessitant de générer des URL de retour/callback comme par exemple pour Stripe avec son API Customer Portal Session. Au niveau des webhooks, selon les possibilités offertes par les tierces parties utilisées, il vous sera possible ou non de lancer des requêtes API pour mettre à jour l'URL des différents webhooks automatiquement. Si l'automatisation n'est pas possible, il vous faudra toujours aller faire cette opération manuellement. A noter que certains systèmes les plus avancés comme Stripe offrent des solutions clé en main pour vous faciliter la tâche (https://docs.stripe.com/webhooks) :
stripe listen --forward-to https://custom-url.loc/webhook/stripe --skip-verify
J'espère que cette petite astuce vous permettra de gagner du temps et d'optimiser vos processus de développement logiciel au sein de votre propre équipe ! N'hésitez pas à nous contacter si vous souhaitez un accompagnement pour mettre en place Docker, de l'intégration ou du déploiement continu au sein de votre organisation et accélérer vos déploiements et votre time to market.