Eu tenho uma página da web ( https://smartystreets.com/contact ) que usa jQuery para carregar alguns arquivos SVG do S3 através do CloudFront CDN.
No Chrome, abrirei uma janela anônima, bem como o console. Então eu vou carregar a página. À medida que a página é carregada, normalmente recebo de 6 a 8 mensagens no console semelhantes a esta:
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.
Se eu fizer um recarregamento padrão da página, mesmo várias vezes, continuo recebendo os mesmos erros. Se eu fizer Command+Shift+R
isso, a maioria e, às vezes, todas as imagens serão carregadas sem o XMLHttpRequest
erro.
Às vezes, mesmo depois que as imagens são carregadas, eu atualizo e uma ou mais imagens não carregam e retornam esse XMLHttpRequest
erro novamente.
Verifiquei, alterei e verifiquei novamente as configurações no S3 e no Cloudfront. No S3 minha configuração CORS fica assim:
<?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>
(Nota: inicialmente tinha apenas <AllowedOrigin>*</AllowedOrigin>
, mesmo problema.)
No CloudFront, o comportamento de distribuição é definido para permitir os métodos HTTP: GET, HEAD, OPTIONS
. Os métodos em cache são os mesmos. Encaminhar Cabeçalhos é definido como "Lista de permissões" e essa lista de permissões inclui "Cabeçalhos de solicitação de controle de acesso, Método de solicitação de controle de acesso, Origem".
O fato de funcionar após um recarregamento do navegador sem cache parece indicar que tudo está bem no lado do S3/CloudFront, caso contrário, por que o conteúdo seria entregue. Mas então por que o conteúdo não seria entregue na visualização de página inicial?
Estou trabalhando no Google Chrome no macOS. O Firefox não tem problemas para obter os arquivos todas as vezes. O Opera NUNCA recebe os arquivos. O Safari pegará as imagens após várias atualizações.
Usando curl
não tenho problemas:
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==
Alguns sugeriram que eu exclua a distribuição do CloudFront e a recrie. Parece uma correção bastante dura e inconveniente.
O que está causando esse problema?
Atualizar:
Adicionando cabeçalhos de resposta de uma imagem que falhou ao carregar.
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
Você está fazendo duas solicitações para o mesmo objeto, uma de HTML e outra de XHR. A segunda falha porque o Chrome usa a resposta em cache da primeira solicitação, que não tem
Access-Control-Allow-Origin
cabeçalho de resposta.Por quê?
Bug do Chromium 409090 Falha na solicitação de origem cruzada do cache após a solicitação regular ser armazenada em cache descreve esse problema e é um "não corrigirá" - eles acreditam que seu comportamento está correto. O Chrome considera a resposta em cache utilizável, aparentemente porque a resposta não incluiu um
Vary: Origin
cabeçalho.Mas o S3 não retorna
Vary: Origin
quando um objeto é solicitado sem umOrigin:
cabeçalho de solicitação, mesmo quando o CORS está configurado no bucket.Vary: Origin
é enviado apenas quando umOrigin
cabeçalho está presente na solicitação.E o CloudFront não adiciona
Vary: Origin
mesmo quandoOrigin
está na lista de permissões para encaminhamento, o que deve, por definição, significar que variar o cabeçalho pode modificar a resposta. Essa é a razão pela qual você encaminha e armazena em cache nos cabeçalhos de solicitação.O CloudFront recebe um passe, porque sua resposta estaria correta se os S3 fossem mais corretos, pois o CloudFront retorna isso quando é fornecido pelo S3.
S3, um pouco mais confuso. Não é errado retornar
Vary: Some-Header
quando não haviaSome-Header
no pedido.Claramente,
Vary: Some-Absent-Header
é válido, então S3 estaria correto se adicionasseVary: Origin
à sua resposta se o CORS estivesse configurado, pois isso de fato poderia variar a resposta.E, aparentemente, isso faria o Chrome fazer a coisa certa. Ou, se não fizer a coisa certa neste caso, estaria violando um
MUST NOT
. Da mesma seção:Então, o S3 realmente
SHOULD
estará retornandoVary: Origin
quando o CORS estiver configurado no bucket, seOrigin
estiver ausente da requisição, mas não.Ainda assim, o S3 não está estritamente errado por não retornar o cabeçalho, porque é apenas um
SHOULD
, não umMUST
. Novamente, da mesma seção do RFC-7231:Por outro lado, pode-se argumentar que o Chrome deve saber implicitamente que a variação do
Origin
cabeçalho deve ser uma chave de cache, pois pode alterar a resposta da mesma maneiraAuthorization
que pode alterar a resposta.Da mesma forma, a reutilização entre origens é indiscutivelmente limitada pela natureza,
Origin
mas esse argumento não é forte.tl;dr: Você aparentemente não pode buscar com sucesso um objeto do HTML e depois buscá-lo novamente com sucesso como uma solicitação CORS com Chrome e S3 (com ou sem CloudFront), devido a peculiaridades nas implementações.
Gambiarra:
Esse comportamento pode ser contornado com CloudFront e Lambda@Edge, usando o código a seguir como um gatilho de resposta de origem.
Isso adiciona
Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Origin
a qualquer resposta do S3 que não tenhaVary
cabeçalho. Caso contrário, oVary
cabeçalho na resposta não será modificado.Atribuição: também sou o autor da postagem original nos fóruns do AWS Support em que esse código foi compartilhado inicialmente.
A solução Lambda@Edge acima resulta em um comportamento totalmente correto, mas aqui estão duas alternativas que podem ser úteis, dependendo de suas necessidades específicas:
Alternativa/solução nº 1: forje os cabeçalhos CORS no CloudFront.
O CloudFront oferece suporte a cabeçalhos personalizados que são adicionados a cada solicitação. Se você definir
Origin:
em todas as solicitações, mesmo aquelas que não são de origem cruzada, isso permitirá o comportamento correto no S3. A opção de configuração é chamada de Custom Origin Headers, com a palavra "Origin" significando algo totalmente diferente do que significa no CORS. A configuração de um cabeçalho personalizado como este no CloudFront substitui o que é enviado na solicitação pelo valor especificado ou o adiciona se estiver ausente. Se você tiver exatamente uma origem acessando seu conteúdo por XHR, por exemplohttps://example.com
, você pode adicionar isso. O uso*
é duvidoso, mas pode funcionar para outros cenários. Considere as implicações com cuidado.Alternativa/Solução nº 2: Use um parâmetro de string de consulta "fictício" que difere para HTML e XHR ou está ausente de um ou de outro. Esses parâmetros geralmente são nomeados
x-*
, mas não devem serx-amz-*
.Digamos que você invente o nome
x-request
. Então<img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">
. Ao acessar o objeto do JS, não adicione o parâmetro de consulta. O CloudFront já está fazendo a coisa certa, armazenando em cache diferentes versões dos objetos usando oOrigin
cabeçalho ou a ausência dele como parte da chave de cache, porque você encaminhou esse cabeçalho em seu comportamento de cache. O problema é que seu navegador não sabe disso. Isso convence o navegador de que este é realmente um objeto separado que precisa ser solicitado novamente, em um contexto CORS.Se você usar essas sugestões alternativas, use uma ou outra – não ambas.
A partir de novembro de 2021, o CloudFront oferece suporte direto às políticas de cabeçalhos de resposta . Isso inclui CORS, segurança e cabeçalhos personalizados. Não há mais necessidade de injetar cabeçalhos personalizados via Lambda@Edge ou CloudFront Functions.
Talvez mais agradável, não há mais necessidade de adicionar Vary como um cabeçalho personalizado. A nova implementação do CORS nas Políticas de Cabeçalhos inclui lógica adicional para definir os cabeçalhos apropriados, como Vary, de acordo com o padrão de busca.
Não sei por que você estaria obtendo resultados tão diferentes em vários navegadores, mas:
Essa linha é o que (se você conseguir chamar a atenção deles) um engenheiro do CloudFront ou de suporte usará para seguir uma de suas solicitações com falha. Se a solicitação estiver chegando a um servidor CloudFront, ela deverá ter esse cabeçalho na resposta. Se esse cabeçalho não estiver lá, a solicitação provavelmente está falhando em algum lugar antes de chegar ao CloudFront.
A solução aceita resolve o problema, mas não é a de melhor desempenho, principalmente para distribuições do CloudFront que atendem a conteúdo dinâmico. A configuração do cache de cabeçalho com uma lista de permissões resulta no armazenamento em cache do CloudFront de várias versões do objeto solicitado, dependendo do cabeçalho. Isso significa que internamente o CloudFront pode precisar buscar novamente o objeto da origem do S3 várias vezes. A transferência de dados do S3 para o CloudFront é gratuita, mas isso não leva em conta a latência adicional.
Uma solução alternativa aqui seria desabilitar a configuração CORS no bucket do S3 e, em vez disso, definir manualmente os cabeçalhos CORS usando uma função Lambda@Edge configurada na resposta do visualizador. A função pode ter a seguinte aparência:
Existe outra solução mais simples que funciona para mim com o uso de um atributo HTML chamado
crossorigin='anonymous'
conforme detalhado aqui . Basicamente, você pode adicionar este atributo como tal:<img src="your_image_url_here" crossorigin='anonymous'>
e isso faria essencialmente sua "primeira" solicitação para a imagem uma solicitação CORS, agora se você tentar recuperar a mesma imagem novamente por meio do XHR, mesmo que o Chrome decida usar o cache (resposta em cache para a primeira solicitação), tudo bem como agora vem com
Access-Control-Allow-Origin
header.Eu não tinha a reputação de comentar a resposta aceita, mas queria ajudar alguém com problemas semelhantes.
Em suma, acredito que a AWS mudou algo para que o código lambda da solução aceita não funcione mais (talvez se/quando você mudar para a nova implementação da política de cache do CloudFront?)
headers['vary']
não é falsey, portanto, a solução alternativa nunca é acionada.Este é o lambda corrigido conforme a solução do dobesv na postagem original do fórum ( https://forums.aws.amazon.com/thread.jspa?messageID=796312 ):
Para evitar completamente o comportamento de cache, você pode anexar um parâmetro de consulta aleatório ao fazer a solicitação via XHR.