1

I wasn't in charge of the Apache configuration, so I'm not sure what I can provide in terms of useful conf text, but I'm fairly certain I have narrowed the problem down to the login. EventSource works flawlessly both locally on XAMPP without any login and once you refresh the page after authenticating on the production server, but that first load on the server just will not open a connection. Has anyone seen this problem before? I couldn't find anything on the internet about this after searching for the past few days.

Edit: Some code

Some of the server-side code (which mostly shouldn't be relevant):

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

$client_stream = new RedisStream();
$client_stream->poll(1); //The loop, with sleep time as a parameter

The JavaScript:

var xhttpViewSet;
var xhttpSearch;
var view = 'tile';
var search = '';

var seed_url = '/core/seed_view.php';
var stream_url = '/core/stream.php';

var default_class = 'panel-default';
var success_class = 'panel-success';
var warning_class = 'panel-warning';
var danger_class = 'panel-danger';

function UpdateClient(c_name, c_obj) {
    if ((c_element = document.getElementById(c_name)) !== null) {
        c_element.classList.remove('text-muted');

        c_element.classList.remove(default_class);
        c_element.classList.remove(success_class);
        c_element.classList.remove(warning_class);
        c_element.classList.remove(danger_class);

        switch (c_obj['status']) {
            case 0:
                c_obj['status'] = 'OK';
                c_element.classList.add(success_class)
                break;
            case 1:
                c_obj['status'] = 'Warning';
                c_element.classList.add(warning_class)
                break;
            case 2:
                c_obj['status'] = 'Critical';
                c_element.classList.add(danger_class)
                break;
            default:
                c_obj['status'] = 'Unknown';
                c_element.classList.add(danger_class)
                break;
        }

        for (i in c_obj) {
            var var_nodes = c_element.getElementsByClassName(i);
            if (var_nodes.length > 0) {
                for (var j = var_nodes.length - 1; j >= 0; j--) {
                    var_nodes[j].innerHTML = c_obj[i];
                }
            }
        }
    }
}

function SetView() {
    var view_url = seed_url + '?search=' + search + '&view=' + view;

    xhttpViewSet.open('GET', view_url, true);
    xhttpViewSet.send();
}

var main = function() {
    container = document.getElementById('content');

    if (new XMLHttpRequest()) {
        xhttpViewSet = new XMLHttpRequest();
        xhttpSearch = new XMLHttpRequest();
    } else {
        xhttpViewSet = new ActiveXObject('Microsoft.XMLHTTP');
        xhttpSearch = new ActiveXObject('Microsoft.XMLHTTP');
    }

    var stream = new EventSource(stream_url);
    stream.onopen = function() {
        console.log('Connection opened.'); //This doesn't fire
    }

    stream.onmessage = function(e) {
        var c_obj = JSON.parse(e.data);
        UpdateClient(c_obj.name, c_obj.value);
    };

    xhttpViewSet.onreadystatechange = function() {
        if (xhttpViewSet.readyState == 4) {
            var resp = xhttpViewSet.responseText;
            if (xhttpViewSet.status == 200 && resp.length > 0) {
                container.innerHTML = resp;
                if (view == 'list') {
                    $('#computer-table').DataTable({
                      "lengthMenu": [[25, 50, 100], [25, 50, 100]]
                    });
                }
            } else {
                container.innerHTML = '<error>No computers matched your search or an error occured.</error>';
            }
        }
    }
    SetView(); //This successfully does all but make the EventSource connection, and only fails to do that on first load

    document.getElementById('list-view').addEventListener('click', function() {
        view = 'list';
        SetView();
    });

    document.getElementById('tile-view').addEventListener('click', function() {
        view = 'tile';
        SetView();
    });

    document.getElementById('search').addEventListener('keyup', function() {
        search = this.value.toUpperCase();
        SetView();
    });

    document.getElementById('clear-search').addEventListener('click', function() {
        document.getElementById('search').value = '';
        search = '';
        SetView();
    });
};

window.onload = main;
  • You're going to need to provide some more details. e.g. client code, server code, the URLs you use... something to go on. Actually, even more important: what do you see happening in the console? Test in at least two browsers, using the developer tools. Pay attention to what redirects happen, what headers get sent, etc. – Darren Cook Aug 13 '16 at 08:39
  • @DarrenCook I'll update to include the front- and back-end code, but as for what's happening in the console: nothing interesting at the moment. For simple debugging and connection counting I have been logging "Connection opened.", and that is not shown immediately after logging in, so I suspect it's either the "onopen" that's not working or the creation of the EventSource object itself; it was--and should still be--working as is. – Hawkens VonShriek Aug 14 '16 at 17:24
  • Do you use PHP sessions? Other than login, what else is different on the production system vs. the local XAMPP system where it works. As for "nothing interesting" in the debug console, all possible outcomes are interesting: E.g. either you see the auth header being sent, which tells you one thing, or you don't see it, which tells you another thing :-) – Darren Cook Aug 14 '16 at 20:30
  • I do use sessions, though the annoying weirdness of this is that the call to seed_view.php in the same directory works and creates the view, while the stream.php does not, and they both make use of the session variable "keys". I'm not sure about the differences of the production server as I did none of the configuration. I could do some more digging and talk more in depth with the person who did, but I'm starting to think that this will probably be a waste of your time :P It baffles me; just logged in=broken, refresh the page=fine. So much wtf! – Hawkens VonShriek Aug 14 '16 at 20:45

1 Answers1

1

It is a bit hard to know for sure without a lot more information, but based on what you have said so far, I think it is one of:

HEAD/OPTIONS: Some browsers will send a HEAD or OPTIONS http call to a server script, before they send the GET or POST. The purpose of sending OPTIONS is to ask what headers are allowed to be sent. It is possible this is happening as part of the login process; that might explain why it works when you reload. See chapter 9 of Data Push Apps with HTML5 SSE (disclaimer: my book) for more details; basically, at the top of your SSE script you need to check the value of $_SERVER["REQUEST_METHOD"] and if it is "OPTIONS", intercept and say what headers you want to accept. I've used this one before:

header("Access-Control-Allow-Headers: Last-Event-ID,".
  " Origin, X-Requested-With, Content-Type, Accept,".
  " Authorization");`

CORS: The HTML page URL and the SSE page URL must have identical origins. There are detailed explanations (specific to SSE) in chapter 9 of Data Push Apps with HTML5 SSE (again), or (less specifically) at Wikipedia. If this is the problem, look into adding header("Access-Control-Allow-Origin: *"); to your SSE script.

withCredentials: There is a second parameter to the SSE constructor, and you use it like this: var stream = new EventSource(stream_url, { withCredentials: true }); It is saying it is okay to send the auth credentials. (Again, chapter 9 of the book goes into more detail - sorry for the repeated plugs!) There is a second step, over on the server-side: at the top of your PHP SSE script you need to add the following.

header("Access-Control-Allow-Origin: ".@$_SERVER["HTTP_ORIGIN"]);
header("Access-Control-Allow-Credentials: true");

PHP Sessions locking: This normally causes the opposite problem, which is that the SSE script has locked the PHP session, so no other PHP scripts work. See https://stackoverflow.com/a/30878764/841830 for how to handle it. (It is a good idea to do this anyway, even if it isn't your problem.)

Community
  • 1
  • 1
Darren Cook
  • 27,837
  • 13
  • 117
  • 217
  • Interesting; I'll try some of these and get back to you. Now that you've said it I'm thinking it probably is header related, but I won't know for sure for at least a few days, so I'm not going to accept the answer yet--though I will when I can. – Hawkens VonShriek Aug 15 '16 at 18:16