Cesse de demander au serveur une couverture qui n'existe pas

La mise en cache retombait sur le relais dès que la tentative directe n'était
pas « ok », sans distinguer « je n'ai pas pu demander » de « on m'a répondu
qu'il n'y a rien ». Un 404 est une réponse : le relais irait chercher la même
URL et ne peut pas faire mieux.

D'où deux 404 par vignette manquante sur la bibliographie d'un auteur, à
chaque affichage puisque rien ne se met alors en cache — et dont le second
venait de notre propre serveur, qui refuse par construction une URL absente
de la base.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-22 02:09:56 +02:00
co-authored by Claude Opus 5
parent c168959e12
commit 611dd1e2ad
2 changed files with 38 additions and 1 deletions
+28
View File
@@ -1887,6 +1887,34 @@ Les suivre reste sûr précisément parce que le garde est **à la connexion** :
chaque saut, cible de redirection comprise. C'est la position du garde, et non l'interdiction de chaque saut, cible de redirection comprise. C'est la position du garde, et non l'interdiction de
rediriger, qui ferme le SSRF. Bornées à 3 sauts. rediriger, qui ferme le SSRF. Bornées à 3 sauts.
### ⚠️ Le relais ne rattrape qu'un accès EMPÊCHÉ, jamais une image absente (2026-08-22)
Constaté en console sur la bibliographie d'un auteur : **deux 404 par vignette manquante**, dont
un de *notre* serveur. Le second n'apprenait rien et ne pouvait rien apprendre.
`couvertureMettreEnCache` retombait sur le relais dès que la tentative directe n'était pas `ok`
— sans distinguer **« je n'ai pas pu demander »** de **« on m'a répondu qu'il n'y a rien »**. Or
un 404 est une réponse : l'hôte a parlé, et le relais ira chercher la **même** URL. Il ne peut
par construction pas faire mieux.
Le cas se produit à chaque affichage, et pas une fois pour toutes : rien ne se met en cache
quand l'image n'existe pas, donc la paire de 404 revient à chaque rendu. Et sur une
bibliographie, l'URL n'est de toute façon **jamais** en base — le garde du relais la refuse par
construction, ce qui rend le second appel doublement vain.
Le repli sur le relais est donc réservé à un **échec de la requête** (CORS, hôte injoignable) ou
à un statut qui n'est pas un refus définitif. `404` et `410` s'arrêtent là.
⚠️ **Ce qui n'est PAS un défaut, et qu'il ne faut pas « corriger »** : le 404 d'OpenLibrary
lui-même. `?default=false` existe précisément pour l'obtenir franc plutôt qu'une image
d'attente, et `onerror` fait apparaître le substitut à initiale. Une couverture inexistante
**doit** produire un 404 en console.
Vérifié en exécution, magasin `couvertures` vidé au préalable : sur une bibliographie de
47 œuvres, une couverture absente ne produit plus qu'un seul appel (celui d'OpenLibrary), une
couverture existante d'œuvre **non possédée** se range bien en cache, et l'unique appel au relais
— l'hébergeur sans CORS — répond **200**.
### L'ordre des tentatives, côté client ### L'ordre des tentatives, côté client
`couvertureMettreEnCache` tente **d'abord l'URL directe** — elle profite du cache HTTP du `couvertureMettreEnCache` tente **d'abord l'URL directe** — elle profite du cache HTTP du
+10 -1
View File
@@ -125,7 +125,16 @@ export async function couvertureMettreEnCache(url) {
// Bloqué par le CORS, ou hôte injoignable : on ne sait pas lequel, et peu importe. // Bloqué par le CORS, ou hôte injoignable : on ne sait pas lequel, et peu importe.
} }
if (!reponse || !reponse.ok) { // ⚠️ Le relais ne rattrape QU'UN ACCÈS EMPÊCHÉ, jamais une image absente. Un 404 (ou un
// 410) est une réponse : l'hôte a parlé, et il a dit qu'il n'avait rien — or le relais
// ira chercher la MÊME url, il ne peut donc pas faire mieux. L'appeler quand même
// produit un second 404, de notre serveur cette fois, sur toute couverture
// qu'OpenLibrary ne connaît pas (« ?default=false » les rend franches, c'est voulu).
// C'est ce qui se voyait en console sur la bibliographie d'un auteur, à chaque
// affichage puisque rien ne se met jamais en cache.
const absente = reponse !== null && (reponse.status === 404 || reponse.status === 410);
if (!absente && (!reponse || !reponse.ok)) {
reponse = await fetch('/api/couvertures?url=' + encodeURIComponent(url)); reponse = await fetch('/api/couvertures?url=' + encodeURIComponent(url));
} }