225 lines
9.0 KiB
Markdown
225 lines
9.0 KiB
Markdown
|
|
# Évaluation du plan : centraliser_base_url_axum_middleware
|
||
|
|
|
||
|
|
## Résumé de l'audit
|
||
|
|
|
||
|
|
Le plan est **bien pensé et cohérent**. Il identifie correctement le problème et la solution. Cependant, j'ai identifié plusieurs points nécessitant des amendements.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Points validés (conformes au code actuel)
|
||
|
|
|
||
|
|
1. **Problème bien identifié** : URLs hardcodées avec IP locale (`PMO_SERVER_URL`) retournées au frontend via reverse proxy.
|
||
|
|
|
||
|
|
2. **`get_request_base_url` existe déjà** à `pmoserver/src/lib.rs:199` — pas besoin de la recréer.
|
||
|
|
|
||
|
|
3. **`covers_route_for` existe déjà** dans `pmocache/src/lib.rs:149`.
|
||
|
|
|
||
|
|
4. **`covers_absolute_url_for` utilisée dans les contextes UPnP** :
|
||
|
|
- `pmoupnp/src/cache_registry.rs:57`
|
||
|
|
- `pmoradiofrance/src/metadata_cache.rs:263`
|
||
|
|
- `pmoparadise/src/source.rs:216`
|
||
|
|
- `pmoaudio-ext/src/sinks/streaming_icyflac_sink.rs:91`
|
||
|
|
|
||
|
|
5. **Route audio correcte** : `/audio/tracks/{pk}` (pas `/audio/flac/{pk}`).
|
||
|
|
|
||
|
|
6. **Architecture du Server** : Les routes sont construites dynamiquement via `Arc<RwLock<Router>>`. Le layer devra être ajouté dans la construction du router, pas après.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Points à amender
|
||
|
|
|
||
|
|
### 1. Ajout du layer dans le Server
|
||
|
|
|
||
|
|
Le plan suggère d'ajouter le layer "dans `server.rs`" mais la structure du router est complexe :
|
||
|
|
- Les routes sont dynamiques (`RwLock<Router>`)
|
||
|
|
- Le router final est un fallback qui délègue
|
||
|
|
|
||
|
|
**Correction** : Ajouter le layer directement lors de la création du `registry_route` initial (ligne 120-122) :
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let registry_route = Router::new()
|
||
|
|
.route("/api/registry", get(get_api_registry))
|
||
|
|
.with_state(api_registry.clone())
|
||
|
|
.layer(base_url_layer()); // ← ici
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Comportement requis pour LAN vs WAN
|
||
|
|
|
||
|
|
Le middleware doit supporter les deux cas d'usage :
|
||
|
|
|
||
|
|
- **LAN (sans reverse proxy)** : Pas de headers `X-Forwarded-*` → utiliser l'adresse IP locale du serveur (`PMO_SERVER_URL`)
|
||
|
|
- **WAN (via reverse proxy)** : Headers `X-Forwarded-*` présents → utiliser l'URL publique du reverse proxy
|
||
|
|
|
||
|
|
**Important** : `get_request_base_url` dans `pmoserver/src/lib.rs:199` lit déjà ces headers. Le fallback doit être `PMO_SERVER_URL` qui est configuré au démarrage avec l'IP locale.
|
||
|
|
|
||
|
|
### 3. Chemin du middleware dans la pile
|
||
|
|
|
||
|
|
Le plan dit d'appliquer le layer "avant" les autres. En réalité, Tower/Acorn applique les couches dans l'ordre où elles sont ajoutées — le premier layer ajouté est le plus extérieur (exécuté en premier). Le `base_url_layer` doit donc être ajouté en **premier** (le plus intérieur) pour voir les headers nettoyés.
|
||
|
|
|
||
|
|
### 3. Les handlers n'ont PAS besoin de BaseUrl
|
||
|
|
|
||
|
|
Après analyse, **aucun handler** dans le codebase actuel n'appelle `covers_absolute_url_for()` directement pour le frontend. Les `album_art_uri` sont :
|
||
|
|
- Soit **propagés** depuis les réponses UPnP des media servers (pas des URLs pmomusic)
|
||
|
|
- Soit **construits en tâche de fond** dans les caches (RadioFrance, RadioParadise)
|
||
|
|
|
||
|
|
**Correction** : Le plan surestime le nombre de handlers à modifier. La vraie question est : d'où viennent les URLs incorrectes ?
|
||
|
|
|
||
|
|
### 4. Source du problème à clarifier
|
||
|
|
|
||
|
|
Les URLs incorrectes ne viennent pas des handlers REST classiques. Elles viennent probablement de :
|
||
|
|
|
||
|
|
**a) Tâches de fond** (background tasks) qui stockent des URLs complètes :
|
||
|
|
- `pmoradiofrance/src/metadata_cache.rs:263` — construit `covers_absolute_url_for()` dans le cache
|
||
|
|
- `pmoparadise/src/source.rs:216` — même problème
|
||
|
|
|
||
|
|
**b) API Qobuz** (`pmoqobuz/src/api_rest.rs:302`) — utilise `covers_route_for` (route relative, OK)
|
||
|
|
|
||
|
|
**c) Playlist** (`pmoplaylist/src/handle/read.rs:204,271,355`) — utilise `covers_route_for` (OK)
|
||
|
|
|
||
|
|
### 5. Correction du fallback
|
||
|
|
|
||
|
|
Le plan suggère `localhost:8080` ou `0.0.0.0:8080` comme fallback. Le port doit provenir de la configuration du serveur (`get_server_base_url()` existe déjà dans `pmoserver/src/lib.rs`).
|
||
|
|
|
||
|
|
**Correction** : Le fallback utilise `get_server_base_url()` (disponible via `GLOBAL_SERVER`) :
|
||
|
|
- En LAN : pas de `X-Forwarded-*` → `get_server_base_url()` → URLs en IP locale
|
||
|
|
- En WAN : `X-Forwarded-*` présents → URLs en URL publique du reverse proxy
|
||
|
|
|
||
|
|
### 6. Fonction `audio_route_for` pas nécessaire maintenant
|
||
|
|
|
||
|
|
Le plan propose d'ajouter `audio_route_for` dans `pmoaudiocache`. Mais :
|
||
|
|
- Les fichiers audio sont servis par `pmoaudiocache` lui-même (routes internes)
|
||
|
|
-Aucune URL audio n'est retournée au frontend via JSON
|
||
|
|
|
||
|
|
**Supprimer** cette étape du plan.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Plan amendé
|
||
|
|
|
||
|
|
### Étape 0 — Audit spécifique (à faire avant implémentation)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Trouver les constructions d'URLs dans les tâches de fond (caches, sources)
|
||
|
|
grep -rn "covers_absolute_url_for\|PMO_SERVER_URL" --include="*.rs" | grep -v "pmocontrol\|pmoplaylist\|pmoqobuz"
|
||
|
|
|
||
|
|
# Vérifier les URLs dans les réponses JSON des handlers
|
||
|
|
grep -rn "album_art_uri" --include="*.rs" | grep -E "fn |->"
|
||
|
|
```
|
||
|
|
|
||
|
|
Identifier spécifiquement quels endpoints REST retournent des URLs au frontend.
|
||
|
|
|
||
|
|
### Étape 1 — `pmoserver/src/lib.rs` : Ajouter `BaseUrl` + middleware
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use axum::{extract::Request, middleware::Next, response::Response};
|
||
|
|
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct BaseUrl(pub String);
|
||
|
|
|
||
|
|
impl BaseUrl {
|
||
|
|
pub fn url_for(&self, route: &str) -> String {
|
||
|
|
debug_assert!(route.starts_with('/'), "route must start with '/'");
|
||
|
|
format!("{}{}", self.0.trim_end_matches('/'), route)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn base_url_middleware(mut request: Request, next: Next) -> Response {
|
||
|
|
// Priorité : 1) X-Forwarded-* (reverse proxy), 2) get_server_base_url() (adresse configurée)
|
||
|
|
let base = get_request_base_url(request.headers())
|
||
|
|
.or_else(|| get_server_base_url())
|
||
|
|
.unwrap_or_else(|| {
|
||
|
|
panic!(
|
||
|
|
"BaseUrl: impossible de déterminer l'URL de base.\n\
|
||
|
|
Configurer PMO_SERVER_URL ou démarrer le serveur avant les handlers HTTP."
|
||
|
|
);
|
||
|
|
});
|
||
|
|
tracing::debug!("BaseUrl calculée : {}", base);
|
||
|
|
request.extensions_mut().insert(BaseUrl(base));
|
||
|
|
next.run(request).await
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn base_url_layer() -> axum::middleware::FromFnLayer {
|
||
|
|
axum::middleware::from_fn(base_url_middleware)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Comportement** :
|
||
|
|
- Accès LAN (pas de proxy) : `get_server_base_url()` → URLs en IP locale configurée
|
||
|
|
- Accès WAN (reverse proxy) : `X-Forwarded-*` → URLs en URL publique
|
||
|
|
|
||
|
|
**Note** : Si ni les headers ni le serveur ne sont disponibles, le middleware panic (fail-fast) car c'est une erreur de configuration.
|
||
|
|
|
||
|
|
### Étape 2 — `pmoserver/src/server.rs` : Appliquer le layer
|
||
|
|
|
||
|
|
Dans `Server::new()`, ligne ~120-122 :
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let registry_route = Router::new()
|
||
|
|
.route("/api/registry", get(get_api_registry))
|
||
|
|
.with_state(api_registry.clone())
|
||
|
|
.layer(base_url_layer()); // ← Ajouter ici (couche la plus intérieure)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Étape 3 — `pmocache/src/lib.rs` : Renommer sans déprecation
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Rename direct - pas de déprecation (soft en cours de dev, pas une library)
|
||
|
|
pub fn covers_absolute_url_for_upnp(pk: &str, param: Option<&str>) -> String {
|
||
|
|
// PMO_SERVER_URL contient l'IP locale (LAN) - utilisé uniquement pour UPnP
|
||
|
|
// Fallback sur get_server_base_url() si dispo, sinon erreur
|
||
|
|
let base = std::env::var("PMO_SERVER_URL")
|
||
|
|
.or_else(|_| pmoserver::get_server_base_url().ok_or("PMO_SERVER_URL not set"))
|
||
|
|
.unwrap_or_else(|e| {
|
||
|
|
tracing::error!("covers_absolute_url_for_upnp: {}", e);
|
||
|
|
panic!("BaseUrl non disponible pour UPnP");
|
||
|
|
});
|
||
|
|
format!("{}{}", base.trim_end_matches('/'), covers_route_for(pk, param))
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Étape 4 — Mettre à jour les appels UPnP
|
||
|
|
|
||
|
|
```bash
|
||
|
|
grep -rn "covers_absolute_url_for" --include="*.rs"
|
||
|
|
```
|
||
|
|
|
||
|
|
Modifier `pmoupnp/src/cache_registry.rs:57` → `covers_absolute_url_for_upnp`
|
||
|
|
|
||
|
|
### Étape 5 — Tâches de fond : stocker la route, pas l'URL
|
||
|
|
|
||
|
|
**pmoradiofrance/src/metadata_cache.rs:263** :
|
||
|
|
```rust
|
||
|
|
// Avant :
|
||
|
|
let public_url = pmocache::covers_absolute_url_for(&pk, None);
|
||
|
|
|
||
|
|
// Après : stocker la route relative
|
||
|
|
let album_art_route = pmocache::covers_route_for(&pk, None);
|
||
|
|
```
|
||
|
|
|
||
|
|
Le handler REST qui retourne ces métadonnées devra extraire `Extension<BaseUrl>` et appliquer `base_url.url_for()`.
|
||
|
|
|
||
|
|
**pmoparadise/src/source.rs:216** : Même traitement.
|
||
|
|
|
||
|
|
### Étape 6 — Vérification et tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Plus d'appels à covers_absolute_url_for dans les contextes HTTP
|
||
|
|
grep -rn "covers_absolute_url_for" --include="*.rs" | grep -v "pmocache\|pmoupnp"
|
||
|
|
|
||
|
|
# Tests du middleware
|
||
|
|
cargo test base_url
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Questions en suspens
|
||
|
|
|
||
|
|
1. **Fallback avec panic** : Si ni les headers ni le serveur ne sont disponibles, le middleware panic au démarrage avec un message clair (ex: "BaseUrl: configurer PMO_SERVER_URL ou démarrer le serveur avant les handlers HTTP").
|
||
|
|
→ **Décision utilisateur** : OK, panic avec message clair.
|
||
|
|
|
||
|
|
2. **Reverse proxy avec Authelia** : NPM ajoutera les headers `X-Forwarded-*`. Authelia gère l'authentification separately. Pas de vérification de header supplémentaire nécessaire pour le middleware BaseUrl.
|
||
|
|
→ **Décision** : Pas de vérification supplémentaire.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Le plan original est bon mais surestime le travail.** La correction principale est de clarifier que le problème vient des tâches de fond (background tasks), pas des handlers REST.
|