Reputation: 1409
I am trying to continuously stream output on my flask app from my server to client (webapp). I am able to successfully stream output using by opening an ajax request and leaving it open like this:
//Open up a stream
let url = '/stream';
xhr.open('GET', url);
xhr.send(fd);
//Display it like this
data = xhr.responseText;
$(".myTextArea").val(data);
I can close this ajax request by doing this (but i'm leaving it open):
xhr.readyState == XMLHttpRequest.DONE
//do stuff
But I can also use websockets (flask_socketio
) to continuously poll server output stream to the client.
Are these two methods of polling the same in terms of performance?
Here is how i am collecting the AJAX stream from remote server:
let streamTimer;
function stream(){
let xhr;
let streamData;
let lastStreamData;
xhr = new XMLHttpRequest();
const url = '/stream';
xhr.open('GET', url);
xhr.send();
let streamTimer = setInterval(function() {
streamData = xhr.responseText;
if (lastStreamData !== streamData) {
console.log(streamData)
lastStreamData = streamData;
}
}, 1000);
}
My ajax calls my flask route stream()
which basically tails a log with the -f
option, that way it will keep the connection open. If there is output to the log then it will send it back to my ajax call and display it on the webpage.
def stream():
def generate():
if request.method == "POST":
hostname = request.data.decode('utf-8')
# ssh set up
client = set_up_client()
# Connect to hostname
client.connect(hostname, username=USERNAME,
password=PASSWORD)
cmd = ('tail -f -n0 /home/server.log')
stdin, stdout, stderr = client.exec_command(cmd)
for line in iter(lambda: stdout.readline(2048), ""):
print(line, end="")
yield line
if re.search(r'keyword1|keyword2', line):
print('change detected')
yield 'change detected\n'
return Response(stream_with_context(generate()), mimetype='text/html')
Upvotes: 0
Views: 513
Reputation:
Are these two methods of polling the same in terms of performance?
No they are not the same. You may not be sending a request to the server every X milliseconds, but you are polling the XMLHttpRequest#responseText
getter every X milliseconds, which is overhead no matter how you look at it.
The good news is that you don't need an interval at all because the XMLHttpRequest#onprogress
event handler is called every time data is sent from the server. Just move the content of your interval into an onprogress
event handler for the request and you're good to go.
let data;
const request = new XMLHttpRequest();
request.open('GET', '/stream');
request.onprogress = function() {
if(data === request.response) return;
data = request.response;
console.log(data);
};
request.send();
There is no polling in this example. The client simply handles progress
events by updating the target element accordingly. If no updates are received for a while, we aren't running an interval every second just to check, so no harm done.
Is this the same performance as a socket? I don't have an answer for that, but I'm willing to bet that it is pretty darn close. Close enough that I would say "reasonably similar".
Now the one glaring difference I see between this method and a socket is that sockets enable two-way communication, whereas I have not been able to replicate sending data to the server more than once using the same request. It may be possible, but I haven't figured it out yet.
Further abstraction
You could abstract this by implementing a class that extends XMLHttpRequest
like so:
class ChunkedResponseRequest extends XMLHttpRequest {
constructor(...args) {
super(...args);
this.data = null;
this.addEventListener('progress', _ => {
if(this.data === this.response) return;
const chunk = this.data === null ? this.response
: this.response.substr(this.data.length);
this.data = this.response;
this.dispatchEvent(new MessageEvent('message', { data: chunk }));
});
}
}
Then you could use it like this:
const request = new ChunkedResponseRequest();
request.addEventListener('message', function({ data }) {
console.log(data);
});
request.open('GET', '/stream');
request.send();
The value of the message event's data
property will be the last response from the server.
Implementation of a chunked response
For those who would like to reproduce this behavior using Node.js (I personally don't work with Python or Flask), here's a quick example of a server that responds with an infinite number of chunks spaced one second apart:
const http = require('http')
const requestHandler = (request, response) => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.setHeader('Access-Control-Allow-Origin', '*'); // so we can access locally
response.setHeader('Transfer-Encoding', 'chunked');
(function send(i, delay) {
setTimeout(_ => {
response.write(i + '\r\n');
send(i + 1, 1000);
}, delay)
})(0, 0);
}
const server = http.createServer(requestHandler)
server.listen(3000, (error) => error && console.log(error));
Make sure to set the URL of the XMLHttpRequest
in the client side code to http://localhost:3000
where 3000
is the port that the server is listening on. Obviously if you already have something listening on port 3000
, you'll want to select an unused port.
Upvotes: 1
Reputation: 3156
No for the ajax polling you have to continuously hit the server to get the data, but in case of web socket you it's two way communications server can also connect with you. If something have it will directly send you the data.
Upvotes: 0