使用 Javascript 刷新图像,但前提是在服务器上进行了更改

发布于 2024-08-14 22:52:51 字数 574 浏览 5 评论 0原文

如果图像已在服务器上更新,我想在页面上重新加载图像。在其他问题中,建议执行诸如

newImage.src = "http://localhost/image.jpg?" + new Date().getTime();

强制重新加载图像之类的操作,但这意味着即使图像确实没有更改,也会再次下载。

是否有任何 Javascript 代码会导致使用正确的 If-Modified-Since 标头生成对同一图像的新请求,以便仅在图像实际发生更改时才下载该图像?

更新:我仍然很困惑:如果我只请求典型的 URL,我将获得本地缓存的副本。 (除非我让服务器将其标记为不可缓存,但我不想这样做,因为整个想法是不重新下载它,除非它确实发生了变化。)如果我更改 URL,我会总是重新下载,因为新 URL 的目的是破坏缓存。那么,如何获得我想要的中间行为,即仅当文件与本地缓存的副本不匹配时才下载文件?

I want to reload an image on a page if it has been updated on the server. In other questions it has been suggested to do something like

newImage.src = "http://localhost/image.jpg?" + new Date().getTime();

to force the image to be re-loaded, but that means that it will get downloaded again even if it really hasn't changed.

Is there any Javascript code that will cause a new request for the same image to be generated with a proper If-Modified-Since header so the image will only be downloaded if it has actually changed?

