Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firefox randomly close XMLHttpRequest connection if inactive. Why?

In a JavaScript class, an XMLHttpRequest connect to the server. The server is sending data, slowly. This work fine in Chromium, but Firefox close the connection after random time (between ~4s and ~70s).

Why Firefox close the connection? and How to avoid that?

Simplified JS code:

    var options = {};
    options['header']=
        { 'Cache-Control':'no-cache, max-age=0', 
            'Content-type': 'application/octet-stream',
            'Content-Disposition': 'inline'
        };

    // Get request information
    this.http = new XMLHttpRequest();
    this.http.onreadystatechange = _streamingResponse.bind(this);
    this.http.open('post', url, true);
    for (var i in options['header'])
    {
        this.http.setRequestHeader(i, options['header'][i]);
    }
    this.http.send('');

for the PHP part, something like:

sleep(200); //wait long time, so firefox close the socket.

If the server send something every few seconds (<5s) the connection stay alive "forever". But if no data is sent, Firefox close the connection.

The connection close with: - readyState = 4 - status = 0

The server seem to be correct, as in Chromium it work correctly.

Full test code:

test.html

<html>
<header>
</header>
<body>
</body>

<script type="application/javascript">

    function log( msg )
    {
        document.body.appendChild(document.createElement('div').appendChild(document.createTextNode(msg)));
        document.body.appendChild(document.createElement('br'));
    }

    function request(url)
    {
        function _streamingResponse()
        {
            if (4==this.http.readyState)
            {
                log('Done: ' + this.http.status);
            }
            else if (3==this.http.readyState)
            {
                var text = this.http.response.substr(this.lastRequestPos);
                this.lastRequestPos = this.http.response.length;
                log('Update: ' + text);
            }
        }

        var options = {};
        options['header']=
            { 'Cache-Control':'no-cache, max-age=0', 
                'Content-type': 'application/octet-stream',
                'Content-Disposition': 'inline'
            };

        this.lastRequestPos=0;

        // Get request information
        this.http = new XMLHttpRequest();
        this.http.onreadystatechange = _streamingResponse.bind(this);
        this.http.open('post', url, true);
        for (var i in options['header'])
        {
            this.http.setRequestHeader(i, options['header'][i]);
        }
        this.http.send('');
        log('Request sent!');
    }

    req = new request('./test.php');
</script>
</html>

test.php

<?php

$timer = 60;

ignore_user_abort(true);
set_time_limit(0);

// Turn off output buffering and compression
ini_set('output_buffering', 'off');
ini_set('zlib.output_compression', false);
ini_set('implicit_flush', true);
ob_implicit_flush(true);
while (ob_get_level() > 0) {
    $level = ob_get_level();
    ob_end_clean();
    if (ob_get_level() == $level) break;
}
if (function_exists('apache_setenv')) {
    apache_setenv('no-gzip', '1');
    apache_setenv('dont-vary', '1');
}

// Set header for streaming
header('Content-type: application/octet-stream');
flush();

// Send information
sleep($timer);
echo '<yes></yes>';
flush();

?>

Additional note: Firefox 43.0.03, Chromium 47.0.2526

EDITED:

Setting a callback for timeout it do not trigger. I conclude it is not a timeout.

this.http.timeout = 2000;
this.http.ontimeout = _streamingTimeout.bind(this);
like image 778
Adrian Maire Avatar asked Jan 16 '16 00:01

Adrian Maire


2 Answers

After searching further, I found a Bug in Mozilla which seem the responsible of this behaviour. It should be solved in the version 45, but until then, we have to lead with it.

Even if the bug seem related only to Ipv6, I have the same issue using 127.0.0.1. However, with Firefox Developer Edition (V 45) the problem seem solved.

Why Firefox close the connection?

It should not. ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1240319

How to solve it?

Except to send data every 3-4 seconds to maintain the connection open, I have no idea.

like image 143
Adrian Maire Avatar answered Oct 20 '22 13:10

Adrian Maire


Sounds an awful lot like garbage collection.

I see you have tried timeout. But I'm not sure I understand your conclusion: Setting a callback for timeout it do not trigger.

Just be absolutely certain that you follow these rules:

4.2 Garbage collection

An XMLHttpRequest object must not be garbage collected if its state is either opened with the send() flag set, headers received, or loading, and it has one or more event listeners registered whose type is one of readystatechange, progress, abort, error, load, timeout, and loadend.

If an XMLHttpRequest object is garbage collected while its connection is still open, the user agent must terminate the request.

xhr.spec.whatwg.org

Check which state, send() flag and event listeners are set while no data is sent.

like image 33
Payne Avatar answered Oct 20 '22 13:10

Payne