mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 04:10:04 +02:00
Lot 35 : routes de base de la façade aussi par le tunnel du relais
Les neuf routes que la façade sert toujours (ping, status, config, ligne de commande du moteur, couches du modèle, chemins, RAM, VRAM, journal du service) arrivent désormais à la façade par le tunnel du relais aussi : withFacadeChat les lui remet dès qu'elle tourne (facade_core.go). Les handlers du Go ne servent plus que sans façade (LOKI_FACADE=0). Côté Rust, une requête relayée par le Go (jeton interne) n'est pas réauthentifiée, comme pour les routes des lots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
8 files changed
+186
-9
No files matched your search
+48
@@ -0,0 +1,48 @@
|
||||
# Lot 35 — Routes de base par le tunnel du relais
|
||||
|
||||
## Contexte
|
||||
|
||||
La façade sert toujours neuf routes, sans variable (`api.rs`) :
|
||||
|
||||
- `/api/ping` ;
|
||||
- `/api/status` ;
|
||||
- `/api/config` ;
|
||||
- `/api/engine/cmdline` ;
|
||||
- `/api/model/layers` ;
|
||||
- `/api/paths` ;
|
||||
- `/api/ram` ;
|
||||
- `/api/vram` ;
|
||||
- `/api/service/log`.
|
||||
|
||||
Mais le tunnel du relais (accès distant, E2E) sert le mux du Go tel quel.
|
||||
`withFacadeChat` ne remet à la façade que les routes des lots. Un utilisateur
|
||||
distant tombait donc encore sur les handlers Go de ces neuf routes : deux
|
||||
implémentations à garder d'accord.
|
||||
|
||||
## Le lot
|
||||
|
||||
- Go (`facade_core.go`) : une table des routes de base, remises à la façade
|
||||
par `withFacadeChat` dès qu'elle tourne (adresse et jeton interne
|
||||
présents), sans variable. Façade muette : 502 « façade indisponible ».
|
||||
Sans façade (`LOKI_FACADE=0`), le Go les sert lui-même.
|
||||
- Rust (`api.rs`) : une requête relayée par le Go (jeton interne) a déjà été
|
||||
authentifiée par lui. Ni clé ni hôte local ne sont exigés de nouveau,
|
||||
comme pour les routes des lots ; la version du Go est lue quand même pour
|
||||
`/api/ping`.
|
||||
|
||||
## Tests
|
||||
|
||||
- Go (`facade_core_test.go`) :
|
||||
- les neuf routes relayées avec le jeton ;
|
||||
- une route du Go gardée ;
|
||||
- façade muette ;
|
||||
- sans façade, servies ici.
|
||||
- Rust (`tests/lot2.rs`) :
|
||||
- relayée par le Go avec un hôte distant et une clé posée : servie par la
|
||||
façade, pas renvoyée au Go ;
|
||||
- sans jeton, ou avec un faux : refusée.
|
||||
|
||||
## Reste à faire
|
||||
|
||||
L'interface statique et `/v1` passent encore par le Go sur le tunnel. Ses
|
||||
handlers sont gardés pour `LOKI_FACADE=0`.
|
||||
@@ -142,6 +142,8 @@ func withFacadeChat(path string, h http.HandlerFunc) http.HandlerFunc {
|
||||
held, unavailable = facadeBench, "banc d'essai indisponible : "
|
||||
case facadeTuneRoutes[path]:
|
||||
held, unavailable = facadeTune, "optimiseur indisponible : "
|
||||
case facadeCoreRoutes[path]:
|
||||
held, unavailable = facadeCore, "façade indisponible : "
|
||||
default:
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ func TestFacadeRelaieLesModeles(t *testing.T) {
|
||||
t.Fatalf("reçu par la façade : %v", got)
|
||||
}
|
||||
// Le chat, lui, reste ici (LOKI_CHAT absent), et les routes voisines aussi.
|
||||
for _, p := range []string{"/api/chat/send", "/api/model/layers", "/api/presets"} {
|
||||
for _, p := range []string{"/api/chat/send", "/api/mem/health", "/api/presets"} {
|
||||
w := httptest.NewRecorder()
|
||||
withFacadeChat(p, local)(w, httptest.NewRequest("POST", p, nil))
|
||||
if w.Body.String() != "go" {
|
||||
@@ -1125,9 +1125,9 @@ func TestFacadeMoteurImage(t *testing.T) {
|
||||
t.Fatalf("relais : %v", calls)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
withFacadeChat("/api/engine/cmdline", local)(w, httptest.NewRequest("GET", "/api/engine/cmdline", nil))
|
||||
withFacadeChat("/api/llamacpp/install", local)(w, httptest.NewRequest("POST", "/api/llamacpp/install", nil))
|
||||
if w.Body.String() != "go" {
|
||||
t.Fatalf("/api/engine/cmdline doit rester au Go : %q", w.Body.String())
|
||||
t.Fatalf("/api/llamacpp/install doit rester au Go : %q", w.Body.String())
|
||||
}
|
||||
|
||||
// Sans jeton interne, rien ne part.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package loki
|
||||
|
||||
// facade_core.go — les routes de base que la façade sert toujours (api.rs,
|
||||
// lot 35 de la migration) : santé, état, configuration, ligne de commande du
|
||||
// moteur, mémoire vive et vidéo, journal du service, chemins, couches du
|
||||
// modèle. Quand elles arrivent par le tunnel du relais (accès distant, E2E),
|
||||
// le Go les lui remet aussi, comme les routes des lots : ses propres
|
||||
// handlers ne servent plus que sans façade (LOKI_FACADE=0).
|
||||
|
||||
var facadeCoreRoutes = map[string]bool{
|
||||
"/api/ping": true,
|
||||
"/api/status": true,
|
||||
"/api/config": true,
|
||||
"/api/engine/cmdline": true,
|
||||
"/api/model/layers": true,
|
||||
"/api/paths": true,
|
||||
"/api/ram": true,
|
||||
"/api/vram": true,
|
||||
"/api/service/log": true,
|
||||
}
|
||||
|
||||
// facadeCore : une façade tourne devant ce processus (son adresse et le
|
||||
// jeton interne sont là).
|
||||
func facadeCore() bool {
|
||||
return facadeAddr() != "" && internalToken() != ""
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Lot 35 : les routes de base arrivent à la façade par le tunnel du relais
|
||||
// dès qu'elle tourne, sans variable ; sans façade (LOKI_FACADE=0 : ni
|
||||
// adresse ni jeton), le Go les sert lui-même.
|
||||
func TestFacadeRelaieLesRoutesDeBase(t *testing.T) {
|
||||
testHome(t)
|
||||
f := demarrerFacade(t)
|
||||
t.Setenv(facadeChatEnv, "")
|
||||
local := func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "go") }
|
||||
for p := range facadeCoreRoutes {
|
||||
w := httptest.NewRecorder()
|
||||
withFacadeChat(p, local)(w, httptest.NewRequest("GET", p, nil))
|
||||
if w.Body.String() != f.reply {
|
||||
t.Fatalf("%s : réponse %q", p, w.Body.String())
|
||||
}
|
||||
}
|
||||
if got := f.recu(); len(got) != len(facadeCoreRoutes) || !strings.HasSuffix(strings.TrimSpace(got[0]), "jeton") {
|
||||
t.Fatalf("reçu par la façade : %v", got)
|
||||
}
|
||||
// Une route que le Go garde : ici.
|
||||
w := httptest.NewRecorder()
|
||||
withFacadeChat("/api/mem/health", local)(w, httptest.NewRequest("GET", "/api/mem/health", nil))
|
||||
if w.Body.String() != "go" {
|
||||
t.Fatalf("route du Go relayée : %q", w.Body.String())
|
||||
}
|
||||
// Façade muette : 502 qui le dit.
|
||||
f.srv.Close()
|
||||
w = httptest.NewRecorder()
|
||||
withFacadeChat("/api/status", local)(w, httptest.NewRequest("GET", "/api/status", nil))
|
||||
if w.Code != http.StatusBadGateway || !strings.Contains(w.Body.String(), "façade indisponible : ") {
|
||||
t.Fatalf("façade muette : %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
// Sans façade : le Go sert.
|
||||
t.Setenv(facadeAddrEnv, "")
|
||||
w = httptest.NewRecorder()
|
||||
withFacadeChat("/api/status", local)(w, httptest.NewRequest("GET", "/api/status", nil))
|
||||
if w.Body.String() != "go" {
|
||||
t.Fatalf("sans façade : %q", w.Body.String())
|
||||
}
|
||||
}
|
||||
+14
-4
@@ -136,11 +136,21 @@ pub fn handles(_method: &Method, path: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn serve(ctx: &Ctx, req: Request<Incoming>) -> Response<Body> {
|
||||
/// `from_go` : relayée par le Go (tunnel du relais), qui l'a déjà
|
||||
/// authentifiée.
|
||||
pub async fn serve(ctx: &Ctx, req: Request<Incoming>, from_go: bool) -> Response<Body> {
|
||||
let (parts, _body) = req.into_parts();
|
||||
let version = match ctx.guard(&parts.headers).await {
|
||||
Ok(v) => v,
|
||||
Err(refus) => return *refus,
|
||||
let version = if from_go {
|
||||
ctx.internal
|
||||
.auth()
|
||||
.await
|
||||
.map(|i| i.version)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
match ctx.guard(&parts.headers).await {
|
||||
Ok(v) => v,
|
||||
Err(refus) => return *refus,
|
||||
}
|
||||
};
|
||||
match parts.uri.path() {
|
||||
// Vérifie la connectivité ET la clé (200 = bonne clé, 401 = mauvaise).
|
||||
|
||||
@@ -266,7 +266,7 @@ impl Proxy {
|
||||
return oai::serve(&self.ctx, &self.engine, req).await;
|
||||
}
|
||||
if api::handles(req.method(), path) {
|
||||
return api::serve(&self.ctx, req).await;
|
||||
return api::serve(&self.ctx, req, from_go).await;
|
||||
}
|
||||
if let Some(chat) = &self.ctx.chat
|
||||
&& crate::chatx::handles(path)
|
||||
|
||||
@@ -76,6 +76,12 @@ struct Banc {
|
||||
}
|
||||
|
||||
async fn banc(cle: Option<&str>) -> Banc {
|
||||
banc_avec(cle, false).await
|
||||
}
|
||||
|
||||
/// `tenu` : la façade tient au moins un groupe de routes (le Go lui en
|
||||
/// relaie alors par le tunnel du relais).
|
||||
async fn banc_avec(cle: Option<&str>, tenu: bool) -> Banc {
|
||||
let etat = Arc::new(FauxGo::default());
|
||||
*etat.cle.lock().unwrap() = cle.map(String::from);
|
||||
let backend = faux_go(etat.clone()).await;
|
||||
@@ -96,7 +102,7 @@ async fn banc(cle: Option<&str>) -> Banc {
|
||||
chat: None,
|
||||
models: None,
|
||||
dictate: None,
|
||||
settings: false,
|
||||
settings: tenu,
|
||||
files: None,
|
||||
push: None,
|
||||
mem: None,
|
||||
@@ -235,3 +241,39 @@ async fn ui_servie_par_rust() {
|
||||
"seule la police inconnue doit atteindre le Go : {vus:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Lot 35 : les routes de base arrivent aussi par le tunnel du relais, que
|
||||
/// le Go a déjà authentifiées (jeton interne) : ni clé ni hôte local exigés.
|
||||
/// Sans le jeton, la règle de toujours.
|
||||
#[tokio::test]
|
||||
async fn routes_de_base_relayees_par_le_go() {
|
||||
let b = banc_avec(Some("secret"), true).await;
|
||||
let (code, _, body) = get(
|
||||
&b,
|
||||
"/api/ping",
|
||||
&[("host", "loki.distant.example"), ("x-loki-internal", JETON)],
|
||||
)
|
||||
.await;
|
||||
assert_eq!(code, StatusCode::OK, "{}", String::from_utf8_lossy(&body));
|
||||
assert!(
|
||||
b.etat
|
||||
.vus
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.all(|(p, _)| p != "/api/ping"),
|
||||
"servie par la façade, pas relayée au Go"
|
||||
);
|
||||
let (code, _, _) = get(&b, "/api/ping", &[("host", "loki.distant.example")]).await;
|
||||
assert_ne!(code, StatusCode::OK);
|
||||
let (code, _, _) = get(
|
||||
&b,
|
||||
"/api/ping",
|
||||
&[
|
||||
("host", "loki.distant.example"),
|
||||
("x-loki-internal", "faux"),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
assert_ne!(code, StatusCode::OK);
|
||||
}
|
||||
Reference in new issue
Block a user