freefall has asked for the wisdom of the Perl Monks concerning the following question:

I am developing a Perl chat server designed to push events (JS commands) back to the browser for real-time updates and reduced server load from polling. Currently I am at a dead stop with IE. I am using a Comet implementation modified slightly from this page: http://empireenterprises.com/_comet.html

I am looking for anyone who knows why IE is not executing the Javascript commands sent by the server (alert('a' + 0) for example) when they are loaded in an iframe (and even when they are not loaded in an iframe).

The browser connects to the push server via a long-lived XMLHTTPRequest (Firefox/Opera) or an htmlfile ActiveXObject (IE) as seen in the example. The server stores the HTTP browser socket connection and sends commands back to it for each of the chat channels that it is subscribed to. This works fine in Opera and Firefox, but IE does not handle the streaming properly.

I included a very simple, self-contained, and well-documented implementation of the push technology used at the link above. The script is a simple Perl HTTP server listening on port 9090 (this is a very simple mimic of the environment of my actual application). The primary difference is that my server is an HTTP server, not a mod_perl script (using persistent Apache connections for this application is not acceptable). Here are my instructions for the provided code and what I've tried so far:

Once the Perl command line is running the script below, connect to http://localhost:9090 in Firefox. The Perl HTTP server will print the page which will then connect back to the HTTP server (via http://localhost:9090/?comet) and stream the content. The content generated by the server is three simple alert() calls which each include a 'a', 'b', or 'c' and random number for reference, these are also shown in the server command line. Firefox will print these messages as they are generated by the server, but IE will not print them until the server closes the socket and the full DOM has been constructed.

The most curious thing I have found is that if you go to http://localhost:9090/?comet in IE, the first time the alert()s will not show up until the script has finished. However, if you refresh the page, the alert()s will come in at the proper time. I am hoping that this is a good signal to someone of what I am doing wrong.

Have I implemented this sample HTTP server incorrectly? Am I printing something wrong? Am I completely off track?

I've tried using $client->send() instead of print $client, sending w/ and w/o endlines, sending the OK header and not, sending <html><body> before the script tags, along with many other miscellaneous ideas and combinations, but maybe just not quite the right one.

Many thanks to anyone who can take a look at this and help figure out the problem!
- Ian Paterson

