Enregistrement & heartbeat
Au démarrage, le module se présente au core ; ensuite il prouve qu'il est vivant toutes les 30 secondes. Toutes ces routes /internal/* exigent l'en-tête X-Internal-Secret.
S'enregistrer #
async fn register_with_core(http: &reqwest::Client, s: &Settings) {
let payload = json!({
"module_id": "memo",
"display_name": "Memo",
"base_url": format!("http://{}:{}", s.server.host, s.server.port),
"version": env!("CARGO_PKG_VERSION"),
"routes": [{ "method": "*", "path": "/*" }],
"sidebar_items": [{ "id": "memo", "label": "Memo", "icon": "StickyNote",
"path": "/memo", "position": 30, "section": "main" }],
"subscribed_events": ["UserDeleted"],
});
let url = format!("{}/internal/modules/register", s.core.url);
let _ = http.post(&url)
.header("X-Internal-Secret", s.core.internal_secret.as_str())
.json(&payload)
.send().await; // en vrai : retry + backoff
}Le core répond 201 et garde l'instance (table core.module_instances) ; il proxifiera désormais /api/v1/memo/* vers votre port.
Le même appel POST, dans d'autres langages :
// Rust — reqwest
let core = std::env::var("KC__CORE__URL")?;
let secret = std::env::var("KC__CORE__INTERNAL_SECRET")?;
reqwest::Client::new()
.post(format!("{core}/internal/modules/register"))
.header("X-Internal-Secret", secret)
.json(&serde_json::json!({
"module_id": "demo",
"base_url": "http://127.0.0.1:3120",
"version": "0.1.0"
}))
.send().await?;<?php
// PHP — cURL
$core = getenv('KC__CORE__URL');
$secret = getenv('KC__CORE__INTERNAL_SECRET');
$ch = curl_init("$core/internal/modules/register");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-Internal-Secret: $secret",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"module_id" => "demo",
"base_url" => "http://127.0.0.1:3120",
"version" => "0.1.0",
]),
]);
curl_exec($ch);# Python — requests
import os, requests
core = os.environ["KC__CORE__URL"]
secret = os.environ["KC__CORE__INTERNAL_SECRET"]
requests.post(
f"{core}/internal/modules/register",
headers={"X-Internal-Secret": secret},
json={
"module_id": "demo",
"base_url": "http://127.0.0.1:3120",
"version": "0.1.0",
},
)// Go — net/http
core := os.Getenv("KC__CORE__URL")
secret := os.Getenv("KC__CORE__INTERNAL_SECRET")
body, _ := json.Marshal(map[string]string{
"module_id": "demo",
"base_url": "http://127.0.0.1:3120",
"version": "0.1.0",
})
req, _ := http.NewRequest("POST", core+"/internal/modules/register", bytes.NewReader(body))
req.Header.Set("X-Internal-Secret", secret)
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)# Perl — LWP::UserAgent
use LWP::UserAgent;
use JSON::PP;
my $core = $ENV{'KC__CORE__URL'};
my $secret = $ENV{'KC__CORE__INTERNAL_SECRET'};
my $ua = LWP::UserAgent->new;
$ua->post("$core/internal/modules/register",
'X-Internal-Secret' => $secret,
'Content-Type' => 'application/json',
Content => encode_json({
module_id => "demo",
base_url => "http://127.0.0.1:3120",
version => "0.1.0",
}),
);Heartbeat & auto-réparation #
fn spawn_heartbeat(http: reqwest::Client, s: Settings) {
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(30)).await;
let url = format!("{}/internal/modules/memo/heartbeat", s.core.url);
match http.post(&url).header("X-Internal-Secret", s.core.internal_secret.as_str()).send().await {
Ok(r) if r.status().is_success() => {}
Ok(r) if r.status() == StatusCode::NOT_FOUND => register_with_core(&http, &s).await, // ré-enregistrement
_ => tracing::warn!("heartbeat: réponse inattendue"),
}
}
});
}Note
Un heartbeat qui reçoit 404 signifie que le core a oublié le module (redémarrage) → on se ré-enregistre automatiquement. Un 403 signifie « module désactivé » par l'admin.