Tutoriel backend — « Memo »

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.