Jak zmusić przeglądarkę internetową, aby NIE buforowała obrazów

124

tło

Piszę i używam bardzo prostego narzędzia do zarządzania treścią opartego na CGI (Perl) dla dwóch witryn pro-bono. Udostępnia administratorowi strony formularze HTML do wydarzeń, w których wypełnia pola (data, miejsce, tytuł, opis, linki itp.) I zapisuje je. Na tym formularzu pozwalam administratorowi na przesłanie zdjęcia związanego z wydarzeniem. Na stronie HTML wyświetlającej formularz pokazuję również podgląd wgranego zdjęcia (tag HTML img).

Problem

Problem pojawia się, gdy administrator chce zmienić obrazek. Musiałby po prostu nacisnąć przycisk „Przeglądaj”, wybrać nowe zdjęcie i wcisnąć ok. I to działa dobrze.

Po załadowaniu obrazu mój back-end CGI obsługuje przesyłanie i ponownie ładuje formularz.

Problem polega na tym, że wyświetlany obraz nie jest odświeżany. Stary obraz jest nadal wyświetlany, mimo że baza danych zawiera właściwy obraz. Ograniczyłem to do faktu, że OBRAZ JEST CACHOWANY w przeglądarce internetowej. Jeśli administrator naciśnie przycisk RELOAD w przeglądarce Firefox / Explorer / Safari, wszystko zostanie poprawnie odświeżone i nowy obraz po prostu się pojawi.

Moje rozwiązanie - nie działa

Próbuję kontrolować pamięć podręczną, pisząc instrukcję HTTP Expires z datą bardzo dawną w przeszłości.

Expires: Mon, 15 Sep 2003 1:00:00 GMT

Pamiętaj, że jestem po stronie administracyjnej i nie obchodzi mnie, czy ładowanie stron trwa trochę dłużej, ponieważ zawsze wygasają.

Ale to też nie działa.

Uwagi

Podczas przesyłania obrazu jego nazwa pliku nie jest przechowywana w bazie danych. Został zmieniony na Image.jpg (aby po prostu rozwiązać problem podczas korzystania z niego). Podczas zastępowania istniejącego obrazu nowym, nazwa również się nie zmienia. Zmienia się tylko zawartość pliku obrazu.

Serwer WWW jest udostępniany przez usługę hostingową / dostawcę usług internetowych. Używa Apache.

Pytanie

Czy istnieje sposób, aby zmusić przeglądarkę internetową, aby NIE buforowała rzeczy z tej strony, nawet obrazów?

Żongluję opcją „zapisania nazwy pliku” w bazie danych. W ten sposób, jeśli obraz zostanie zmieniony, zmieni się również src tagu IMG. Wymaga to jednak wielu zmian w całej witrynie i raczej nie robię tego, jeśli mam lepsze rozwiązanie. Ponadto, to nadal nie zadziała, jeśli nowy przesłany obraz ma tę samą nazwę (powiedzmy, że obraz został trochę przerobiony w Photoshopie i ponownie przesłany).

Philibert Perusse
źródło
Myślę, że to pytanie można przerobić, aby usunąć cały „teraz bezużyteczny kontekst”, który umieściłem tam jakiś czas temu, kiedy to pisałem. Myślę, że tytuł jest właściwy, a także najlepsza odpowiedź. Ale pytanie zostało napisane pierwotnie, aby zapewnić dużo miejsca na różnego rodzaju odpowiedzi i nie mogłem oczekiwać tak prostego rozwiązania. Dlatego pytanie jest nieco zawiłe dla ludzi przyjeżdżających tam, aby uzyskać odpowiedź.
Philibert Perusse

Odpowiedzi:

187

Armin Ronacher ma dobry pomysł. Problem polega na tym, że przypadkowe ciągi mogą się zderzać. Użyłbym:

<img src="picture.jpg?1222259157.415" alt="">

Gdzie „1222259157.415” to bieżący czas na serwerze.
Generuj czas za pomocą JavaScript z performance.now()lub przez Python ztime.time()

