data.mdx.frontmatter.hero_image

Ciekawy Przypadek CORS-a

2023-05-10 | Frontend | bd90

Chyba wieki minęły od mojego ostatniego wpisu w tematyce kategorii frontendowych. Pomimo tego, że obecnie dużo więcej czasu spędzam w aplikacjach bacekndowych lub infrastrukturze, to nadal lubię, od czasu do czasu, pomóc w rozwiązaniu ciekawszych frontowych problemów (bądź zmusza mnie do tego sytuacja, ale taka klątwa fullstacka). Nie inaczej było tym razem. Podczas tworzenia aplikacji natknęliśmy się na dziwne zachowanie przeglądarki w przypadku ściągania zdjęć z usługi AWS S3.

Konfiguracja S3

Jako, że nie podpisaliśmy S3 pod customową domenę, to wiedzieliśmy, że requesty pomiędzy naszą stroną a S3 będą wymagały konfiguracji pod CORS. Stworzyliśmy niezbyt skomplikowaną konfigurację

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "https://bd90.pl",
            "http://localhost:8000"
        ],
        "ExposeHeaders": [
            "Content-Type",
            "Content-Length"
        ]
    }
]

i ruszyliśmy dalej z implementacją funkcjonalności.

Funkcjonalność

Sam use-case też nie był zbyt skomplikowany, ot użytkownik miał mieć możliwość pobrania pliku graficznego znajdującego się na S3 przy pomocy przycisku ‘download’. Korzystając z analogii, to raczej poziom Górki Szczęśliwickiej niż Mount Everestu. Sam kodzik rozwiązania wyglądał następująco:

function downloadImage(url, name) {
  fetch(url)
    .then(response => response.blob())
    .then(blob => {
      const link = document.createElement('a')
      link.href = URL.createObjectURL(blob)
      link.download = name
      link.click()
  })
}

Dodatkowo kod html, który po prostu wyglądał tak:

<img src="{url}" alt="jakis tekst"/>

Naprawdę nie ma nic szczególnego w tym kodzie. Zadanie zakodowane poszło, sprawdzone na losowo wybranej przeglądarce, poszło do testów. I tutaj pojawiła się niespodzianka. Rozwiązanie, chodź wydawało się niemożliwością, by cokolwiek w nim zawiodło, nie na wszystkich przeglądarkach zadziałało. Mianowicie otrzymaliśmy odpowiedź, że na Google Chrome nie da się pobrać pliku graficznego (Na Brave ten problem nie występował).

Ze zdziwieniem ruszyliśmy aby sprawdzić z czego to wynika

Geneza problemu - CORS

Ku naszemu zdziwieniu, w momencie kiedy użytkownik naciskał przycisk ‘download’, aby pobrać plik graficzny, otrzymywaliśmy błąd CORS-a

Co się w tej sytuacji wydarzyło? Już spieszę z odpowiedzią w formie diagramu sekwencji:

TLDR: plik graficzny, który próbowaliśmy zapisać przy pomocy kodu JS-a odwoływał się do cache-a przeglądarki, gdzie przebywał plik bez nagłówków CORS-a. (Co ciekawe, nie na wszystkich przeglądarkach to jest błąd!)

Możliwe rozwiązania

Uniknięcie cache-a

Jednym z możliwych rozwiązań jest po prostu uniknięcie cache-a. W kodzie js-a możemy dodać query string, aby oszukać przeglądarkę i wymusić pobranie zasobu:

function downloadImage(url, name) {
  fetch(`${url}?please-not-from-cache`)
    .then(response => response.blob())
    .then(blob => {
      const link = document.createElement('a')
      link.href = URL.createObjectURL(blob)
      link.download = name
      link.click()
  })
}

Pobieranie nagłówków CORS-a

Wadą pierwszego sposobu jest to, że wykonujemy drugi request po zasób, który już fizycznie posiadamy na maszynie użytkownika, a optymalizacja rzecz święta. Dlatego według mnie lepszym sposobem jest oznaczenie tagu img atrybutem crossorigin, aby przeglądarka pobrała i zapisała do cache-a nagłówki cors:

<img src={url} crossorigin="anonymous" alt="jakis tekst"/>

Dzięki temu zabiegowi przeglądarka wyślę nagłówek Origin, serwer odpowie nagłówkami CORS-a i będziemy mogli pobrać plik bez potrzeby wykonywania drugiego żądania http.

Przykład

Na koniec chciałbym spróbować czegoś, co na tym blogu nie miało jeszcze miejsca (i nie mówię o serii pompek). Mianowicie, jeżeli chcecie sprawdzić czy wasza przeglądarka obsługuje wcześniej opisane żądania, czy walnie błędem, to możecie wykorzystać dwa komponenty umieszczone poniżej. Pierwszy powinien walnąć błędem (na niektórych przeglądarkach jak np. brave zadziała poprawnie), natomiast drugi dać wam możliwość pobrania obrazka.






Zakończenie

Jak zwykle mam nadzieje, że dowiedziałeś / dowiedziałaś się czegoś nowego z tego artykułu :)

Do Następnego!

Cześć!

By Bd90 | 10-05-2023 | Frontend