J'ai une page Web ( https://smartystreets.com/contact ) qui utilise jQuery pour charger certains fichiers SVG à partir de S3 via le CDN CloudFront.
Dans Chrome, j'ouvrirai une fenêtre de navigation privée ainsi que la console. Ensuite, je chargerai la page. Au fur et à mesure que la page se charge, je reçois généralement 6 à 8 messages dans la console qui ressemblent à ceci:
XMLHttpRequest cannot load
https://d79i1fxsrar4t.cloudfront.net/assets/img/feature-icons/documentation.08e71af6.svg.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'https://smartystreets.com' is therefore not allowed access.
Si je fais un rechargement standard de la page, même plusieurs fois, je continue à obtenir les mêmes erreurs. Si je le fais Command+Shift+R
, la plupart des images, et parfois toutes, se chargeront sans l' XMLHttpRequest
erreur.
Parfois, même après le chargement des images, je rafraîchis et une ou plusieurs images ne se chargent pas et renvoient à XMLHttpRequest
nouveau cette erreur.
J'ai vérifié, modifié et revérifié les paramètres sur S3 et Cloudfront. Dans S3, ma configuration CORS ressemble à ceci:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedOrigin>http://*</AllowedOrigin>
<AllowedOrigin>https://*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>
(Remarque: au départ, il n'y avait que le <AllowedOrigin>*</AllowedOrigin>
même problème.)
Dans CloudFront le comportement de distribution est configuré pour autoriser les méthodes HTTP: GET, HEAD, OPTIONS
. Les méthodes mises en cache sont les mêmes. Les en-têtes de transfert sont définis sur "Liste blanche" et cette liste blanche comprend "En-têtes de demande de contrôle d'accès, Méthode de demande de contrôle d'accès, origine".
Le fait qu'il fonctionne après un rechargement de navigateur sans cache semble indiquer que tout va bien du côté S3 / CloudFront, sinon pourquoi le contenu serait-il livré. Mais alors pourquoi le contenu ne serait-il pas livré lors de la première visualisation de la page?
Je travaille dans Google Chrome sur macOS. Firefox n'a aucun problème à récupérer les fichiers à chaque fois. Opera n'obtient JAMAIS les fichiers. Safari récupérera les images après plusieurs rafraîchissements.
En utilisant curl
je ne reçois aucun problème:
curl -I -H 'Origin: smartystreets.com' https://d79i1fxsrar4t.cloudfront.net/assets/img/phone-icon-outline.dc7e4079.svg
HTTP/1.1 200 OK
Content-Type: image/svg+xml
Content-Length: 508
Connection: keep-alive
Date: Tue, 20 Jun 2017 17:35:57 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Last-Modified: Thu, 15 Jun 2017 16:02:19 GMT
ETag: "dc7e4079f937e83291f2174853adb564"
Cache-Control: max-age=31536000
Expires: Wed, 01 Jan 2020 23:59:59 GMT
Accept-Ranges: bytes
Server: AmazonS3
Vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
Age: 4373
X-Cache: Hit from cloudfront
Via: 1.1 09fc52f58485a5da8e63d1ea27596895.cloudfront.net (CloudFront)
X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g==
Certains ont suggéré que je supprime la distribution CloudFront et que je la recrée. Cela semble être une solution plutôt dure et peu pratique.
Quelle est la cause de ce problème?
Mise à jour:
Ajout d'en-têtes de réponse à partir d'une image qui n'a pas pu se charger.
age:1709
cache-control:max-age=31536000
content-encoding:gzip
content-type:image/svg+xml
date:Tue, 20 Jun 2017 17:27:17 GMT
expires:2020-01-01T23:59:59.999Z
last-modified:Tue, 11 Apr 2017 18:17:41 GMT
server:AmazonS3
status:200
vary:Accept-Encoding
via:1.1 022c901b294fedd7074704d46fce9819.cloudfront.net (CloudFront)
x-amz-cf-id:i0PfeopzJdwhPAKoHpbCTUj1JOMXv4TaBgo7wrQ3TW9Kq_4Bx0k_pQ==
x-cache:Hit from cloudfront
Réponses:
Vous faites deux demandes pour le même objet, une à partir de HTML, une à partir de XHR. La seconde échoue, car Chrome utilise la réponse mise en cache de la première demande, qui n'a pas d'en-
Access-Control-Allow-Origin
tête de réponse.Pourquoi?
Chromium bug 409090 La demande d'origine croisée du cache échoue après la mise en cache de la demande régulière décrit ce problème, et c'est un "ne résoudra pas" - ils croient que leur comportement est correct. Chrome considère que la réponse mise en cache est utilisable, apparemment parce que la réponse ne comprenait pas d'en-
Vary: Origin
tête.Mais S3 ne retourne pas
Vary: Origin
lorsqu'un objet est demandé sans en-Origin:
tête de demande, même lorsque CORS est configuré sur le compartiment.Vary: Origin
n'est envoyé que lorsqu'un en-Origin
tête est présent dans la demande.Et CloudFront n'ajoute rien
Vary: Origin
même lorsqu'ilOrigin
est sur liste blanche pour le transfert, ce qui devrait par définition signifier que la modification de l'en-tête peut modifier la réponse - c'est la raison pour laquelle vous transférez et mettez en cache les en-têtes de demande.CloudFront obtient un laissez-passer, car sa réponse serait correcte si les S3 étaient plus corrects, car CloudFront le renvoie quand il est fourni par S3.
S3, un peu plus flou. Il n'est pas faux de revenir
Vary: Some-Header
quand il n'y en avait pasSome-Header
dans la demande.Clairement,
Vary: Some-Absent-Header
est valide, donc S3 serait correct s'il s'ajoutaitVary: Origin
à sa réponse si CORS est configuré, car cela pourrait en effet faire varier la réponse.Et, apparemment, cela ferait de Chrome la bonne chose. Ou, s'il ne fait pas la bonne chose dans ce cas, il violerait a
MUST NOT
. De la même section:Donc, S3
SHOULD
revient vraimentVary: Origin
lorsque CORS est configuré sur le compartiment, s'ilOrigin
est absent de la demande, mais ce n'est pas le cas.Pourtant, S3 n'a pas strictement tort de ne pas retourner l'en-tête, car c'est seulement un
SHOULD
, pas unMUST
. Encore une fois, à partir de la même section de la RFC-7231:D'un autre côté, l'argument pourrait être avancé que Chrome devrait implicitement savoir que la variation de l'en-
Origin
tête devrait être une clé de cache, car cela pourrait changer la réponse de la même manièreAuthorization
pourrait changer la réponse.De même, la réutilisation entre les origines est sans doute limitée par la nature de
Origin
mais cet argument n'est pas fort.tl; dr: Vous ne pouvez apparemment pas récupérer un objet à partir de HTML, puis le récupérer à nouveau avec une demande CORS avec Chrome et S3 (avec ou sans CloudFront), en raison de particularités dans les implémentations.
Solution de contournement:
Ce comportement peut être contourné avec CloudFront et Lambda @ Edge, en utilisant le code suivant comme déclencheur de réponse d'origine.
Cela s'ajoute
Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Origin
à toute réponse de S3 qui n'a pas d'en-Vary
tête. Sinon, l'en-Vary
tête de la réponse n'est pas modifié.Attribution: je suis également l'auteur de la publication originale sur les forums de support AWS où ce code a été initialement partagé.
La solution Lambda @ Edge ci-dessus se traduit par un comportement entièrement correct, mais voici deux alternatives qui peuvent vous être utiles, en fonction de vos besoins spécifiques:
Alternative / Hackaround # 1: Forgez les en-têtes CORS dans CloudFront.
CloudFront prend en charge les en-têtes personnalisés qui sont ajoutés à chaque demande. Si vous définissez
Origin:
sur chaque demande, même celles qui ne sont pas d'origine croisée, cela permettra un comportement correct dans S3. L'option de configuration est appelée en-têtes d'origine personnalisés, le mot «origine» signifiant quelque chose de complètement différent de ce qu'il signifie dans CORS. La configuration d'un en-tête personnalisé comme celui-ci dans CloudFront écrase ce qui est envoyé dans la demande avec la valeur spécifiée ou l'ajoute en cas d'absence. Si vous avez exactement une origine qui accède à votre contenu via XHR, par exemplehttps://example.com
, vous pouvez l'ajouter. L'utilisation*
est douteuse, mais peut fonctionner pour d'autres scénarios. Examinez attentivement les implications.Alternative / Hackaround # 2: utilisez un paramètre de chaîne de requête "factice" qui diffère pour HTML et XHR ou qui est absent de l'un ou de l'autre. Ces paramètres sont généralement nommés
x-*
mais ne doivent pas l'êtrex-amz-*
.Disons que vous inventez le nom
x-request
. Alors<img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">
. Lorsque vous accédez à l'objet à partir de JS, n'ajoutez pas le paramètre de requête. CloudFront fait déjà la bonne chose, en mettant en cache différentes versions des objets en utilisant l'en-Origin
tête ou son absence dans le cadre de la clé de cache, car vous avez transmis cet en-tête dans votre comportement de cache. Le problème est que votre navigateur ne le sait pas. Cela convainc le navigateur qu'il s'agit en fait d'un objet distinct qui doit être demandé à nouveau, dans un contexte CORS.Si vous utilisez ces suggestions alternatives, utilisez l'une ou l'autre - pas les deux.
la source
?x-some-key=some-value
paramètre de chaîne de requête arbitraire convaincra le navigateur que la demande est différente.Je ne sais pas pourquoi vous obtiendriez des résultats si différents de différents navigateurs, mais:
Cette ligne est là ce que (si vous pouvez attirer leur attention) un ingénieur CloudFront ou de support utilisera pour suivre l'une de vos demandes ayant échoué. Si la demande parvient à un serveur CloudFront, il doit avoir cet en-tête dans la réponse. Si cet en-tête n'est pas là, la demande échoue probablement quelque part avant d'arriver à CloudFront.
la source