epochwolf
źródło
32
Ważnym dodatkiem jest to, że nigdy nie możesz zmusić przeglądarki do niczego. Wszystko, co możesz zrobić, to przedstawić przyjazne sugestie. To od przeglądarki i użytkownika zależy, czy zastosują się do tych sugestii. Przeglądarka może to zignorować lub użytkownik może zmienić ustawienia domyślne.
Joel Coehoorn
8
Joel, lepiej by było, gdybyś dodał to we własnej odpowiedzi.
epochwolf
1
Tylko myśl i zdecydowanie za późno, aby przyjść na imprezę tutaj - ale ponieważ masz kontrolę nad zmianą obrazu, być może lepiej byłoby zmienić nazwę obrazu podczas jego aktualizacji, zamiast dodawać ciąg zapytania. Na przykład: 1222259157.jpg zamiast obraz.jpg? 1222259157. W ten sposób jest aktualizowany i ponownie buforowany po ponownej wizycie.
danjah
43
Zamiast dodawać czas serwera, co całkowicie zapobiega buforowaniu, dlaczego nie dodać czasu ostatniej modyfikacji pliku po znaku „?”. W ten sposób obraz będzie przechowywany w pamięci podręcznej do czasu następnej zmiany.
Doin
3
Ostatni komentarz Doina musi zostać przyjęty! Inteligentne buforowanie jest rzeczywiście ważne, dlaczego ktoś miałby pobierać w kółko ten sam plik?
f.arcarese
46

Prosta poprawka: dołącz losowy ciąg zapytania do obrazu:

<img src="foo.cgi?random=323527528432525.24234" alt="">

Co mówi HTTP RFC:

Cache-Control: no-cache

Ale to nie działa tak dobrze :)

Armin Ronacher
źródło
1
jak dać Cache-Control: no-cache w normalnym tagu <img> html?
P Satish Patro
Czy musi być w nagłówku odpowiedzi?
P Satish Patro
22

Używam funkcji czasu modyfikacji pliku PHP , na przykład:

echo <img  src='Images/image.png?" . filemtime('Images/image.png') . "'  />";

Jeśli zmienisz obraz, zostanie użyty nowy obraz, a nie zapisany w pamięci podręcznej, ze względu na inny zmodyfikowany znacznik czasu.

Stóg
źródło
Świetna wskazówka. Masz zarówno buforowanie, jak i obraz ponownie wysyłany do klienta, jeśli zmieni się jego czas modyfikacji (nadpisany lub zmieniony).
Vasilis Lourdas
zobacz tutaj inną opcję stackoverflow.com/questions/56588850/ ...
drooh
Absolute to załatwiło. Świetny kolega napiwek.
Khurram Shaikh,
Idealny ! Tylko dodatkowa wskazówka: <img src = "images / image.png? <? Php echo filemtime ('images / image.jpg')?>">
Rica Gurgel
16

Użyłbym:

<img src="picture.jpg?20130910043254">

gdzie „20130910043254” to czas modyfikacji pliku.

Podczas przesyłania obrazu jego nazwa pliku nie jest przechowywana w bazie danych. Został zmieniony na Image.jpg (aby po prostu rozwiązać problem podczas korzystania z niego). Podczas zastępowania istniejącego obrazu nowym, nazwa również się nie zmienia. Zmienia się tylko zawartość pliku obrazu.

Myślę, że istnieją dwa rodzaje prostych rozwiązań: 1) te, które przychodzą na myśl pierwsze (proste rozwiązania, ponieważ są łatwe do wymyślenia), 2) takie, które przychodzą do głowy po przemyśleniu (ponieważ są łatwe do posługiwać się). Najwyraźniej nie zawsze odniesiesz korzyści, jeśli zdecydujesz się przemyśleć sprawy. Uważam jednak, że druga opcja jest raczej niedoceniana. Pomyśl tylko, dlaczego phpjest tak popularny;)

