Chaque application web fait des requêtes HTTP. Un tableau de bord SaaS récupère des analyses depuis une API. Un site e-commerce facture des cartes via Stripe. Une application sociale tire des profils utilisateurs depuis un backend. HTTP est le tissu connectif du web moderne, et pourtant la plupart des langages de programmation le traitent comme une arrière-pensée -- quelque chose que vous gérez avec une bibliothèque tierce.
En JavaScript, l'API native fetch est arrivée des années après le lancement de Node.js, et elle manque encore de fonctionnalités comme les retries automatiques et les timeouts de requête (le contournement par AbortController n'est l'idée d'élégance de personne). En Python, le urllib de la bibliothèque standard est si pénible que requests est devenu le package le plus téléchargé sur PyPI. En Go, le client net/http intégré est correct mais verbeux -- un simple POST avec corps JSON prend 15 lignes.
FLIN inclut un client HTTP complet comme fonctionnalité intégrée. Les Sessions 063 et 064 l'ont conçu et implémenté. Cinq fonctions. Zéro import. Gestion JSON automatique. Timeouts et retries configurables. Tout ce dont un développeur web a besoin pour s'intégrer avec n'importe quelle API.
Les cinq fonctions
flinhttp_get(url)
http_get(url, options)
http_post(url, options)
http_put(url, options)
http_patch(url, options)
http_delete(url)
http_delete(url, options)Cinq fonctions pour cinq méthodes HTTP. C'est toute la surface d'API. Chaque fonction prend une URL et un map d'options optionnel, fait la requête et retourne un objet de réponse. Pas de construction d'objets de requête. Pas de configuration de chaînes de middleware. Pas d'import d'adaptateurs.
Requêtes GET : le cas simple
L'opération HTTP la plus courante est de récupérer des données. En FLIN, c'est une ligne :
flinresponse = http_get("https://api.example.com/users")L'objet response contient tout ce dont vous avez besoin :
flinresponse.status // 200
response.ok // true (statut 200-299)
response.headers // Map des en-têtes de réponse
response.body // Corps brut de la réponse (texte)
response.json // JSON analysé (si Content-Type est application/json)response.ok est un booléen qui est true quand le code de statut est dans la plage 200-299. Cela élimine le bug le plus courant dans le code client HTTP : oublier de vérifier le code de statut. En JavaScript, fetch se résout avec succès même pour les réponses 404 et 500 -- un choix de conception qui a causé des millions de bugs. En FLIN, response.ok rend la vérification explicite et évidente.
response.json analyse paresseusement le corps de la réponse en JSON. Si le corps n'est pas du JSON valide, il retourne none au lieu de lancer une erreur. Cela signifie que vous pouvez y accéder en toute sécurité sans bloc try-catch :
flinresponse = http_get("https://api.example.com/users")
{if response.ok}
users = response.json
{if users != none}
{for user in users}
<Text>{user["name"]}</Text>
{/for}
{else}
<Alert type="warning">Invalid response format</Alert>
{/if}
{else}
<Alert type="danger">Request failed: {response.status}</Alert>
{/if}Pas de try-catch. Pas de callbacks d'erreur. Pas de chaînes de promesses. Juste des vérifications conditionnelles sur des valeurs qui sont toujours présentes.
Requêtes POST : envoyer des données
Les requêtes POST sont là où la gestion JSON automatique de FLIN brille :
flinresponse = http_post("https://api.example.com/users", {
body: {
name: "Juste Gnimavo",
email: "[email protected]",
role: "admin"
}
})Quand le body est un map (ou une entité), FLIN le sérialise automatiquement en JSON et définit l'en-tête Content-Type: application/json. Vous n'avez pas besoin d'appeler JSON.stringify. Vous n'avez pas besoin de définir les en-têtes manuellement. Le cas courant -- envoyer du JSON à une API REST -- est le comportement par défaut.
Si vous avez besoin d'un type de contenu différent, vous pouvez le remplacer :
flin// Données de formulaire
response = http_post("https://api.example.com/login", {
body: "username=juste&password=secret",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
})
// Texte brut
response = http_post("https://api.example.com/webhook", {
body: "raw payload text",
headers: {
"Content-Type": "text/plain"
}
})La règle est simple : si vous fournissez un map comme corps, FLIN envoie du JSON. Si vous fournissez une chaîne, FLIN l'envoie telle quelle avec le Content-Type que vous spécifiez.
En-têtes d'authentification
La plupart des appels API nécessitent une authentification. Les fonctions HTTP de FLIN acceptent un map d'en-têtes :
flintoken = env("API_TOKEN")
response = http_get("https://api.example.com/me", {
headers: {
"Authorization": "Bearer {token}"
}
})
user = response.json
print("Logged in as: {user['name']}")L'interpolation de chaînes fonctionne à l'intérieur des valeurs d'en-têtes, vous pouvez donc intégrer des variables directement. La fonction env() lit les variables d'environnement -- une autre fonction intégrée qui élimine une dépendance.
Timeouts et retries
Les applications de production ont besoin de timeouts (pour qu'une API lente ne bloque pas toute votre application) et de retries (pour que les erreurs réseau transitoires n'échouent pas définitivement). Les deux sont intégrés dans les options :
flinresponse = http_post("https://api.payment.com/charge", {
body: { amount: 2500, currency: "XOF" },
timeout: 30.seconds,
retry: 3
})timeout accepte une valeur de durée (utilisant la syntaxe naturelle de durée de FLIN). Si la requête prend plus longtemps que la durée spécifiée, elle échoue avec une erreur de timeout et response.ok est false.
retry spécifie combien de fois retenter une requête échouée. Les retries utilisent un backoff exponentiel : premier retry après 1 seconde, deuxième après 2 secondes, troisième après 4 secondes. Seules les erreurs réseau et les réponses 5xx déclenchent des retries -- les réponses 4xx (erreurs client) ne sont pas retentées parce qu'elles échoueraient à nouveau avec le même payload.
Exemple concret : intégration d'API tierce
Voici un exemple complet d'intégration avec une API de paiement :
flinfn charge_customer(customer_id: text, amount: int, currency: text) {
response = http_post("https://api.payment.com/v1/charges", {
body: {
customer: customer_id,
amount: amount,
currency: currency,
description: "Order #{uuid().slice(0, 8)}"
},
headers: {
"Authorization": "Bearer {env('PAYMENT_SECRET_KEY')}",
"Idempotency-Key": uuid()
},
timeout: 30.seconds,
retry: 2
})
{if response.ok}
charge = response.json
return { success: true, charge_id: charge["id"] }
{else}
error = response.json
log_error("Payment failed: {response.status} - {error['message']}")
return { success: false, error: error["message"] }
{/if}
}
// Utilisation dans une vue
result = charge_customer("cus_abc123", 5000, "XOF")
{if result.success}
<Alert type="success">Payment processed: {result.charge_id}</Alert>
{else}
<Alert type="danger">Payment failed: {result.error}</Alert>
{/if}C'est une intégration de paiement prête pour la production en 30 lignes. Elle gère l'authentification, les clés d'idempotence, les timeouts, les retries, les réponses d'erreur et le feedback utilisateur. Pas de bibliothèque HTTP. Pas de bibliothèque d'analyse JSON. Pas de bibliothèque de variables d'environnement. Tout est intégré.
Implémentation : reqwest sous le capot
Le client HTTP de FLIN est construit sur le crate reqwest de Rust, le client HTTP le plus populaire de l'écosystème Rust. reqwest utilise hyper pour le support HTTP/1.1 et HTTP/2, rustls pour TLS et tokio pour l'I/O asynchrone.
Les fonctions intégrées sont de minces enveloppes qui traduisent les valeurs FLIN en appels reqwest :
rustfn builtin_http_post(vm: &mut Vm, args: &[Value]) -> Result<Value, VmError> {
let url = vm.get_string(args[0])?;
let options = if args.len() > 1 { vm.get_map(args[1])? } else { Map::new() };
let client = reqwest::blocking::Client::builder()
.timeout(extract_timeout(&options))
.build()
.map_err(|e| VmError::HttpError(e.to_string()))?;
let mut request = client.post(url);
// Définir les en-têtes
if let Some(headers) = options.get("headers") {
for (key, value) in vm.get_map(headers)?.iter() {
request = request.header(key, value);
}
}
// Définir le corps (auto-sérialiser les maps en JSON)
if let Some(body) = options.get("body") {
match body {
Value::Map(_) => request = request.json(&serialize_to_json(vm, body)?),
Value::String(s) => request = request.body(s.clone()),
_ => request = request.body(vm.value_to_string(body)?),
}
}
// Exécuter avec logique de retry
let response = execute_with_retry(request, extract_retry_count(&options))?;
// Construire le map de réponse
Ok(build_response_value(vm, response))
}Le client bloquant est utilisé parce que la VM de FLIN est actuellement mono-thread. Le support HTTP asynchrone est prévu pour une version future, où http_get deviendra une opération asynchrone sur laquelle la VM peut yield en attendant la réponse.
Ce que nous avons intentionnellement exclu
Le client HTTP est délibérément simple. Nous avons exclu les fonctionnalités qui ajoutent de la complexité sans bénéficier à 90 % des cas d'utilisation :
Conteneurs de cookies. La gestion automatique des cookies entre requêtes ajoute un état difficile à déboguer. Si vous avez besoin de cookies, passez-les comme en-têtes explicitement.
Téléchargement de fichiers. Les données de formulaire multipart sont complexes et rarement nécessaires dans la communication API-vers-API. Les téléchargements de fichiers en FLIN sont gérés par le système de vues et le système d'entités, pas par le client HTTP.
Support WebSocket. Les WebSockets sont un protocole différent avec une sémantique différente. Ils seront une fonctionnalité intégrée séparée (ws_connect) quand ils seront implémentés.
Intercepteurs de requêtes. Les chaînes de middleware pour la journalisation, l'authentification et la transformation sont un anti-pattern dans un langage qui valorise l'explicite. Si vous devez ajouter un en-tête d'auth à chaque requête, écrivez une fonction wrapper. C'est cinq lignes, c'est évident, et cela ne nécessite pas de comprendre un pipeline de middleware.
Réponses en streaming. Les événements envoyés par le serveur et les réponses en streaming nécessitent un support asynchrone. Ils sont prévus pour la version du runtime asynchrone.
Le client HTTP fait une seule chose bien : faire des requêtes HTTP synchrones avec des corps JSON et retourner des réponses structurées. Pour 90 % des appels API d'applications web, c'est exactement ce dont vous avez besoin.
Cinq fonctions, zéro dépendance
Le client HTTP de FLIN remplace :
- JavaScript : API
fetch+AbortController+JSON.stringify+ boilerplate de gestion d'erreur - Python : bibliothèque
requests(31 millions de téléchargements par semaine) - Go :
net/http+json.Marshal+ioutil.ReadAll - Ruby :
net/httpoufaradayouhttparty
Cinq fonctions. Un format d'options. Un format de réponse. Chaque méthode HTTP qu'une application web utilise, disponible dès la première ligne de code sans aucune configuration.
Ceci est la partie 75 de la série "How We Built FLIN", documentant comment un CEO à Abidjan et un CTO IA ont construit un client HTTP dans un langage de programmation.
Navigation de la série : - [74] Time and Timezone Functions - [75] HTTP Client Built Into the Language (vous êtes ici) - [76] Security Functions: Crypto, JWT, Argon2 - [77] Introspection and Reflection at Runtime