Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations dencom on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Perl HTTP server - can't stream data to IE

Status
Not open for further replies.

colorplane

Programmer
Aug 9, 2007
44
US
Although Firefox and Opera support streaming Javascript commands sent by an HTTP server (Comet), I can't figure out why my server/client code is not streaming in Internet Explorer.

The concept of Comet as I understand it is that IE runs the <script> tags generated by the server as it receives them. This technique is used because IE does not allow access to responseTEXT or innerHTML while loading. I just can't get it to work.

I have been working on this for a long time, so I decided to make a very well-documented, self-contained Perl HTTP server to see if anyone could help me track down the issue. This is not the application that I am developing, but it exhibits the exact same peculiarities in IE.

When the code below is running in the command line, it will listen on port 9090, so you should be able to run the server script and connect to Basically the alert boxes should appear at 2-second intervals, but IE clumps them all together at the end of the request.

Please, if anyone can take a look at this and find anything fishy - endlines, html tags, socket problems, anything...

The clearest working example and my glimmer of hope is here:
Thank you, any feedback is appreciated.


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 [URL unfurl="true"]http://localhost:9090/,[/URL] the browser 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 specified message.
# The script should print these messages in Firefox, but in Internet Explorer,
# 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 closed the
# socket.  It /should/, however, signal alerts, but this is not happening.
#
# After the server prints the three commands, it closes the socket.  The 
# client re-establishes the connection and it begins again.  Only at this point
# does IE print the alert messages.
#
# Javascript below is adapted from [URL unfurl="true"]http://empireenterprises.com/_comet.html[/URL]

use IO::Socket;
use strict;

$| = 1;

my $sock = new IO::Socket::INET(
  LocalPort => 9090,
  Proto => 'tcp',
  Listen => SOMAXCONN,
  Reuse => 1
);

# Demo supports one client at a time, wait for connections.
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="reconnect = 0;">
  <div id="status">
    Awaiting commands...<br>
  </div>
  

  <script>
    // Change the server address here if you cannot use localhost:9090
    var server = '[URL unfurl="true"]http://localhost:9090/';[/URL]
    var interval = 0;
    var status = document.getElementById('status');
    var pos = 0;
    var reconnect = 1;
  
    // Adapted from [URL unfurl="true"]http://empireenterprises.com/_comet.html[/URL]
    // 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 load 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 receives 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 understand 
        // about IE, the content WILL NOT be available until the DOM is 
        // 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 /just right/)
        // that IE will actually call the Javascript functions as it receives
        // 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 receives 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 included
      // (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;
 
Hi Randy,

Neat code...

I note that from version 1.18 all IO::Socket objects have autoflush turned on by default.

Seems to me that autoflush is probably what you want, but it might bear looking at.



Mike

When working on any project the value of other people is exactly that - they are other people, with views that don't necessarily match yours. This mismatch, between their views and the view you've been contentedly assuming is right, is where that value lies.
 
Thanks, but I've tried setting autoflush explicitly, unfortunately it didn't change anything.

I tested the server on both Windows and RedHat and still get the same results. I suppose I really need a combined IE-quirks-and-Perl-sockets expert =)
 
Did you try telnetting to the server and making the request? This would tell you if your server is actually sending your message in chunks or waiting until the end and sending it all at once.

-------------
Cuvou.com | My personal homepage
Project Fearless | My web blog
 
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 <tt>print $sock ' ' x 256;</tt>

What a ridiculous solution. I found this in the last sentence of the PHP manual entry for "flush".
 
I'm pretty sure this applies to custom error pages also.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[noevil]
Travis - Those Who Say It Cannot Be Done Are Usually Interrupted by Someone Else Doing It; Give the wrong symptoms, get the wrong solutions;
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top