UPDATE: I'm still confused: if I just request the typical URL, I'll get the locally cached copy. (unless I make the server mark it as not cacheable, but I don't want to do that because the whole idea is to not re-download it unless it really changes.) if I change the URL, I'll always re-download, because the point of the new URL is to break the cache. So how do I get the in-between behavior I want, i.e. download the file only if it doesn't match the locally cached copy?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

海风掠过北极光 2024-08-21 22:52:51

JavaScript 无法侦听服务器上的事件。相反,您可以采用某种形式的长轮询或顺序调用服务器来查看图像是否已更改。

Javascript can't listen for an event on the server. Instead, you could employ some form of long-polling, or sequential calls to the server to see if the image has been changed.

—━☆沉默づ 2024-08-21 22:52:51

您应该查看 xhr.setRequestHeader() 方法。它是任何 XMLHttpRequest 对象的方法,可用于设置 Ajax 查询的标头。在 jQuery 中,您可以轻松地将 beforeSend 属性添加到 ajax 对象并在那里设置一些标头。

话虽这么说,使用 Ajax 进行缓存可能会很棘手。您可能想查看 Google 上的此帖子组,因为尝试覆盖浏览器的缓存机制涉及一些问题。您需要确保您的服务器返回正确的缓存控制标头,以便能够使此类功能正常工作。

You should have a look at the xhr.setRequestHeader() method. It's a method of any XMLHttpRequest object, and can be used to set headers on your Ajax queries. In jQuery, you can easily add a beforeSend property to your ajax object and set up some headers there.

That being said, caching with Ajax can be tricky. You might want to have a look at this thread on Google Groups, as there's a few issues involved with trying to override a browser's caching mechanisms. You'll need to ensure that your server is returning the proper cache control headers in order to be able to get something like this to work.

木有鱼丸 2024-08-21 22:52:51

执行此操作的一种方法是用户 server-发送事件让服务器在图像发生更改时推送通知。为此,您需要一个服务器端脚本来定期检查已通知的图像。下面的服务器端脚本确保服务器至少每(大约)60 秒发送一次事件,以防止超时,并且客户端 HTML 处理离开和到达页面的导航:

sse.py

#!/usr/bin/env python3

import time
import os.path

print("Content-Type: text/event-stream\n\n", end="")

IMG_PATH = 'image.jpg'

modified_time = os.path.getmtime(IMG_PATH)
seconds_since_last_send = 0

while True:
    time.sleep(1)
    new_modified_time = os.path.getmtime(IMG_PATH)
    if new_modified_time != modified_time:
        modified_time = new_modified_time
        print('data: changed\n\n', end="", flush=True)
        seconds_since_last_send = 0
    else:
        seconds_since_last_send += 1
        if seconds_since_last_send == 60:
            print('data: keep-alive\n\n', end="", flush=True)
            seconds_since_last_send = 0

然后您的 HTML 将包含一些 JavaScript 代码:

sse.html

<html>
<head>
   <meta charset="UTF-8">
   <title>Server-sent events demo</title>
</head>
<body>
  <img id="img" src="image.jpg">

<script>
  const img = document.getElementById('img');
  let evtSource = null;

  function setup_sse()
  {
    console.log('Creating new EventSource.');
    evtSource = new EventSource('sse.py');

    evtSource.onopen = function() {
      console.log('Connection to server opened.');
    };

    // if we navigate away from this page:
    window.onbeforeunload = function() {
      console.log('Closing connection.');
      evtSource.close();
      evtSource = null;
    };

    evtSource.onmessage = function(e) {
      if (e.data == 'changed')
        img.src = 'image.jpg?version=' + new Date().getTime();
    };

    evtSource.onerror = function(err) {
      console.error("EventSource failed:", err);
    };
  }


  window.onload = function() {
    // if we navigate back to this page:
    window.onfocus = function() {
      if (!evtSource)
        setup_sse();
    };

    setup_sse(); // first time
  };
</script>
</body>
</html>

One way of doing this is to user server-sent events to have the server push a notification whenever the image has been changed. For this you need a server-side script that will periodically check for the image having been notified. The server-side script below ensures that the server sends an event at least once every (approximately) 60 seconds to prevent timeouts and the client-side HTML handles navigation away from and to the page:

sse.py

#!/usr/bin/env python3

import time
import os.path

print("Content-Type: text/event-stream\n\n", end="")

IMG_PATH = 'image.jpg'

modified_time = os.path.getmtime(IMG_PATH)
seconds_since_last_send = 0

while True:
    time.sleep(1)
    new_modified_time = os.path.getmtime(IMG_PATH)
    if new_modified_time != modified_time:
        modified_time = new_modified_time
        print('data: changed\n\n', end="", flush=True)
        seconds_since_last_send = 0
    else:
        seconds_since_last_send += 1
        if seconds_since_last_send == 60:
            print('data: keep-alive\n\n', end="", flush=True)
            seconds_since_last_send = 0

And then your HTML would include some JavaScript code:

sse.html

<html>
<head>
   <meta charset="UTF-8">
   <title>Server-sent events demo</title>
</head>
<body>
  <img id="img" src="image.jpg">

<script>
  const img = document.getElementById('img');
  let evtSource = null;

  function setup_sse()
  {
    console.log('Creating new EventSource.');
    evtSource = new EventSource('sse.py');

    evtSource.onopen = function() {
      console.log('Connection to server opened.');
    };

    // if we navigate away from this page:
    window.onbeforeunload = function() {
      console.log('Closing connection.');
      evtSource.close();
      evtSource = null;
    };

    evtSource.onmessage = function(e) {
      if (e.data == 'changed')
        img.src = 'image.jpg?version=' + new Date().getTime();
    };

    evtSource.onerror = function(err) {
      console.error("EventSource failed:", err);
    };
  }


  window.onload = function() {
    // if we navigate back to this page:
    window.onfocus = function() {
      if (!evtSource)
        setup_sse();
    };

    setup_sse(); // first time
  };
</script>
</body>
</html>
说好的呢 2024-08-21 22:52:51

这里使用 AJAX 动态加载图像 tree.png 作为二进制数据,并保存 Last-Modified 标头。定期(下面的代码中每 5 秒一次)。我发出另一个下载请求,使用保存的上次修改标头发送备份 If-Modified-Since 标头。我检查数据是否已返回,并使用数据重新创建图像(如果存在):

<!doctype html>
<html>
<head>
<title>Test</title>
<script>
window.onload = function() {

  let image = document.getElementById('img');
  var lastModified = ''; // 'Sat, 11 Jun 2022 19:15:43 GMT'

  function _arrayBufferToBase64(buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa( binary );
}

  function loadImage()
  {
    var request = new XMLHttpRequest();
    request.open("GET", "tree.png", true);
    if (lastModified !== '')
      request.setRequestHeader("If-Modified-Since", lastModified);
    request.responseType = 'arraybuffer';
    request.onload = function(/* oEvent */) {
      lastModified = request.getResponseHeader('Last-Modified');
      var response = request.response;
      if (typeof response !== 'undefined' && response.byteLength !== 0) {
        var encoded = _arrayBufferToBase64(response);
        image.src = 'data:image/png;base64,' + encoded;
      }
      window.setTimeout(loadImage, 5000);
    };
    request.send();
  }

  loadImage();
};
</script>
</head>
<body>
  <img id="img">
</body>
</html>

Here am loading an image, tree.png, as binary data dynamically with AJAX and saving the Last-Modified header. Periodically (every 5 second in the code below). I issue another download request sending backup a If-Modified-Since header using the saved last-modified header. I check to see if data has been returned and re-create the image with the data if present:

<!doctype html>
<html>
<head>
<title>Test</title>
<script>
window.onload = function() {

  let image = document.getElementById('img');
  var lastModified = ''; // 'Sat, 11 Jun 2022 19:15:43 GMT'

  function _arrayBufferToBase64(buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa( binary );
}

  function loadImage()
  {
    var request = new XMLHttpRequest();
    request.open("GET", "tree.png", true);
    if (lastModified !== '')
      request.setRequestHeader("If-Modified-Since", lastModified);
    request.responseType = 'arraybuffer';
    request.onload = function(/* oEvent */) {
      lastModified = request.getResponseHeader('Last-Modified');
      var response = request.response;
      if (typeof response !== 'undefined' && response.byteLength !== 0) {
        var encoded = _arrayBufferToBase64(response);
        image.src = 'data:image/png;base64,' + encoded;
      }
      window.setTimeout(loadImage, 5000);
    };
    request.send();
  }

  loadImage();
};
</script>
</head>
<body>
  <img id="img">
</body>
</html>
酒绊 2024-08-21 22:52:51

您可以编写一个服务器端方法,该方法仅返回图像资源的上次修改日期,
然后,您只需使用轮询来检查修改日期,如果修改日期大于先前的修改日期,则重新加载。

伪代码(ASP.NET)

//server side ajax method
[WebMethod]
public static string GetModifiedDate(string resource)
{
    string path = HttpContext.Current.Server.MapPath("~" + resource);
    FileInfo f = new FileInfo(path);
    return f.LastWriteTimeUtc.ToString("yyyy-dd-MMTHH:mm:ss", CultureInfo.InvariantCulture);//2020-05-12T23:50:21
}

var pollingInterval = 5000;
function getPathFromUrl(url) {
    return url.split(/[?#]/)[0];
}
function CheckIfChanged() {
    $(".img").each(function (i, e) {
        var $e = $(e);
        var jqxhr = $.ajax({
            type: "POST",
            contentType: "application/json; charset=utf-8",
            url: "/Default.aspx/GetModifiedDate",
            data: "{'resource':'" + getPathFromUrl($e.attr("src")) + "'}"
        }).done(function (data, textStatus, jqXHR) {
            var dt = jqXHR.responseJSON.d;
            var dtCurrent = $e.attr("data-lastwrite");
            if (dtCurrent) {
                var curDate = new Date(dtCurrent);
                var dtLastWrite = new Date(dt);
                //refresh if modified date is higher than current date
                if (dtLastWrite > curDate) {
                    $e.attr("src", getPathFromUrl($e.attr("src")) + "?d=" + new Date());//fool browser with date querystring to reload image
                }
            }
            $e.attr("data-lastwrite", dt);
        });
    }).promise().done(function () {
        window.setTimeout(CheckIfChanged, pollingInterval);
    });
}
$(document).ready(function () {
    window.setTimeout(CheckIfChanged, pollingInterval);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img class="img" src="/img/rick.png" alt="rick" />

rick 已更新图片

You can write a server side method which just returns last modified date of the image resource,
Then you just use polling to check for the modified date and then reload if modified date is greater than previous modified date.

pseudo code (ASP.NET)

//server side ajax method
[WebMethod]
public static string GetModifiedDate(string resource)
{
    string path = HttpContext.Current.Server.MapPath("~" + resource);
    FileInfo f = new FileInfo(path);
    return f.LastWriteTimeUtc.ToString("yyyy-dd-MMTHH:mm:ss", CultureInfo.InvariantCulture);//2020-05-12T23:50:21
}

var pollingInterval = 5000;
function getPathFromUrl(url) {
    return url.split(/[?#]/)[0];
}
function CheckIfChanged() {
    $(".img").each(function (i, e) {
        var $e = $(e);
        var jqxhr = $.ajax({
            type: "POST",
            contentType: "application/json; charset=utf-8",
            url: "/Default.aspx/GetModifiedDate",
            data: "{'resource':'" + getPathFromUrl($e.attr("src")) + "'}"
        }).done(function (data, textStatus, jqXHR) {
            var dt = jqXHR.responseJSON.d;
            var dtCurrent = $e.attr("data-lastwrite");
            if (dtCurrent) {
                var curDate = new Date(dtCurrent);
                var dtLastWrite = new Date(dt);
                //refresh if modified date is higher than current date
                if (dtLastWrite > curDate) {
                    $e.attr("src", getPathFromUrl($e.attr("src")) + "?d=" + new Date());//fool browser with date querystring to reload image
                }
            }
            $e.attr("data-lastwrite", dt);
        });
    }).promise().done(function () {
        window.setTimeout(CheckIfChanged, pollingInterval);
    });
}
$(document).ready(function () {
    window.setTimeout(CheckIfChanged, pollingInterval);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img class="img" src="/img/rick.png" alt="rick" />

rick updated image

南巷近海 2024-08-21 22:52:51

如果您要检查服务器上的文件是否已更改,则必须从服务器发出文件时间的 http 请求,因为一旦页面加载到浏览器,就没有其他方法可以检查文件时间。

这样时间检查脚本就像

filetimecheck.php

<?php
    echo filemtime(string $filename);
?>

然后你可以使用 Javascript 检查文件时间。顺便说一句,我已经放置了 jQuery $.get 来检查文件时间。

dusplayimage.php

<img id="badge" src="image.jpg"> />
<script>
var image_time = <?php echo filemtime(string $filename); ?>;
var timerdelay = 5000;
function imageloadFunction(){
    $.get("filetimecheck.php", function(data, status){
        console.log("Data: " + data + "\nStatus: " + status);
        if(image_time < parseInt(data)) {
            document.getElementById('yourimage').src = "image.jpg?random="+new Date().getTime();
        }
      });

    setTimeout(imageloadFunction, timerdelay);
}

imageloadFunction();

</script>

您将使用对服务器的额外调用来检查文件时间,这是无法避免的,但是您可以使用时间延迟来微调轮询时间。

If you are going to check whether files has changed on the server you have to make http request from the server for the file time, because there is no other way for your check the file time once page get loaded to the browser.

So that time check script will like

filetimecheck.php

<?php
    echo filemtime(string $filename);
?>

Then you can check the file time using your Javascript. BTW I have put jQuery $.get for check the file time.

dusplayimage.php

<img id="badge" src="image.jpg"> />
<script>
var image_time = <?php echo filemtime(string $filename); ?>;
var timerdelay = 5000;
function imageloadFunction(){
    $.get("filetimecheck.php", function(data, status){
        console.log("Data: " + data + "\nStatus: " + status);
        if(image_time < parseInt(data)) {
            document.getElementById('yourimage').src = "image.jpg?random="+new Date().getTime();
        }
      });

    setTimeout(imageloadFunction, timerdelay);
}

imageloadFunction();

</script>

You will be using extra call to the server to check the file time which you can't avoid however you can use the time delay to fine-tune the polling time.

潦草背影 2024-08-21 22:52:51

是的,您可以自定义此行为。即使您的客户端代码几乎没有任何更改。

因此,您将需要一个 ServiceWorker (caniuse 96.59%)。

ServiceWorker 可以代理您的 http 请求。另外,ServiceWorker 已经内置了缓存存储。如果你还没有使用过ServiceWorker,那么你需要详细研究一下。
想法如下:

当请求图片(实际上是任何文件)时,检查缓存。
如果缓存中没有这样的图片,则发送请求并用请求的日期和文件填充缓存存储。
如果缓存包含所需的文件,则仅将文件的日期和路径发送到服务器的特殊 API。
API 立即返回文件和修改日期(如果文件已更新),或者返回文件未更改的响应 {"changed": false}
然后,根据响应,工作线程要么将新文件写入缓存并使用新文件解析请求,要么使用缓存中的旧文件解析请求。

这是一个示例代码(不起作用,但用于理解)

s-worker.js

self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET') return;

  event.respondWith(
    (async function () {
      const cache = await caches.open('dynamic-v1');
      const cachedResponse = await cache.match(event.request);

      if (cachedResponse) {
        // check if a file on the server has changed
        const isChanged = await fetch('...');
        if (isChanged) {
          // give file, and in the background write to the cache
        } else {
          // return data
        }
        return cachedResponse;
      } else {
        // request data, send from the worker and write to the cache in the background
      }
    })()
  );
});

在任何情况下,请查找“使用 ServiceWorker 缓存静态数据的方法”并自行更改示例。

Yes, you can customize this behavior. Even with virtually no change to your client code.

So, you will need a ServiceWorker (caniuse 96.59%).

ServiceWorker can proxy your http requests. Also, ServiceWorker has already built-in storage for the cache. If you have not worked with ServiceWorker, then you need to study it in detail.
The idea is the following:

When requesting a picture (in fact, any file), check the cache.
If there is no such picture in the cache, send a request and fill the cache storage with the date of the request and the file.
If the cache contains the required file, then send only the date and path of the file to the special API to the server.
The API returns either the file and modification date at once (if the file was updated), or the response that the file has not changed {"changed": false}.
Then, based on the response, the worker either writes a new file to the cache and resolves the request with the new file, or resolves the request with the old file from the cache.

Here is an example code (not working, but for understanding)

s-worker.js

self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET') return;

  event.respondWith(
    (async function () {
      const cache = await caches.open('dynamic-v1');
      const cachedResponse = await cache.match(event.request);

      if (cachedResponse) {
        // check if a file on the server has changed
        const isChanged = await fetch('...');
        if (isChanged) {
          // give file, and in the background write to the cache
        } else {
          // return data
        }
        return cachedResponse;
      } else {
        // request data, send from the worker and write to the cache in the background
      }
    })()
  );
});

In any case, look for "ways to cache statics using ServiceWorker" and change the examples for yourself.

时光与爱终年不遇 2024-08-21 22:52:51

警告这个解决方案就像用锤子压碎苍蝇

您可以使用sockets.io将信息拉取到浏览器。

在这种情况下,您需要监视服务器端的图像文件更改,然后如果发生更改,则发出一个事件来指示文件更改。

在客户端(浏览器)端监听该事件,然后每次收到该事件时刷新图像。

WARNING this solution is like taking a hammer to crush a fly

You can use sockets.io to pull information to browser.

In this case you need to monitor image file changes on the server side, and then if change occur emit an event to indicate the file change.

On client (browser) side listen to the event and then then refresh image each time you get the event.

笑红尘 2024-08-21 22:52:51

在 data-src 属性中设置图像源,

并使用 javascript 定期将其设置为带有锚点 (#) 的图像的 src 属性,URL 中的锚点标记不会发送到服务器。

如果图像没有更改,您的网络服务器(apache / nginx)应该响应 HTTP 304,或者如果更改了,则响应正文中的新图像,响应 200 OK

setInterval(function(){ 
  l= document.getElementById('logo'); 
  l.src = l.dataset.src+'#'+ new Date().getTime();
  },1000);
 <img id="logo" alt="awesome-logo" data-src="https://upload.wikimedia.org/wikipedia/commons/1/11/Test-Logo.svg" />


编辑

Crhome 会忽略 http 缓存控制标头,以便后续图像重新加载。

但 fetch api 按预期工作,

fetch('https://upload.wikimedia.org/wikipedia/commons/1/11/Test-Logo.svg', { cache: "no-cache" }).then(console.log);

无缓存指示浏览器始终与服务器重新验证,如果服务器响应 304,则使用本地缓存版本。

set your image source in a data-src property,

and use javascript to periodicaly set it to the src attribute of that image with a anchor (#) the anchor tag in the url isn't send to the server.

Your webserver (apache / nginx) should respond with a HTTP 304 if the image wasn't changed, or a 200 OK with the new image in the body, if it was

setInterval(function(){ 
  l= document.getElementById('logo'); 
  l.src = l.dataset.src+'#'+ new Date().getTime();
  },1000);
 <img id="logo" alt="awesome-logo" data-src="https://upload.wikimedia.org/wikipedia/commons/1/11/Test-Logo.svg" />


EDIT

Crhome ignores http cache-control headers, for subsequent image reloads.

but the fetch api woks as expected

fetch('https://upload.wikimedia.org/wikipedia/commons/1/11/Test-Logo.svg', { cache: "no-cache" }).then(console.log);

the no-cache instructs the browser to always revalidate with the server, and if the server responds with 304, use the local cached version.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文