x-yuri
źródło
1
+1 Myślę, że jest to o wiele lepszy pomysł niż inne odpowiedzi, ponieważ używając czasu modyfikacji lasera, twój serwer nie będzie próbował wielokrotnie udostępniać tego samego obrazu tej samej przeglądarce, gdy nic się nie zmieniło. Wyodrębnienie czasu ostatniego zapisu obrazu za każdym razem, gdy pojawia się żądanie, wymaga niewielkiego narzutu, ale w przypadku dużego obrazu jest to lepsze niż konieczność zwracania obrazu za każdym razem, a gdy obraz się zmieni, użytkownik będzie miał nowy. Dziękuję za ten miły mały dodatek.
Samuel
Cóż, można przechowywać czas modyfikacji wraz ze ścieżką do obrazu w bazie danych. Więc narzut może być jeszcze mniej znaczący. Z drugiej strony, jeśli są to obrazy, które są częścią kodu źródłowego, o którym mówimy, można również buforować ich czasy modyfikacji. Generując skrypt z czasami modyfikacji obrazów (np images.php.). Ten skrypt musi być ponownie generowany przy każdym zatwierdzeniu i eliminuje narzut związany z określaniem czasów modyfikacji plików.
x-yuri,
@ x-yori dlatego
decyduję
9

użyj Class = "NO-CACHE"

przykładowy html:

<div>
    <img class="NO-CACHE" src="images/img1.jpg" />
    <img class="NO-CACHE" src="images/imgLogo.jpg" />
</div>

jQuery:

    $(document).ready(function ()
    {           
        $('.NO-CACHE').attr('src',function () { return $(this).attr('src') + "?a=" + Math.random() });
    });

javascript:

var nods = document.getElementsByClassName('NO-CACHE');
for (var i = 0; i < nods.length; i++)
{
    nods[i].attributes['src'].value += "?a=" + Math.random();
}

Wynik: src = "images / img1.jpg" => src = "images / img1.jpg? A = 0.08749723793963926"

Aref Rostamkhani
źródło
Prosty, elegancki i nie wymaga renderowania po stronie serwera. MIŁY!
Ahi Tuna
7

Możesz napisać skrypt proxy do wyświetlania obrazów - to jednak trochę więcej pracy. Coś takiego:

HTML:

<img src="image.php?img=imageFile.jpg&some-random-number-262376" />

Scenariusz:

// PHP
if( isset( $_GET['img'] ) && is_file( IMG_PATH . $_GET['img'] ) ) {

  // read contents
  $f = open( IMG_PATH . $_GET['img'] );
  $img = $f.read();
  $f.close();

  // no-cache headers - complete set
  // these copied from [php.net/header][1], tested myself - works
  header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Some time in the past
  header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); 
  header("Cache-Control: no-store, no-cache, must-revalidate"); 
  header("Cache-Control: post-check=0, pre-check=0", false); 
  header("Pragma: no-cache"); 

  // image related headers
  header('Accept-Ranges: bytes');
  header('Content-Length: '.strlen( $img )); // How many bytes we're going to send
  header('Content-Type: image/jpeg'); // or image/png etc

  // actual image
  echo $img;
  exit();
}

Właściwie albo nagłówki bez pamięci podręcznej lub losowa liczba w źródle obrazu powinny być wystarczające, ale ponieważ chcemy być kuloodporni ...

Mindeh
źródło
Twoje jest dobrym rozwiązaniem, poza tym, że Pragma nie jest nagłówkiem odpowiedzi.
Piskvor opuścił budynek
1
@Piskvor: w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32 wydaje się mówić inaczej.
Grizly
6

Jestem NOWYM koderem, ale oto, co wymyśliłem, aby powstrzymać przeglądarkę przed buforowaniem i utrzymywaniem widoków z mojej kamery internetowej:

<meta Http-Equiv="Cache" content="no-cache">
<meta Http-Equiv="Pragma-Control" content="no-cache">
<meta Http-Equiv="Cache-directive" Content="no-cache">
<meta Http-Equiv="Pragma-directive" Content="no-cache">
<meta Http-Equiv="Cache-Control" Content="no-cache">
<meta Http-Equiv="Pragma" Content="no-cache">
<meta Http-Equiv="Expires" Content="0">
<meta Http-Equiv="Pragma-directive: no-cache">
<meta Http-Equiv="Cache-directive: no-cache">

Nie jestem pewien, co działa w jakiej przeglądarce, ale działa w niektórych: IE: działa, gdy strona jest odświeżana i kiedy ponownie odwiedzana jest witryna (bez odświeżania). CHROME: działa tylko po odświeżeniu strony internetowej (nawet po ponownej wizycie). SAFARI i iPad: nie działa, muszę wyczyścić historię i dane internetowe.

Jakieś pomysły na SAFARI / iPad?

Timmy T.
źródło
4

Podczas przesyłania obrazu jego nazwa pliku nie jest przechowywana w bazie danych. Został zmieniony na Image.jpg (aby po prostu rozwiązać problem podczas korzystania z niego).

Zmień to i naprawiłeś swój problem. Używam znaczników czasu, tak jak w rozwiązaniach zaproponowanych powyżej: Image- <timestamp> .jpg

Prawdopodobnie wszelkie problemy, których unikniesz, zachowując tę ​​samą nazwę pliku obrazu, można rozwiązać, ale nie mówisz, jakie one są.

AmbroseChapel
źródło
4

Sprawdziłem wszystkie odpowiedzi w sieci i wydawało się, że najlepsza z nich brzmi: (właściwie nie jest)

<img src="image.png?cache=none">

najpierw.

Jeśli jednak dodasz parametr cache = none (które jest statycznym słowem „none”), to nic nie zmienia, przeglądarka nadal ładuje się z pamięci podręcznej.

Rozwiązaniem tego problemu było:

<img src="image.png?nocache=<?php echo time(); ?>">

gdzie zasadniczo dodajesz uniksowy znacznik czasu, aby parametr był dynamiczny i nie miał pamięci podręcznej, zadziałało.

Jednak mój problem był trochę inny: ładowałem w locie wygenerowany obraz wykresu php i kontrolowałem stronę za pomocą parametrów $ _GET. Chciałem, aby obraz był odczytywany z pamięci podręcznej, gdy parametr URL GET pozostaje taki sam, i nie buforował, gdy zmieniają się parametry GET.

Aby rozwiązać ten problem, musiałem haszować $ _GET, ale ponieważ jest to tablica, oto rozwiązanie:

$chart_hash = md5(implode('-', $_GET));
echo "<img src='/images/mychart.png?hash=$chart_hash'>";

Edycja :

Chociaż powyższe rozwiązanie działa dobrze, czasami chcesz udostępniać wersję z pamięci podręcznej do momentu zmiany pliku. (przy powyższym rozwiązaniu całkowicie wyłącza pamięć podręczną tego obrazu) Tak więc, aby wyświetlać buforowany obraz z przeglądarki, DO DOPÓKI nastąpi zmiana w użyciu pliku obrazu:

echo "<img src='/images/mychart.png?hash=" . filemtime('mychart.png') . "'>";

filemtime () pobiera czas modyfikacji pliku.

Tarik
źródło
2
filemtimeRozwiązanie jest lepsze, ponieważ też md5zajmuje dużo mocy obliczeniowej.
oriadam,
2
Spędziłem dni, próbując przekonać aplikację opartą na Chromium, aby przestała buforować obrazy. „Nocache z czasem echo” rozwiązał problem. Dziękuję Ci!
Woody
2

Twój problem polega na tym, że pomimo Expires:nagłówka Twoja przeglądarka ponownie wykorzystuje znajdującą się w pamięci kopię obrazu sprzed aktualizacji, zamiast sprawdzać pamięć podręczną.

