so, below is a code snippet from my server.js file. When running, and I send a URL with a message, the res.end() causes the view to render a blank page.
When I comment out the res.end() command, the view displays all of the messages, but the browser waits and waits for the signal that the response from the server is complete.
I get that you can use res.end() and put data in the parens, to be transmitted and rendered by the view.
What I expect to happen is that with no args, it will just leave the view alone, but the empty args in the parens is manifesting as an empty view.
How do I indicate that the response is complete without deleting the data on the view?
server.js
var http = require('http'),
url = require('url'),
fs = require('fs');
var messages = ["testing"];
var clients = [];
http.createServer(function(req,res) {
var url_parts = url.parse(req.url);
console.log(url_parts);
if(url_parts.pathname == '/') {
// file serving
fs.readFile('./index.html', function(err, data) {
// console.log(data);
res.end(data);
});
} else if(url_parts.pathname.substr(0,5) == '/poll'){
//polling code
var count = url_parts.pathname.replace(/[^0-9]*/,'');
console.log(count);
if(messages.length > count){
res.end(JSON.stringify({
count: messages.length,
append: messages.slice(count).join("\n")+"\n"
}));
} else {
clients.push(res);
}
} else if(url_parts.pathname.substr(0, 5) == '/msg/') {
// message receiving
var msg = unescape(url_parts.pathname.substr(5));
messages.push(msg);
while(clients.length > 0) {
var client = clients.pop();
client.end(JSON.stringify({
count: messages.length,
append: msg+"\n"
}));
}
// res.end(); //if left in, this renders an empty page, if removed,
// client keeps waiting....
}
}).listen(8080, 'localhost');
console.log('server running!');
index.html
<html>
<head>
<script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script>
var counter = 0;
var poll = function() {
$.getJSON('/poll/'+counter, function(response) {
counter = response.count;
var elem = $('#output');
elem.text(elem.text() + response.append);
//elem.text(counter);
poll();
});
}
poll();
</script>
</head>
<body>
<textarea id="output" style="width: 90%; height: 90%;">
</textarea>
</body>
</html>
I have looked in the docs, but I don't see anything specific about using .end() method with empty args to signify and end without passing data to be rendered. I have googled this, but I don't have an answer yet.
Do a res.json({success:"true"}) instead. The reason being is because res.end inherently thinks the client was sent a view prior to the stream being closed. With res.json() you can send any generic data, without an implied view being expected as well as close out the stream on client and server side.
Move res.end() inside while loop
while (clients.length > 0) {
var client = clients.pop();
client.end(JSON.stringify({
count : messages.length,
append : msg + "\n"
}));
if(!clients.length) {
res.end();
}
}
My understand of your problem is:
You have an HTML page (index.html), which has a textarea displaying all messages submitted by user. After one message is received and displayed, it will send the request for next message immediately (/poll/<n>).
To accept user's input for latest message, you open an API (/msg/<message>). When an HTTP request is sent to this API, server will extract the message, and return this message to /poll/<n> sent in step 1.
However, as HTML page (index.html) and the request to /msg/<message> happens in the same browser window, you can't let the http handler of /msg/<message> in node.js invoke res.end(), because in that case, the browser window will render the HTTP response of /msg/<message> request (blank page). Actually, you can't make the res return 200 OK, whatever data it returns. You can't make res fail the /msg/<message> request either (using req.destroy()), because in that case the browser window will render a failure/broken page, which is worse.
Unfortunately, you can't make res of /msg/<message> in node.js keep pending either. Although it will update index.html, the browser window will keep waiting...
The root cause of your problem is: browser window resource conflict between index.html and /msg/<message> response -- as long as /msg/<message> request is sent by using index.html window's URL bar, whenever its response is sent back, the window content (index.html) will be cleaned.
One solution is: using Ajax to send /msg/<message>. In this way, there would be no conflict for window resource. Example code is listed below:
<body>
<textarea id="output" style="width: 90%; height: 90%;">
</textarea>
<div>
<input type="text" id="msg">
<button type="button" onclick="submitMsg()">Submit</button>
</div>
</body>
window.submitMsg = function() {
var msg = $('#msg').val();
$.getJSON('/msg/' + msg, function(res) {
$('#msg').val('');
console.log('message sent.');
});
}
EDIT:
Another simpler solution is: open index.html in one browser window, and open /msg/<message> in another one (use res.end('message received successfully') to indicate message receiving result).
Related
I'm trying to use server-side events (SSE) in Javascript and Node.JS to push updates to a web client.
To keep things simple, I have a function which will generate the time every second:
setTimeout(function time() {
sendEvent('time', + new Date);
setTimeout(time, uptimeTimeout);
}, 1000);
The sendEvent function puts together the event in the expected format and sends it to the client.
var clientRes;
var lastMessageId = 0;
function sendEvent(event, message) {
message = JSON.stringify(message);
++lastMessageId;
sendSSE(clientRes, lastMessageId, event, message);
}
The clientRes value comes from the server function to handle the route from the base URL.
app.use('/', function (req, res) {
clientRes = res;
...
}
What I want to achieve at the client UI is a simple page which shows:
> <h1>The current time is {event.data}</h1>
where I derive the current time from the latest message data received from the server.
I have created an index.html file to have the client listen for these server-sent messages:
<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>
<script>
if(typeof(EventSource) !== "undefined") {
console.log("Event source is supported");
var source = new EventSource("localhost:3000");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += "=>" + event.data + "<br>";
};
} else {
console.log("Event source not supported");
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
evtSource.addEventListener("time", function(event) {
const newElement = document.createElement("li");
const time = JSON.parse(event.data).time;
console.log("Time listener found time " + time);
newElement.innerHTML = "ping at " + time;
eventList.appendChild(newElement);
});
</script>
</body>
</html>
If I respond to a GET request with this index.html, I don't see any of the time messages.
That is, this server code does not work:
app.use("/", function(request, response) {
response.sendFile(__dirname + "/index.html");
clientRes = response;
});
However if I don't respond with the index.html file and allow the server to push timestamps to the client, they to show up in the browser:
event: time
id: 104
data: 1587943717153
event: time
id: 105
data: 1587943727161
...
Here's is where I'm stuck.
It appears I have successfully gotten the server to push new timestamps every second.
And the browser is seeing them and displaying the text.
But the arrival of the message from the server is not triggering the listener and the message is not being rendered based on the index.html.
Most of the examples I've seen for use of SSE involves a PHP data source. I need for the server to both generate the data and to provide the HTML to display it.
I've been successful in one or the other, but not both at the same time.
I figured out what I was missing.
I did not specify the endpoints correctly.
For the root endpoint, the server code needs to deliver the index.html file.
app.use("/", function(request, response) {
console.log("In root handler");
response.sendFile(__dirname + "/index.html");
});
Index.html contains the script that creates the event source:
var source = new EventSource("http://localhost:3000/time");
But the URL that gets passed in as the input to the EventSource constructor must be a different endpoint (not root). It needs to be the endpoint that generates the timestamps.
So in the server, the handler for the /time endpoint is the one which pushes the data.
app.use('/time', function (req, res) {
res.writeHead(200, {
'content-type': 'text/event-stream',
'cache-control': 'no-cache',
'connection': 'keep-alive'
});
// Save the response
clientRes = res;
});
I'm improving a system for controlling a telescope remotely. A Raspberry Pi runs flask, and provides a video stream for a camera attached to the telescope. The telescope's focuser is actuated by a stepper motor controlled with an Arduino.The server provides a website that shows the video stream, and offers two buttons to move the focuser in and out.
When either button is clicked, the client sends a POST to the RasPi, and then the RasPi tells the Arduino to move the focuser. But crucially I did not want the page to refresh while refocusing. Hence, I used jQuery and Ajax to suppress the page refresh.
The relevant code snippets are here:
Python/Flask code:
#app.route('/stream/<wcam>', methods=['GET'])
def stream_get(wcam):
class FocuserForm(FlaskForm):
nsteps = IntegerField('# steps: ', default=1)
focuser_in = SubmitField('Focuser in')
focuser_out = SubmitField('Focuser out')
form = FocuserForm()
return render_template('stream.html', wcam=wcam, form=form)
#app.route('/stream/<wcam>', methods=['POST'])
def stream_post(wcam):
results = request.form
arduino_serial = SerialFocuser()
if results['caller'] == "focuser_in":
command = "MVD" + results['steps'] + "\n"
arduino_serial.send_command(command)
elif results['caller'] == "focuser_out":
command = "MVU" + results['steps'] + "\n"
arduino_serial.send_command(command)
return ''
Web (stream.html):
<html>
<head>
<title>Video Streaming</title>
<style>
...
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$(document).ready(function() {});
</script>
</head>
<body>
<h1>Streaming camera {{ wcam }}</h1>
<br>
<img id="bg" src="{{ url_for('video_feed', wcam=wcam) }}", height="480" width="640">
Back
<br>
<!--######################################################-->
<!--# Focuser handling -->
<!--######################################################-->
<br>
<form id="flaskform" method="POST">
<p>
{{ form.nsteps.label }} {{ form.nsteps() }}
{{ form.focuser_in() }}
{{ form.focuser_out() }}
</p>
</form>
<script>
// $(document).ready(function() { // Moved to header
var form = document.getElementById('flaskform');
function onSubmit(event) {
console.log('onSubmit function');
var objectID = event.explicitOriginalTarget.id;
var nsteps = form.nsteps.value;
var return_data = {caller: "", steps: nsteps};
if (objectID == "focuser_in") {
return_data.caller = objectID;
console.log("Focuser_in detected");
} else if (objectID == "focuser_out") {
return_data.caller = objectID;
console.log("Focuser_out detected");
} else if (objectID == "nsteps") {
console.log("nsteps detected");
event.preventDefault();
return;
} else {
console.log("No matches");
return;
}
console.log("About to run Ajax");
$.ajax({
url: "stream.html",
type: "post",
data: return_data,
success: function(response) {
console.log('It worked!');
},
error: function(xhr, status, text) {
console.log('An error occurred:', status,"; ", text);
},
timeout: 1000 // 1s
}); // Ajax
console.log("After running Ajax");
if (event) { event.preventDefault(); }
}
// prevent when a submit button is clicked
form.addEventListener('submit', onSubmit, false);
//<!--form.addEventListener('submit', onSubmit, false);-->
// prevent submit() calls by overwriting the method
form.submit = onSubmit;
//}); // Moved to header
</script>
</body>
</html>
The problem is as follows:
If I refresh the page on the client's browser and then click a button, ajax does POST, but flask does not seem to receive it. The request times out.
If I now restart the server (I'm developing this with PyCharm, so I just click re-run) without refreshing the page in the client, and then click a button, flask does get the POST, and the focuser works like a charm.
If I refresh the page again, then the buttons stop working until I reset the server.
Why does this happen? Obviously the code works in its main purpose, but somehow the page refresh is breaking something.
I had a similar issue once with a camera thread blocking all calls. When you reset the server, does your camera feed still run (before clicking the button)?
Because basically you are calling your camera feed twice - first with the get call when you refresh your page, then again with the post call.
I'd advice you to refactor your submitted code into an alternative function for clarity:
#app.route('/stream/<wcam>', methods=['POST'])
def moveCommand:
if form.is_submitted():
# POST method
results = request.form
arduino_serial = SerialFocuser()
if results['caller'] == "focuser_in":
command = "MVD" + results['steps'] + "\n"
arduino_serial.send_command(command)
elif results['caller'] == "focuser_out":
command = "MVU" + results['steps'] + "\n"
arduino_serial.send_command(command)
So basically you keep your get method for only the streaming and use the post for the moving around.
Thanks to #Peter van der Wal for pointing me towards the solution.
The video streamer has a while True loop, which continually takes frames from the camera, hence locking the thread.
The solution was to start the app with the threaded option on:
Before:
app.run(host='0.0.0.0', debug=True)
Now:
app.run(host='0.0.0.0', debug=True, threaded=True)
This allows the video streaming thread to continue on its own, while allowing the server to process other commands.
New to JavaScript and Node.js
I have a setup where I have a raspberry pi running Node.js. The raspberry pi is connected to some embedded device through a USB to UART connection with the USB plugged into the raspberry pi. I can send and receive data at this base level just fine. The pi is connected to a router and I access it through it's IP and a browser.
I want to host a simple webpage that has a title, some text, and a button. When I click the button I want my client machine to contact the node.js server and make the pi send a message(already have a message format I am required to use) over the serial port to the embedded device. I want to wait/or not(depends on suggestions) for data to be sent back and then use that data to repopulate the text on the webpage.
What I have is close to this but not complete.
I run a 'server' on node.js off the pi. It uses express and a static page. The static page has a client side JavaScript file that executes a AJAX request when the button is clicked. On the node.js side I have express able to see the AJAX request. I then construct and send my message over serial port to the embedded device using serialport. At this point, on the Node.js side I can send back a string of text/etc. that can be displayed by the webpage but don't know how to somehow wait or other wise receive the data and send it to the webpage for displaying.
Client .html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Request Sensor Data</title>
<style type="text/css" media="screen"></style>
</head>
<body>
<p>Sensor Data</p>
<p><TEXTAREA id="myTxtArea" NAME="sensorDataTxtBox" ROWS=3 COLS=30 ></TEXTAREA></br>
<button type="button" name="sensorButton" id="mySensorButton" onClick="getSensorData()" >Get Sensor Data</button></p>
<script src="clientCode.js"></script>
</body>
</html>
Client .js:
function getSensorData()
{
console.log('getSensorData() button pushed.');
var xhr = new XMLHttpRequest();
xhr.open('GET', 'sensorGET');
xhr.send(null);
xhr.onreadystatechange = function () {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE)
{
if (xhr.status === OK)
{
//insert DOM grabs to set text in html textbox.
console.log(xhr.responseText); // 'This is the returned text.'
var textAreaDOM = document.getElementById('myTxtArea');
textAreaDOM.value = textAreaDOM.value + 'inserted sensor data here\n';
}
else
{
console.log('Error: ' + xhr.status); // An error occurred during the request.
}
}
};
}
node.js .js:
var express = require('express'),
app = express();
app.use('/', express.static(__dirname + '/'));
app.get('/sensorGET', function (req, res) {
var sensorData = getSensorData();
res.send('sensorData');
})
var serialport = require('serialport'),
portname = '/dev/ttyUSB0';
var myPort = new serialport(portname, {
baudRate: 115200,
dataBits: 8,
parity: 'none',
stopBits: 1,
flowControl: false,
parser: serialport.parsers.byteLength(1)
});
myPort.on('open', showPortOpen);
myPort.on('data', recSerialData);
myPort.on('close', showPortClosed);
myPort.on('error', showError);
myPort.on('disconnect', showDisconnect);
function showDisconnect() {
console.log('Someone disconnected');
}
function showPortOpen()
{
console.log('port open. Data rate: ' + myPort.options.baudRate);
}
function recSerialData(data)
{
parseMessage(data);//This function is not shown but parses a message that is sent on the wire
}
function showPortClosed()
{
console.log('port closed.');
}
function showError(error)
{
console.log('Serial port error: ' + error);
}
function getSensorData()
{
myPort.write(Assume correct message is sent here);
//Can return some set text here and it will be written to the webpage.
//example: return "Temp data was asked for...";
//is there a way to wait here for the next message that comes in?
}
Probably the simplest thing will actually be to use something like socket.io and just send the data to the browser with that after every parseMessage. Because for starters if you try to make http wait for all serial data it will likely timeout, and the way things work its just easier to send every time you get a new data event from the serial port.
I created a website using flask with a running sqlite-db (SQLAlchemy). I want to send an integer with javascript to flask and back. I know AJAX can be used to accomplish that, but I don't know how to send the integer whenever my if/else-statement within my javascript game is met.
games.html
if (loc == unicornloc) {
money = 5000;
alert("\n\nBRAVO! You found the Unicorn! :)");
}else {
money = -250;
alert("The unicorn isn't here :(")
}
<FORM method="POST" name="searchb">
<input type=checkbox onClick="javascript:search('x1y1');">
<input type=checkbox onClick="javascript:search('x1y2');">
<input type=checkbox onClick="javascript:search('x1y3');">
games.py
#app.route('/games/<money>',methods=['GET'])
#login_required
def games(money):
print(request.args.get('money', type=int))
return render_template('games.html',money)
I want to get the money-value to flask, calculate a new value, pass it to my db and show the updated value on my website without reloading the page.
first set up jquery in your html.
make sure that the jquery is included in your head section of the html page:
You won't need to submit a form to update the server it is enough if you put a listener on one of your buttons that sends an ajax request every time it is clicked:
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
var sendServerNotification = function(status){
var urlToCall = '/games/' + status;
$.ajax({
url : urlToCall, // the endpoint
type : "GET", // http method
// handle a successful response
success : function(parentDescriptions) {
console.log('success'); // log the returned json to the console
// update the page with jquery
},
// handle a non-successful response
error : function(xhr,errmsg,err) {
console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console
}
});
}
$( document ).ready(function() {
$('#obsevervedbutton').click(function{
//we read the value that the user wants to submit:
var valueToSubmit = $('#valueToSubmit').val()
// here you can check if you want to submit the value
// or not
if(true){
sendServerNotification(valueToSubmit);
}
});
});
</script>
</head>
<body>
<button id="obsevervedbutton">notify server</button>
<input id="valueToSubmit"></input>
</body>
and on your server side it is important to return a json response instaed of a normal http response to finish the ajax request and invoke either the success or error url:
def games(money):
print(request.args.get('money', type=int))
# update the database here
return json.dumps({"result":"operation successfull!"})
I hope this will get you going.
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?
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.
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.
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>
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>
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" />
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.
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.
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.
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.