Code...
#!/usr/bin/perl -- # IE streaming server-push problem demonstration ##################### +########## ###################################################################### +########## # This script is meant to demonstrate strange IE behavior when working + with # streaming data. When directed to http://localhost:9090/, the browse +r should # print a page which simply displays the following: # # Awaiting commands... # Connecting... (script should start alerting soon) # Executing command: alert('a' + 0) # Executing command: alert('b' + 0) # Executing command: alert('c' + 1) # Disconnected. # Connecting... (script should start alerting soon) # # Each command should be accompanied by an alert box with the specifie +d message. # The script should print these messages in Firefox, but in Internet E +xplorer, # the printing of Executing command: is not expected to occur since IE + is not # expected to allow access to the responseText until the server has cl +osed the # socket. It /should/, however, signal alerts, but this is not happen +ing. # # After the server prints the three commands, it closes the socket. T +he # client re-establishes the connection and it begins again. Only at t +his point # does IE print the alert messages. use IO::Socket; use strict; $| = 1; my $sock = new IO::Socket::INET( LocalPort => 9090, Proto => 'tcp', Listen => SOMAXCONN, Reuse => 1 ); while (my $client = $sock->accept) { print "\nReceived a connection\n"; my $request = <$client>; # PRINT THE COMET SCRIPT SNIPPETS ################################## +########## # This code is run when connecting to localhost:9090/?ajax if ($request =~ /comet/) { print $client "HTTP/1.0 200 OK\nContent-type: text/html\n\n"; $_ = int(rand() * 5); print "a $_\n"; print $client "<script>alert('a ' + $_)</script>\n"; sleep(2); # wait 2 seconds $_ = int(rand() * 5); print "b $_\n"; print $client "<script>alert('b ' + $_)</script>\n"; sleep(2); # wait 2 seconds $_ = int(rand() * 5); print "c $_\n"; print $client "<script>alert('c ' + $_)</script>\n"; } # PRINT THE HTML PAGE ############################################## +########## # This code is run when connecting to localhost:9090 else { print $client "HTTP/1.0 200 OK\nContent-type: text/html\n\n"; print $client qq{ <html> <body> <input type="button" value="Stop connection attempts" onclick="recon +nect = 0;"> <div id="status"> Awaiting commands...<br> </div> <script> // Change the server address here if you cannot use localhost:9090 var server = 'http://localhost:9090/'; var interval = 0; var status = document.getElementById('status'); var pos = 0; var reconnect = 1; // Adapted from http://empireenterprises.com/_comet.html // Removed Opera support for this demo function connect() { status.innerHTML += 'Connecting... (script should start alerting + soon)<br>'; pos = 0; // Internet Explorer (just a quick IE vs FF check) if (document.all) { // By creating a new ActiveXObject('htmlfile'), there is no lo +ad bar. var doc = new ActiveXObject('htmlfile'); doc.open(); doc.write('<html>'); doc.write('<html>'); doc.write('<script>document.domain = \\'' + document.domain + +'\\''); doc.write('</html>'); doc.close(); var div = doc.createElement('DIV'); doc.appendChild(div); // This /should/ just run the Javascript commands as it receiv +es them, // but unfortunately it is not. div.innerHTML = '<iframe name=\\'ifr\\' src=\\'' + server + '/ +?comet\\'></iframe>'; // It should not be useful to poll the iframe. From what I un +derstand // about IE, the content WILL NOT be available until the DOM i +s // complete. Unfortunately that doesn't happen when streaming +. // Hence, the messages are printed as <script> tags because I // believe that somehow (perhaps if the message is printed /ju +st right/) // that IE will actually call the Javascript functions as it r +eceives // them. // Nevertheless, I am leaving this code in because it is also +in the // example. if(!interval) { interval = setInterval( function() { var xmlhttp = doc.frames['ifr'].document; if(xmlhttp.readyState == 'complete') { clearInterval(interval); interval = false; status.innerHTML += 'Disconnected.<br>'; if (reconnect) connect(); } var data; try { data = xmlhttp.firstChild.innerHTML; } catch(error) { return; } data = data.substring(pos); if(data.indexOf('<SCRIPT>') < 0) { return; } var start = data.indexOf('<SCRIPT>') + 8; var stop = data.indexOf('<\\/SCRIPT>') - start; data = data.substr(start, stop); try { // Uncomment these two lines to see that IE only recei +ves the // innerHTML after the server closes the socket. //status.innerHTML += '&nbsp; &nbsp; Executing command +: ' + data.replace(/</g,'&lt;') + '<br>'; //eval(data); } catch(error) { } pos += start + 8 + stop - 1; }, 500 ); } } // Firefox. Opera implementation works, too - it just isn't inc +luded // (use an interval to poll xmlhttp.responseText) else { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if(xmlhttp.readyState == 4) { status.innerHTML += 'Disconnected.<br>'; if (reconnect) connect(); return false; } if(xmlhttp.readyState == 3) { var data = xmlhttp.responseText; data = data.substr(pos); if(data.indexOf('<script>') < 0) { return(false); } var start = data.indexOf('<script>') + 8; var stop = data.indexOf('<\\/script>') - start; data = data.substr(start, stop); status.innerHTML += '&nbsp; &nbsp; Executing command: ' ++ data.replace(/</g,'&lt;') + '<br>'; try { eval(data); } catch(error) { } pos += start + 8 + stop; } }; xmlhttp.open('GET', server + '/?comet', true); xmlhttp.send(''); } } connect(); </script> </body> </html> }; close o; } close $client; } 1;

Replies are listed 'Best First'.
Re: Streaming JS commands to IE with a Perl HTTP push server
by erroneousBollock (Curate) on Aug 10, 2007 at 06:49 UTC
    I'm not sure that IE allows the "inline" javascript to fire reliably before the full DOM is constructed.

    Have you tried using the '100 Continue' header? (eg: using the same socket to send multiple HTTP responses).

    IIUC conforming HTTP/1.1 client implementations will implement this just fine.

    -David

      Well I think you got me going on a good track of checking different HTTP headers and re-examining the example at http://empireenterprises.com/_comet.html

      Unfortunately I still have no luck with IE with different headers, meta tags, regular tags, etc. I re-examined the example and saw that he's not printing javascript functions at all, but rather just JSON objects. JSON is useless unless it is evaluated, so I think that he is somehow actually accessing the innerHTML during page load in IE (unfortunately have not been able to contact him). His client code obviously does this but I have no idea how the server gets IE to work (I even tried the exact same headers and tags).

      I tried a new test to see if the script may be executing but not interacting by setting a variable to the current Javascript time in each step, waiting two seconds, then setting again. The last time I used an alert() to print the values. Firefox - three different times, IE - same time.

      The other major working example is here: http://meteorserver.org/

      The code is much more complicated, but they also created a Perl server. I am not able to pick it apart enough to tell what is wrong with mine. Ugh - I thought for sure something would give me hope today.
        For anyone interested in this post, I discovered the solution to the problem. Before Internet Explorer will start streaming Javascript commands or any content, it must receive 256 bytes of data. This is as simple as
        print $sock ' ' x 256;
        What a ridiculous solution. I found this in the last sentence of the PHP manual entry for "flush".