Miałem bardzo podobną sytuację, gdy ładowałem zdjęcia produktów w zapleczu administracyjnym dla witryny podobnej do sklepu, aw moim przypadku zdecydowałem, że najlepszą opcją jest użycie javascript do wymuszenia odświeżenia obrazu, bez użycia jakichkolwiek technik modyfikacji adresu URL innych osób już tutaj wspomniałem. Zamiast tego umieściłem adres URL obrazu w ukrytej ramce IFRAME, wywołanej location.reload(true)w oknie IFRAME, a następnie zastąpiłem mój obraz na stronie. Wymusza to odświeżenie obrazu, nie tylko na stronie, na której jestem, ale także na wszystkich późniejszych stronach, które odwiedzam - bez konieczności zapamiętywania parametrów zapytania URL lub identyfikatora fragmentu przez klienta lub serwer.

Zamieściłem jakiś kod, aby zrobić to w mojej odpowiedzi tutaj .

Doin
źródło
2

Dodaj znacznik czasu <img src="picture.jpg?t=<?php echo time();?>">

zawsze nada plikowi losową liczbę na końcu i zatrzyma buforowanie

BritishSam
źródło
Warto zauważyć, że wdrożenie tego rozwiązania obciąża serwer na żądanie i niekoniecznie będzie działać, ponieważ przeglądarka może buforować stronę php, która zawiera czas wygenerowany w momencie początkowego żądania, które jest buforowane. To działałoby niezawodnie tylko wtedy, gdyby strona również miała dynamiczny ciąg zapytania.
Dmitry
1

Ze względu na możliwość złego zachowania przezroczystych serwerów proxy między Tobą a klientem, jedynym sposobem na całkowitą gwarancję, że obrazy nie będą buforowane, jest nadanie im unikalnego identyfikatora URI, coś w rodzaju oznaczania znacznika czasu jako ciągu zapytania lub jako części ścieżka.

Jeśli ten znacznik czasu odpowiada czasowi ostatniej aktualizacji obrazu, możesz buforować, kiedy zajdzie taka potrzeba, i wyświetlać nowy obraz we właściwym czasie.

user7375
źródło
1

Zakładam, że oryginalne pytanie dotyczy obrazów przechowywanych z pewnymi informacjami tekstowymi. Tak więc, jeśli masz dostęp do kontekstu tekstowego podczas generowania src = ... url, rozważ przechowywanie / używanie CRC32 bajtów obrazu zamiast bezsensownego znacznika losowego lub czasu. Następnie, jeśli wyświetlana jest strona z dużą ilością obrazów, tylko zaktualizowane obrazy zostaną ponownie załadowane. Ostatecznie, jeśli przechowywanie CRC jest niemożliwe, można je obliczyć i dołączyć do adresu URL w czasie wykonywania.

jbw
źródło
Lub użyj czasu ostatniej modyfikacji obrazu zamiast CRC, jeszcze lepiej!
Doin
1

Z mojego punktu widzenia wyłączenie buforowania obrazów to zły pomysł. W ogóle.

Głównym problemem jest tutaj - jak zmusić przeglądarkę do aktualizacji obrazu, gdy został zaktualizowany po stronie serwera.

Z mojego osobistego punktu widzenia najlepszym rozwiązaniem jest wyłączenie bezpośredniego dostępu do obrazów. Zamiast tego uzyskuj dostęp do obrazów za pośrednictwem filtru po stronie serwera / serwletu / innych podobnych narzędzi / usług.

W moim przypadku jest to usługa odpoczynku, która zwraca obraz i dołącza ETag w odpowiedzi. Usługa przechowuje hash wszystkich plików, jeśli plik zostanie zmieniony, zostanie zaktualizowany. Działa doskonale we wszystkich nowoczesnych przeglądarkach. Tak, wdrożenie go wymaga czasu, ale warto.

Jedyny wyjątek - to favicony. Z pewnych powodów to nie działa. Nie mogłem zmusić przeglądarki do aktualizacji pamięci podręcznej po stronie serwera. ETags, Cache Control, Expires, Pragma headers, nic nie pomogło.

