lion_bash
lion_bash

Reputation: 1409

Difference between polling using ajax request and websockets

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

Answers (2)

user4639281
user4639281

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

aman kumar
aman kumar

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

Related Questions