W tym przypadku wydaje się, że dodanie jakiegoś parametru losowego / version do adresu URL jest jedynym rozwiązaniem.

Alexandr
źródło
1

Najlepiej byłoby dodać przycisk / klawisz / menu do każdej strony internetowej z opcją synchronizacji treści.

Aby to zrobić, możesz śledzić zasoby, które mogą wymagać synchronizacji, i albo użyć xhr do sondowania obrazów za pomocą dynamicznego zapytania, albo utworzyć obraz w czasie wykonywania za pomocą src przy użyciu dynamicznego zapytania. Następnie użyj mechanizmu rozgłaszania, aby powiadomić wszystkie komponenty stron internetowych, które używają zasobu do aktualizacji, aby używały zasobu z dynamicznym zapytaniem dołączonym do adresu URL.

Naiwny przykład wygląda tak:

Zwykle obraz jest wyświetlany i przechowywany w pamięci podręcznej, ale jeśli użytkownik naciśnie przycisk, do zasobu wysyłane jest żądanie xhr z dołączonym zapytaniem o czas; ponieważ można założyć, że czas jest inny przy każdym naciśnięciu, upewni się, że przeglądarka ominie pamięć podręczną, ponieważ nie może stwierdzić, czy zasób jest generowany dynamicznie po stronie serwera na podstawie zapytania, czy też jest statyczny zasób, który ignoruje zapytanie.

W rezultacie możesz uniknąć sytuacji, w której wszyscy użytkownicy będą przez cały czas bombardować Cię żądaniami zasobów, ale jednocześnie umożliwisz użytkownikom aktualizowanie ich zasobów, jeśli podejrzewają, że nie są zsynchronizowani.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="mobile-web-app-capable" content="yes" />        
    <title>Resource Synchronization Test</title>
    <script>
function sync() {
    var xhr = new XMLHttpRequest;
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {            
            var images = document.getElementsByClassName("depends-on-resource");

            for (var i = 0; i < images.length; ++i) {
                var image = images[i];
                if (image.getAttribute('data-resource-name') == 'resource.bmp') {
                    image.src = 'resource.bmp?i=' + new Date().getTime();                
                }
            }
        }
    }
    xhr.open('GET', 'resource.bmp', true);
    xhr.send();
}
    </script>
  </head>
  <body>
    <img class="depends-on-resource" data-resource-name="resource.bmp" src="resource.bmp"></img>
    <button onclick="sync()">sync</button>
  </body>
</html>
Dmitry
źródło
0

Musisz użyć unikalnych nazw plików. Lubię to

<img src="cars.png?1287361287" alt="">

Ale ta technika oznacza wysokie użycie serwera i marnotrawstwo przepustowości. Zamiast tego należy użyć numeru wersji lub daty. Przykład:

<img src="cars.png?2020-02-18" alt="">

Ale chcesz, aby nigdy nie wyświetlał obrazu z pamięci podręcznej. W tym celu, jeśli strona nie korzysta z pamięci podręcznej strony , jest to możliwe po stronie PHP lub serwera.

<img src="cars.png?<?php echo time();?>" alt="">

Jednak nadal nie jest to skuteczne. Powód: pamięć podręczna przeglądarki ... Ostatnią, ale najbardziej skuteczną metodą jest natywny JAVASCRIPT. Ten prosty kod znajduje wszystkie obrazy z klasą „NO-CACHE” i sprawia, że ​​obrazy są prawie niepowtarzalne. Umieść to między tagami skryptu.

var items = document.querySelectorAll("img.NO-CACHE");
for (var i = items.length; i--;) {
    var img = items[i];
    img.src = img.src + '?' + Date.now();
}

STOSOWANIE

<img class="NO-CACHE" src="https://upload.wikimedia.org/wikipedia/commons/6/6a/JavaScript-logo.png" alt="">

WYNIK (y) Jak to

https://example.com/image.png?1582018163634
Ali
źródło