--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# A Meteor Channel
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Channel;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ use Meteor::Message;
+
+ our %Channels=();
+ our $MessageID=0;
+
+###############################################################################
+# Class methods
+###############################################################################
+sub channelWithName {
+ my $class=shift;
+ my $channelName=shift;
+ my $avoidCreation=shift;
+
+ unless(exists($Channels{$channelName}))
+ {
+ return undef if($avoidCreation);
+ #
+ # Create new channel
+ #
+ $Channels{$channelName}=$class->newChannel($channelName);
+
+ &::syslog('debug',"New channel $channelName");
+ }
+
+ return $Channels{$channelName};
+}
+
+sub listChannels {
+ my $class=shift;
+
+ my $list='';
+ foreach my $channelName (sort keys %Channels)
+ {
+ my $channel=$Channels{$channelName};
+
+ $list.=$channelName.'('.$channel->messageCount().'/'.$channel->subscriberCount().")$::CRLF";
+ }
+
+ $list;
+}
+
+sub deleteChannel {
+ my $class=shift;
+ my $channelName=shift;
+
+ delete($Channels{$channelName});
+}
+
+sub trimMessageStoresByTimestamp {
+ my $class=shift;
+ my $minTimeStamp=shift;
+
+ return unless($minTimeStamp);
+
+ map { $_->trimMessageStoreByTimestamp($minTimeStamp) } (values %Channels);
+}
+
+sub clearAllBuffers {
+ my $class=shift;
+
+ map { $_->clearBuffer() } (values %Channels);
+}
+
+sub numChannels {
+
+ return scalar(keys %Channels);
+}
+
+###############################################################################
+# Factory methods
+###############################################################################
+sub new {
+ #
+ # Create a new empty instance
+ #
+ my $class=shift;
+
+ my $obj={};
+
+ bless($obj,$class);
+}
+
+sub newChannel {
+ #
+ # new instance from new server connection
+ #
+ my $self=shift->new();
+
+ my $name=shift;
+ $self->{'name'}=$name;
+
+ $self->{'subscribers'}=[];
+ $self->{'messages'}=[];
+
+ $self;
+}
+
+sub DESTROY {
+ my $self=shift;
+
+ my @subscribers=@{$self->{'subscribers'}};
+ map { $_->closeChannel($self->{'name'}) } @subscribers;
+}
+
+###############################################################################
+# Instance methods
+###############################################################################
+sub name {
+ shift->{'name'};
+}
+
+sub addSubscriber {
+ my $self=shift;
+ my $subscriber=shift;
+ my $startId=shift;
+ my $persist=shift;
+ my $mode=shift || '';
+ my $userAgent=shift || '';
+
+ # Note: negative $startId means go back that many messages
+ my $startIndex=$self->indexForMessageID($startId);
+ my $logStartIndex = $startIndex || $self->lastMsgID() || 0;
+
+ push(@{$self->{'subscribers'}},$subscriber) if($persist);
+
+ &::syslog('info','',
+ 'joinchannel',
+ $subscriber->{'ip'},
+ $subscriber->{'subscriberID'},
+ $self->{'name'},
+ $mode,
+ $logStartIndex,
+ $userAgent
+ );
+
+ return unless(defined($startIndex));
+
+ my $msgCount=scalar(@{$self->{'messages'}});
+ my $txt='';
+
+ $startIndex=0 if($startIndex<0);
+
+ if($startIndex<$msgCount) {
+ $subscriber->sendMessages(@{$self->{'messages'}}[$startIndex..$msgCount-1]);
+ }
+}
+
+sub removeSubscriber {
+ my $self=shift;
+ my $subscriber=shift;
+ my $reason=shift ||'unknown';
+
+ my $idx=undef;
+ my $numsubs = scalar(@{$self->{'subscribers'}});
+
+ for (my $i=0; $i<$numsubs; $i++) {
+ if($self->{'subscribers'}->[$i]==$subscriber) {
+ $idx=$i;
+ last;
+ }
+ }
+
+ if(defined($idx))
+ {
+ splice(@{$self->{'subscribers'}},$idx,1);
+
+ my $timeConnected = time - $subscriber->{'ConnectionStart'};
+ &::syslog('info','',
+ 'leavechannel',
+ $subscriber->{'ip'},
+ $subscriber->{'subscriberID'},
+ $self->{'name'},
+ $timeConnected,
+ $subscriber->{'MessageCount'},
+ $subscriber->{'bytesWritten'},
+ $reason
+ );
+ }
+
+ $self->checkExpiration();
+}
+
+sub subscriberCount {
+ my $self=shift;
+
+ scalar(@{$self->{'subscribers'}});
+}
+
+sub addMessage {
+ my $self=shift;
+ my $messageText=shift;
+
+ my $message=Meteor::Message->newWithID($MessageID++);
+ $message->setText($messageText);
+ $message->setChannelName($self->{'name'});
+ push(@{$self->{'messages'}},$message);
+ &::syslog('debug',"New message ".$message->{"id"}." on channel ".$self->{'name'});
+
+ $self->trimMessageStoreBySize();
+
+ map { $_->sendMessages($message) } @{$self->{'subscribers'}};
+
+ $message;
+}
+
+sub messageCount {
+ my $self=shift;
+
+ scalar(@{$self->{'messages'}});
+}
+
+sub trimMessageStoreBySize {
+ my $self=shift;
+
+ my $numMessages=scalar(@{$self->{'messages'}});
+
+ if($numMessages>$::CONF{'MaxMessagesPerChannel'})
+ {
+ splice(@{$self->{'messages'}},0,-$::CONF{'MaxMessagesPerChannel'});
+ }
+}
+
+sub trimMessageStoreByTimestamp {
+ my $self=shift;
+ my $ts=shift;
+
+ while(scalar(@{$self->{'messages'}})>0 && $self->{'messages'}->[0]->timestamp()<$ts)
+ {
+ my $msg=shift(@{$self->{'messages'}});
+ }
+
+ $self->checkExpiration();
+}
+
+sub clearBuffer {
+ my $self=shift;
+
+ $self->{'messages'}=[];
+
+ $self->checkExpiration();
+}
+
+sub checkExpiration {
+ my $self=shift;
+
+ if($self->messageCount()==0 && $self->subscriberCount()==0)
+ {
+ my $name=$self->name();
+ &::syslog('debug',"Channel expired: $name");
+ $self->deleteChannel($name);
+ }
+}
+
+sub indexForMessageID {
+ my $self=shift;
+ my $id=shift;
+
+ # the messages is always sorted by ID, so we can
+ # use a binary search to find the message.
+ # return undef if there are no messages or the
+ # ID is that of the last message.
+ # Otherwise return the ID of the found message
+ # of if no message with that ID exists the one
+ # with the next higher ID
+ #
+ return undef unless(defined($id));
+
+ my $numMessages=scalar(@{$self->{'messages'}});
+
+ return undef unless($numMessages);
+ return -1 unless($id ne '');
+
+ # Note: negative $id means go back that many messages
+ return $numMessages+$id if($id<0);
+
+ my $low=0;
+ my $high=$numMessages-1;
+ my $mid;
+ my $cond;
+ while($low<=$high)
+ {
+ $mid=($low+$high)>>1;
+ $cond=$id <=> $self->{'messages'}->[$mid]->id();
+ if($cond<0)
+ {
+ $high=$mid-1;
+ }
+ elsif($cond>0)
+ {
+ $low=$mid+1;
+ }
+ else
+ {
+ return $mid;
+ }
+ }
+
+ return undef if($low>=$numMessages);
+
+ return $low;
+}
+
+sub lastMsgID {
+ my $self=shift;
+ my $numMessages=scalar(@{$self->{'messages'}});
+ return undef unless($numMessages>0);
+ @{$self->{'messages'}}[-1]->id();
+}
+
+sub descriptionWithTemplate {
+ my $self=shift;
+ my $template=shift;
+
+ return '' unless(defined($template) && $template ne '');
+
+ $template=~s/~([a-zA-Z0-9_]*)~/
+ if(!defined($1) || $1 eq '') {
+ '~';
+ } elsif($1 eq 'messageCount') {
+ $self->messageCount();
+ } elsif($1 eq 'subscriberCount') {
+ $self->subscriberCount();
+ } elsif($1 eq 'lastMsgID') {
+ $self->lastMsgID() || 0;
+ } elsif($1 eq 'name') {
+ $self->{'name'};
+ } else {
+ '';
+ }
+ /gex;
+
+ $template;
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# Meteor Configuration handling.
+#
+# Main program should call Meteor::Config::setCommandLineParameters(@ARGV),.
+# Afterwards anybody can access $::CONF{<parameterName>}, where
+# <parameterName> is any valid parameter (except 'Help') listed in the
+# @DEFAULTS array below.
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Config;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ our @DEFAULTS=(
+'Template for each line in channelinfo',
+ ChannelInfoTemplate => '<script>ch("~name~", ~lastMsgID~);</script>\r\n',
+
+'Configuration file location on disk (if any)',
+ ConfigFileLocation => '/etc/meteord.conf',
+
+'IP address for controller server (leave empty for all local addresses)',
+ ControllerIP => '',
+
+'Port number for controller connections',
+ ControllerPort => 4671,
+
+'Controller Shutdown message, sent when the controller server shuts down (leave empty for no message)',
+ ControllerShutdownMsg => '',
+
+'Debug Flag, when set daemon will run in foreground and emit debug messages',
+ Debug => 0,
+
+'Name of index file to serve when a directory is requested from the static file web server',
+ DirectoryIndex => 'index.html',
+
+'Header template, ~server~, ~servertime~ and ~status~ will be replaced by the appropriate values.',
+ HeaderTemplate => 'HTTP/1.1 ~status~\r\nServer: ~server~\r\nContent-Type: text/html; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\n\r\n',
+
+'Print out this help message',
+ Help => '',
+
+'Format to use for timestamps in syslog: unix or human',
+ LogTimeFormat => 'human',
+
+'Maximum age of a message in seconds',
+ MaxMessageAge => 7200,
+
+'Maximum number of messages to send to a subscriber before forcing their connection to close. Use 0 to disable',
+ MaxMessages => 0,
+
+'Maximum number of stored messages per channel',
+ MaxMessagesPerChannel => 250,
+
+'Maximum duration in seconds for a subscriber connection to exist before forcing a it to close. Note that the server checks for expired connections in 60 second intervals, so small changes to this value will not have much of an effect. Use 0 to disable',
+ MaxTime => 0,
+
+'Message template, ~text~, ~id~, ~channel~ and ~timestamp~ will be replaced by the appropriate values',
+ MessageTemplate => '<script>p(~id~,"~channel~","~text~");</script>\r\n',
+
+'Interval at which PingMessage is sent to all persistent subscriber connections. Must be at least 3 if set higher than zero. Set to zero to disable.',
+ PingInterval => 5,
+
+'Persistence of a connection.',
+ Persist => 0,
+
+'Message to be sent to all persistent subscriber connections (see above) every PingInterval seconds',
+ PingMessage => '<script>p(-1,"");</script>\r\n',
+
+'IP address for subscriber server (leave empty for all local addresses)',
+ SubscriberIP => '',
+
+'Port number for subscriber connections',
+ SubscriberPort => 4670,
+
+'Subscriber Shutdown message, sent when the subscriber server shuts down (leave empty for no message)',
+ SubscriberShutdownMsg => '<script>eof();</script>\r\n',
+
+'An absolute filesystem path, to be used as the document root for Meteor\'s static file web server. If left empty, no documents will be served.',
+ SubscriberDocumentRoot => '/usr/local/meteor/public_html',
+
+'Since Meteor is capable of serving static pages from a document root as well as streaming events to subscribers, this parameter is used to specify the URI at which the event server can be reached. If set to the root, Meteor will lose the ability to serve static pages.',
+ SubscriberDynamicPageAddress => '/push',
+
+'The syslog facility to use',
+ SyslogFacility => 'daemon',
+
+'IP address for udp server (leave empty for all local addresses)',
+ UDPIP => '',
+
+'Port number for udp connections, set to 0 to disable',
+ UDPPort => 0,
+
+ );
+
+ our %ConfigFileData=();
+ our %CommandLine=();
+ our %Defaults=();
+ our %Modes=();
+
+ for(my $i=0;$i<scalar(@DEFAULTS);$i+=3)
+ {
+ $Defaults{$DEFAULTS[$i+1]}=$DEFAULTS[$i+2];
+ }
+
+###############################################################################
+# Class methods
+###############################################################################
+sub updateConfig {
+ my $class=shift;
+
+ %::CONF=();
+
+ my $debug=$class->valueForKey('Debug');
+
+ print STDERR '-'x79 ."\nMeteor server v$::VERSION (release date: $::RELEASE_DATE)\r\nLicensed under the terms of the GNU General Public Licence (2.0)\n".'-'x79 ."\n" if($debug);
+
+ my @keys=();
+
+ for(my $i=0;$i<scalar(@DEFAULTS);$i+=3)
+ {
+ next if($DEFAULTS[$i+1] eq 'Help');
+ push(@keys,$DEFAULTS[$i+1]);
+ }
+
+ foreach my $mode ('',keys %Modes)
+ {
+ print STDERR ($mode) ? "\r\n$mode:\r\n" : "\r\nDefaults:\r\n" if($debug);
+ foreach my $baseKey (@keys)
+ {
+ my $foundValue=0;
+ my $key=$baseKey.$mode;
+
+ if(exists($CommandLine{$key}))
+ {
+ print STDERR "CmdLine" if($debug);
+ $::CONF{$key}=$CommandLine{$key};
+ $foundValue=1;
+ }
+ elsif(exists($ConfigFileData{$key}))
+ {
+ print STDERR "CnfFile" if($debug);
+ $::CONF{$key}=$ConfigFileData{$key};
+ $foundValue=1;
+ }
+ elsif(exists($Defaults{$key}))
+ {
+ print STDERR "Default" if($debug);
+ $::CONF{$key}=$Defaults{$key};
+ $foundValue=1;
+ }
+
+ next unless($foundValue);
+
+ print STDERR "\t$baseKey\t$::CONF{$key}\n" if($debug);
+
+ # Take care of escapes
+ $::CONF{$key}=~s/\\(.)/
+ if($1 eq 'r') {
+ "\r";
+ } elsif($1 eq 'n') {
+ "\n";
+ } elsif($1 eq 's') {
+ ' ';
+ } elsif($1 eq 't') {
+ "\t";
+ } elsif($1 eq '0') {
+ "\0";
+ } else {
+ $1;
+ }
+ /gex;
+ }
+ }
+ print STDERR '-'x79 ."\n" if($debug);
+}
+
+sub valueForKey {
+ my $class=shift;
+ my $key=shift;
+
+ return $CommandLine{$key} if(exists($CommandLine{$key}));
+ return $ConfigFileData{$key} if(exists($ConfigFileData{$key}));
+
+ $Defaults{$key};
+}
+
+sub setCommandLineParameters {
+ my $class=shift;
+
+ #
+ # Quick check if we should show the version, if so ignore everything else
+ # Accept -v, -version, and everything in between
+ #
+ foreach my $p (@_)
+ {
+ if(index($p,'-v')==0 && index('-version',$p)==0)
+ {
+ print "$::PGM $::VERSION\n";
+ exit(0);
+ }
+ }
+
+ while(my $cnt=scalar(@_))
+ {
+ my $k=shift(@_);
+ &usage("'$k' invalid") unless($k=~s/^\-(?=.+)//);
+
+ $k='Debug' if($k eq 'd');
+
+ my $mode='';
+
+ if($k=~s/(\.(.+))$//)
+ {
+ $mode=$2;
+ $Modes{$mode}=1;
+ }
+
+ my $key=undef;
+ my $kl=length($k);
+ my $kOrig=$k;
+ $k=lc($k);
+
+ for(my $i=0;$i<scalar(@DEFAULTS);$i+=3)
+ {
+ my $p=$DEFAULTS[$i+1];
+ my $pl=length($p);
+
+ next if($kl>$pl);
+
+ #print "$kl $pl $k $p\n";
+
+ if($kl==$pl && $k eq lc($p))
+ {
+ $key=$p;
+ last;
+ }
+
+ my $ps=lc(substr($p,0,$kl));
+
+ if($k eq $ps)
+ {
+ if(defined($key))
+ {
+ &usage("Ambigous parameter name '$kOrig'");
+ }
+ $key=$p;
+ }
+ }
+
+ &usage("Unknown parameter name '$kOrig'") unless(defined($key));
+
+ &usage() if($key eq 'Help');
+
+ #print "$kOrig: $key\n";
+
+ $CommandLine{"$key$mode"}=1;
+
+ if($cnt>1 && $_[0]!~/^\-(?!\-)/)
+ {
+ my $param=shift;
+ $param=~s/^\-\-/\-/;
+ $CommandLine{"$key$mode"}=$param;
+ }
+ }
+
+ $class->readConfig();
+
+ $class->updateConfig();
+}
+
+sub readConfig {
+ my $class=shift;
+
+ %ConfigFileData=();
+
+ my $path=$class->valueForKey('ConfigFileLocation');
+ return unless(defined($path) && -f $path);
+
+ my $mode='';
+
+ open(CONFIG,"$path") or &usage("Config file '$path' for read: $!\n");
+ while(<CONFIG>)
+ {
+ next if(/^\s*#/);
+ next if(/^\s*$/);
+
+ s/[\r\n]*$//;
+
+ if(/^\s*\[\s*([^\]\s]+)\s*\]\s*$/)
+ {
+ $Modes{$1}=1;
+ $mode = $1;
+ next;
+ }
+
+ unless(/^(\S+)\s*(.*)/)
+ {
+ &usage("Invalid configuration file parameter line '$_'");
+ }
+
+ my $key=$1;
+ my $val=$2;
+ $val='' unless(defined($val));
+
+ unless(exists($Defaults{$key}))
+ {
+ &usage("Unknown configuration file parameter name '$key$mode'");
+ }
+ if($key eq 'ConfigFileLocation')
+ {
+ &usage("'ConfigFileLocation' parameter not allowed in configuration file!");
+ }
+
+ $val=~s/^--/-/;
+
+ $ConfigFileData{"$key$mode"}=$val;
+ }
+ close(CONFIG);
+}
+
+sub usage {
+ my $msg=shift || '';
+
+ if($msg) {
+ print STDERR <<"EOT";
+$msg;
+For further help type $::PGM -help
+or consult docs at http://meteorserver.org/
+EOT
+
+ } else {
+
+
+ print STDERR <<"EOT";
+
+Meteor server v$::VERSION (release date: $::RELEASE_DATE)
+Licensed under the terms of the GNU General Public Licence (2.0)
+
+Usage:
+
+ $::PGM [-parameter [value] [-parameter [value]...]]
+
+Accepted command-line parameters:
+
+EOT
+
+ for(my $i=0;$i<scalar(@DEFAULTS);$i+=3)
+ {
+ print STDERR "-$DEFAULTS[$i+1]\n$DEFAULTS[$i].\n\n";
+ }
+
+ print STDERR <<"EOT";
+
+Any of the parameters listed above can also be configured in the
+configuration file. The default location for this file is:
+
+ $Defaults{'ConfigFileLocation'}
+
+For more information and complete documentation, see the Meteor
+website at http://meteorserver.org/
+EOT
+
+ }
+ exit(1);
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# Common super-class for controller and subscriber
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Connection;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ use Errno qw(EAGAIN);
+
+ our $MAX_READ_SIZE=8192;
+ our $CONNECTION_WRITE_TIMEOUT=120;
+
+ our @Connections=();
+
+###############################################################################
+# Class methods
+###############################################################################
+sub addAllHandleBits {
+ my $class=shift;
+
+ my $rVecRef=shift;
+ my $wVecRef=shift;
+ my $eVecRef=shift;
+
+ my @cons=@Connections;
+ map {$_->addHandleBits($rVecRef,$wVecRef,$eVecRef) if(defined($_)) } @cons;
+}
+
+sub checkAllHandleBits {
+ my $class=shift;
+
+ my $rVec=shift;
+ my $wVec=shift;
+ my $eVec=shift;
+
+ my @cons=@Connections;
+ map {$_->checkHandleBits($rVec,$wVec,$eVec) if(defined($_)) } @cons;
+}
+
+sub connectionCount {
+ scalar(@Connections);
+}
+
+sub closeAllConnections {
+ my @cons=@Connections;
+
+ map { $_->close(); } @cons;
+}
+
+###############################################################################
+# Factory methods
+###############################################################################
+sub new {
+ #
+ # Create a new empty instance
+ #
+ my $class=shift;
+
+ my $obj={};
+
+ bless($obj,$class);
+}
+
+sub newFromServer {
+ #
+ # new instance from new server connection
+ #
+ my $self=shift->new();
+
+ $::Statistics->{'total_requests'}++;
+
+ my $server=shift;
+ my $socket=$server->conSocket();
+
+ $self->{'socket'}=$socket;
+ $self->{'socketFN'}=$socket->fileno();
+
+ $socket->setNonBlocking();
+
+ $self->{'writeBuffer'}='';
+ $self->{'readBuffer'}='';
+ $self->{'bytesWritten'}=0;
+ $self->{'ip'}=$socket->{'connection'}->{'remoteIP'};
+
+ push(@Connections,$self);
+
+ &::syslog('debug',"New %s for %s",ref($self),$socket->{'connection'}->{'remoteIP'});
+
+ $self;
+}
+
+###############################################################################
+# Instance methods
+###############################################################################
+sub write {
+ my $self=shift;
+
+ $self->{'writeBuffer'}.=shift;
+ $self->{'writeBufferTimestamp'}=time unless(exists($self->{'writeBufferTimestamp'}));
+}
+
+sub addHandleBits {
+ my $self=shift;
+
+ my $rVecRef=shift;
+ my $wVecRef=shift;
+ my $eVecRef=shift;
+
+ my $fno=$self->{'socketFN'};
+
+ if($self->{'writeBuffer'} ne '')
+ {
+ if(exists($self->{'writeBufferTimestamp'}) && $self->{'writeBufferTimestamp'}+$CONNECTION_WRITE_TIMEOUT<time)
+ {
+ &::syslog('debug',"%s for %s: write timed out",ref($self),$self->{'socket'}->{'connection'}->{'remoteIP'});
+
+ $self->{'writeBuffer'}='';
+ $self->close();
+ return;
+ }
+ vec($$wVecRef,$fno,1)=1;
+ }
+
+ vec($$rVecRef,$fno,1)=1;
+ vec($$eVecRef,$fno,1)=1;
+}
+
+sub checkHandleBits {
+ my $self=shift;
+
+ my $rVec=shift;
+ my $wVec=shift;
+ my $eVec=shift;
+
+ my $fno=$self->{'socketFN'};
+
+ if(vec($eVec,$fno,1))
+ {
+ #
+ # Something went wrong!
+ #
+ $self->exceptionReceived();
+
+ return;
+ }
+
+ if(vec($rVec,$fno,1))
+ {
+ #
+ # Data available for read
+ #
+ my $socket=$self->{'socket'};
+
+ my $buffer='';
+ my $bytesRead=sysread($socket->{'handle'},$buffer,$MAX_READ_SIZE);
+ if(defined($bytesRead) && $bytesRead>0)
+ {
+ $::Statistics->{'total_inbound_bytes'}+=$bytesRead;
+ $self->{'readBuffer'}.=$buffer;
+ while($self->{'readBuffer'}=~s/^([^\r\n]*)\r?\n//)
+ {
+ $self->processLine($1);
+ }
+ }
+ elsif(defined($bytesRead) && $bytesRead==0)
+ {
+ # Connection closed
+ $self->{'remoteClosed'}=1;
+ $self->close(1, 'remoteClosed');
+
+ return;
+ }
+ else
+ {
+ unless(${!}==EAGAIN)
+ {
+ &::syslog('notice',"Connection closed: $!");
+ $self->{'remoteClosed'}=1;
+ $self->close(1, 'remoteClosed');
+
+ return;
+ }
+ }
+ }
+
+ if(vec($wVec,$fno,1) && $self->{'writeBuffer'} ne '')
+ {
+ #
+ # Can write
+ #
+ my $socket=$self->{'socket'};
+
+ my $bytesWritten=syswrite($socket->{'handle'},$self->{'writeBuffer'});
+
+ if(defined($bytesWritten) && $bytesWritten>0)
+ {
+ $::Statistics->{'total_outbound_bytes'}+=$bytesWritten;
+ $self->{'bytesWritten'}+=$bytesWritten;
+ $self->{'writeBuffer'}=substr($self->{'writeBuffer'},$bytesWritten);
+ if(length($self->{'writeBuffer'})==0)
+ {
+ delete($self->{'writeBufferTimestamp'});
+ $self->close(1) if(exists($self->{'autoClose'}));
+ }
+ else
+ {
+ $self->{'writeBufferTimestamp'}=time;
+ }
+ }
+ else
+ {
+ unless(${!}==EAGAIN)
+ {
+ &::syslog('notice',"Connection closed: $!");
+ $self->{'remoteClosed'}=1;
+ $self->close(1, 'remoteClosed');
+
+ return;
+ }
+ }
+ }
+}
+
+sub exceptionReceived {
+ my $self=shift;
+
+ $self->{'writeBuffer'}='';
+
+ $self->close();
+}
+
+sub close {
+ my $self=shift;
+
+ #&::syslog('debug',"Close called for %s for %s when write buffer empty",ref($self),$self->{'socket'}->{'connection'}->{'remoteIP'});
+
+ unless($self->{'remoteClosed'})
+ {
+ if(!exists($self->{'autoClose'}) && length($self->{'writeBuffer'})>0)
+ {
+ $self->{'autoClose'}=1;
+
+ &::syslog('debug',"Will close %s for %s when write buffer empty",ref($self),$self->{'socket'}->{'connection'}->{'remoteIP'});
+
+ return;
+ }
+ }
+
+ eval {
+ $self->{'socket'}->close();
+ };
+
+ #
+ # Remove connection from list of connections
+ #
+ my $idx=undef;
+ my $numcon = scalar(@Connections);
+ for(my $i=0;$i<$numcon;$i++)
+ {
+ if($Connections[$i]==$self)
+ {
+ $idx=$i;
+ last;
+ }
+ }
+
+ if(defined($idx))
+ {
+ splice(@Connections,$idx,1);
+ }
+
+ &::syslog('debug',"Closed %s for %s",ref($self),$self->{'socket'}->{'connection'}->{'remoteIP'});
+
+ $self->didClose();
+}
+
+sub didClose {
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# A Meteor Controller
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Controller;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ use Meteor::Connection;
+ use Meteor::Channel;
+ use Meteor::Subscriber;
+
+ @Meteor::Controller::ISA=qw(Meteor::Connection);
+
+###############################################################################
+# Factory methods
+###############################################################################
+sub newFromServer {
+ my $class=shift;
+
+ my $self=$class->SUPER::newFromServer(shift);
+
+ $::Statistics->{'current_controllers'}++;
+ $::Statistics->{'controller_connections_accepted'}++;
+
+ $self;
+}
+
+###############################################################################
+# Instance methods
+###############################################################################
+sub processLine {
+ my $self=shift;
+ my $line=shift;
+
+ # ADDMESSAGE channel1 Message text
+ # < OK
+ # ADDMESSAGE
+ # < ERR Invalid command syntax
+ # COUNTSUBSCRIBERS channel1
+ # < OK 344
+
+ unless($line=~s/^(ADDMESSAGE|COUNTSUBSCRIBERS|LISTCHANNELS|SHOWSTATS|QUIT)//)
+ {
+ $self->write("ERR Invalid command syntax$::CRLF");
+
+ return;
+ }
+
+ my $cmd=$1;
+
+ if($cmd eq 'ADDMESSAGE')
+ {
+ unless($line=~s/^\s+(\S+)\s//)
+ {
+ $self->write("ERR Invalid command syntax$::CRLF");
+
+ return;
+ }
+
+ my $channelName=$1;
+ my $channel=Meteor::Channel->channelWithName($channelName);
+ my $msg=$channel->addMessage($line);
+ my $msgID=$msg->id();
+ $self->write("OK\t$msgID$::CRLF");
+ }
+ elsif($cmd eq 'COUNTSUBSCRIBERS')
+ {
+ unless($line=~s/^\s+(\S+)$//)
+ {
+ $self->write("ERR Invalid command syntax$::CRLF");
+
+ return;
+ }
+
+ my $channelName=$1;
+ my $numSubscribers=0;
+ my $channel=Meteor::Channel->channelWithName($channelName,1);
+ $numSubscribers=$channel->subscriberCount() if($channel);
+
+ $self->write("OK $numSubscribers$::CRLF");
+ }
+ elsif($cmd eq 'LISTCHANNELS')
+ {
+ unless($line eq '')
+ {
+ $self->write("ERR Invalid command syntax$::CRLF");
+
+ return;
+ }
+
+ my $txt="OK$::CRLF".Meteor::Channel->listChannels()."--EOT--$::CRLF";
+
+ $self->write($txt);
+ }
+ elsif($cmd eq 'SHOWSTATS')
+ {
+ # uptime
+ my $uptime=time-$::STARTUP_TIME;
+ my $txt="OK$::CRLF"."uptime: $uptime$::CRLF";
+
+ # channel_count
+ my $numChannels=Meteor::Channel->numChannels();
+ $txt.="channel_count: $numChannels$::CRLF";
+
+ foreach my $key (keys %{$::Statistics})
+ {
+ $txt.=$key.': '.$::Statistics->{$key}.$::CRLF;
+ }
+
+ $txt.="--EOT--$::CRLF";
+
+ $self->write($txt);
+ }
+ elsif($cmd eq 'QUIT')
+ {
+ unless($line eq '')
+ {
+ $self->write("ERR Invalid command syntax$::CRLF");
+
+ return;
+ }
+
+ $self->write("OK$::CRLF");
+ $self->close(1);
+ }
+ else
+ {
+ # Should never get here
+ die("Unknown command '$cmd'");
+ }
+}
+
+sub close {
+ my $self=shift;
+ my $noShutdownMsg=shift;
+
+ unless($noShutdownMsg || $self->{'remoteClosed'})
+ {
+ my $msg=$::CONF{'ControllerShutdownMsg'};
+ if(defined($msg) && $msg ne '')
+ {
+ $self->write($msg);
+ }
+ }
+
+ $self->SUPER::close();
+}
+
+sub didClose {
+
+ $::Statistics->{'current_controllers'}--;
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# Cache and serve static documents
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Document;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ our %Documents=();
+
+###############################################################################
+# Class methods
+###############################################################################
+sub serveFileToClient {
+ my $class=shift;
+ my $relPath=shift;
+ my $client=shift;
+
+ &::syslog('debug',"Meteor::Document: Request received for '%s'",$relPath);
+
+ my $doc=$class->documentForPath($relPath);
+
+ unless(defined($doc))
+ {
+ $class->emitHeaderToClient($client,'404 Not Found');
+ $::Statistics->{'documents_not_found'}++;
+
+ &::syslog('info','',
+ 'document',
+ $relPath,
+ 0,
+ 404
+ );
+
+ return undef;
+ }
+
+ $doc->serveTo($client);
+
+ $::Statistics->{'documents_served'}++;
+
+ &::syslog('info','',
+ 'document',
+ $relPath,
+ $doc->{'size'},
+ 200
+ );
+
+ $doc;
+}
+
+sub emitHeaderToClient {
+ my $self=shift;
+ my $client=shift;
+ my $status=shift;
+ my $length=shift;
+ my $contenttype=shift;
+ $length = 0 unless ($length);
+ $contenttype = "text/html" unless ($contenttype);
+
+ my $header="HTTP/1.1 ".$status."\r\nServer: ".$::PGM."\r\nContent-Type: ".$contenttype."; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\nContent-length: ".$length."\r\n\r\n";
+
+ $client->write($header);
+}
+
+sub documentForPath {
+ my $class=shift;
+ my $relPath=shift;
+
+ unless(exists($Documents{$relPath}))
+ {
+ my $path=$class->pathToAbsolute($relPath);
+
+ return undef unless(defined($path));
+
+ my $doc=$class->newDocument($path);
+
+ return undef unless(defined($doc));
+
+ $Documents{$relPath}=$doc;
+ }
+
+ $Documents{$relPath};
+}
+
+sub clearDocuments {
+ %Documents=();
+}
+
+sub pathToAbsolute {
+ my $class=shift;
+ my $relPath=shift;
+
+ # Don't serve documents unless SubscriberDocumentRoot is set
+ unless(exists($::CONF{'SubscriberDocumentRoot'})
+ && $::CONF{'SubscriberDocumentRoot'} ne ''
+ && $::CONF{'SubscriberDocumentRoot'} ne '/'
+ )
+ {
+ return undef;
+ }
+
+ #
+ # Verify if name is legal
+ #
+ # Strip leading and trailing slashes
+ $relPath=~s/^[\/]*//;
+ $relPath=~s/[\/]*$//;
+
+
+ # NOTE: With the right strings the code below triggers a bug in
+ # perl (5.8.6 currently) that will result in messages like
+ #
+ # Attempt to free unreferenced scalar
+ #
+ # and an eventual crash.
+ #
+ # So it was replaced with the more naive code following this
+ # commented out code.
+ #
+ # # split into path components
+ # my @pathComponents=split(/[\/]+/,$relPath);
+ #
+ # # Check components
+ # foreach (@pathComponents)
+ # {
+ # # Very strict: We only allow alphanumeric characters, dash and
+ # # underscore, followed by any number of extensions that also
+ # # only allow the above characters.
+ # unless(/^[a-z0-9\-\_][a-z0-9\-\_\.]*$/i)
+ # {
+ # &::syslog('debug',
+ # "Meteor::Document: Rejecting path '%s' due to invalid component '%s'",
+ # $relPath,$_
+ # );
+ #
+ # return undef;
+ # }
+ # }
+ #
+ #my $path=$::CONF{'SubscriberDocumentRoot'}.'/'.join('/',@pathComponents);
+
+ #
+ # Check for all alphanumeric or dash, underscore, dot and slash
+ #
+ unless($relPath=~/^[a-z0-9\-\_\.\/]*$/i)
+ {
+ &::syslog('debug',
+ "Meteor::Document: Rejecting path '%s' due to invalid characters",
+ $relPath
+ );
+
+ return undef;
+ }
+ #
+ # Don't allow '..'
+ #
+ if(index($relPath,'..')>=0)
+ {
+ &::syslog('debug',
+ "Meteor::Document: Rejecting path '%s' due to invalid sequence '..'",
+ $relPath
+ );
+
+ return undef;
+ }
+
+ my $path=$::CONF{'SubscriberDocumentRoot'}.'/'.$relPath;
+
+ # If it is a directory, append DirectoryIndex config value
+ $path.='/'.$::CONF{'DirectoryIndex'} if(-d $path);
+
+ # Verify file is readable
+ return undef unless(-r $path);
+
+ $path;
+}
+
+###############################################################################
+# Factory methods
+###############################################################################
+sub new {
+ #
+ # Create a new empty instance
+ #
+ my $class=shift;
+
+ my $obj={};
+
+ bless($obj,$class);
+}
+
+sub newDocument {
+ #
+ # new instance from new server connection
+ #
+ my $self=shift->new();
+
+ my $path=shift;
+ $self->{'path'}=$path;
+
+ # Read file
+ {
+ local $/; # enable localized slurp mode
+ open(IN,$path) or return undef;
+ $self->{'document'}=<IN>;
+ close(IN);
+ }
+
+ $self->{'size'}=length($self->{'document'});
+
+ $self;
+}
+
+###############################################################################
+# Instance methods
+###############################################################################
+sub serveTo {
+ my $self=shift;
+ my $client=shift;
+ my $ct = "text/html";
+ if ($self->{'path'} =~/\.(js)$/) {
+ $ct = "text/javascript";
+ }
+
+ $self->emitHeaderToClient($client,'200 OK',$self->{'size'}, $ct);
+
+ $client->write($self->{'document'});
+
+}
+
+sub path {
+ shift->{'path'};
+}
+
+1;
+############################################################################EOF
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# Meteor message object
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Message;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+###############################################################################
+# Factory methods
+###############################################################################
+sub new {
+ #
+ # Create a new empty instance
+ #
+ my $class=shift;
+
+ my $obj={};
+
+ bless($obj,$class);
+}
+
+sub newWithID {
+ #
+ # new instance from new server connection
+ #
+ my $self=shift->new();
+ my $id=shift;
+ my $text=shift || '';
+
+ $self->{'timestamp'}=time;
+ $self->{'id'}=$id;
+ $self->{'text'}=$text;
+
+ $::Statistics->{'unique_messages'}++;
+
+ $self;
+}
+
+###############################################################################
+# Instance methods
+###############################################################################
+sub setText {
+ my $self=shift;
+ my $text=shift || '';
+
+ $self->{'text'}=$text;
+}
+
+sub channelName {
+ shift->{'channel'};
+}
+
+sub setChannelName {
+ my $self=shift;
+ my $channelName=shift || '';
+
+ $self->{'channel'}=$channelName;
+}
+
+sub text {
+ shift->{'text'};
+}
+
+sub id {
+ shift->{'id'};
+}
+
+sub timestamp {
+ shift->{'timestamp'};
+}
+
+sub message {
+
+ shift->messageWithTemplate($::CONF{'MessageTemplate'});
+}
+
+sub messageWithTemplate {
+ my $self=shift;
+ my $msg=shift;
+
+ $msg=~s/~([^~]*)~/
+ if(!defined($1) || $1 eq '')
+ {
+ '~';
+ }
+ elsif(exists($self->{$1}))
+ {
+ $self->{$1};
+ }
+ else
+ {
+ '';
+ }
+ /gex;
+
+ $msg;
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# Meteor socket additions
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Socket;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ use Socket;
+ use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
+ use Errno qw(EINTR);
+
+ BEGIN {
+ $Meteor::Socket::handleNum=0;
+
+ # Cache getprotobyname result as on some systems it is slow.
+ $Meteor::Socket::TCP_PROTO_NAME=getprotobyname('tcp');
+ $Meteor::Socket::UDP_PROTO_NAME=getprotobyname('udp');
+ }
+
+###############################################################################
+# Factory methods
+###############################################################################
+sub new {
+ my $class=shift;
+
+ my $self=$class;
+
+ unless(ref($class))
+ {
+ $self={};
+ bless($self,$class);
+ }
+
+ $self->{'timeout'}=0;
+ $self->{'buffer'}='';
+
+ return $self;
+}
+
+sub newWithHandle {
+ my $class=shift;
+
+ my $self=$class->new;
+ $self->{'handle'}=shift;
+
+ my $vec='';
+ vec($vec,CORE::fileno($self->{'handle'}),1)=1;
+ $self->{'handleVec'}=$vec;
+
+ my $timeout=shift;
+ ($timeout) && ($self->{'timeout'}=$timeout);
+
+ return $self;
+}
+
+sub newServer {
+ my($class,$port,$queueSize,$srcIP)=@_;
+
+ ($port) || die("$class: port undefined!");
+
+ $queueSize||=5;
+
+ my $self=$class->new;
+
+ my $localAdr=INADDR_ANY;
+ $localAdr=inet_aton($srcIP) if(defined($srcIP) && $srcIP ne '');
+
+ my $local;
+ my $sockType=AF_INET;
+ my $proto=$Meteor::Socket::TCP_PROTO_NAME;
+
+ $self->{'port'}=$port;
+ ($local=sockaddr_in($port,$localAdr))
+ || die("$class: sockaddr_in for port '$port' failed");
+
+ $self->{'handle'}=$self->nextHandle();
+ $self->{'socketType'}=$sockType;
+
+ socket($self->{'handle'},$sockType,SOCK_STREAM,$proto)
+ || die("$class socket: $!");
+
+ setsockopt($self->{'handle'},SOL_SOCKET,SO_REUSEADDR,1);
+
+ bind($self->{'handle'},$local)
+ || die("$class bind: $!");
+ listen($self->{'handle'},$queueSize)
+ || die("$class listen: $!");
+
+ select((select($self->{'handle'}),$|=1)[0]);
+
+ my $vec='';
+ vec($vec,CORE::fileno($self->{'handle'}),1)=1;
+ $self->{'handleVec'}=$vec;
+
+ return $self;
+}
+
+sub newUDPServer {
+ my($class,$port,$srcIP)=@_;
+
+ ($port) || die("$class: port undefined!");
+
+ my $self=$class->new;
+
+ my $localAdr=INADDR_ANY;
+ $localAdr=inet_aton($srcIP) if(defined($srcIP) && $srcIP ne '');
+
+ my $local;
+ my $sockType=PF_INET;
+ my $proto=$Meteor::Socket::UDP_PROTO_NAME;
+
+ $self->{'port'}=$port;
+ ($local=sockaddr_in($port,$localAdr))
+ || die("$class: sockaddr_in for port '$port' failed");
+
+ $self->{'handle'}=$self->nextHandle();
+ $self->{'socketType'}=$sockType;
+
+ socket($self->{'handle'},$sockType,SOCK_DGRAM,$proto)
+ || die("$class socket: $!");
+
+ setsockopt($self->{'handle'},SOL_SOCKET,SO_REUSEADDR,pack("l", 1))
+ || die("setsockopt: $!");
+
+ bind($self->{'handle'},$local)
+ || die("$class bind: $!");
+
+ select((select($self->{'handle'}),$|=1)[0]);
+
+ my $vec='';
+ vec($vec,CORE::fileno($self->{'handle'}),1)=1;
+ $self->{'handleVec'}=$vec;
+
+ return $self;
+}
+
+###############################################################################
+# Instance methods
+###############################################################################
+sub DESTROY {
+ my $self=shift;
+
+ if(exists($self->{'handle'}))
+ {
+ warn("$self->DESTROY caught unclosed socket")
+ unless($Meteor::Socket::NO_WARN_ON_CLOSE);
+ $self->close();
+ }
+}
+
+sub conSocket {
+ my $self=shift;
+
+ my $handle=$self->nextHandle();
+
+ my $paddr;
+ $paddr=&saccept($handle,$self->{'handle'}) || die($!);
+
+ select((select($handle),$|=1)[0]);
+
+ my $newSock=Meteor::Socket->newWithHandle($handle,20);
+ $newSock->{'socketType'}=$self->{'socketType'};
+ if($self->{'socketType'}==AF_INET)
+ {
+ my($port,$iaddr)=unpack_sockaddr_in($paddr);
+
+ $newSock->{'connection'}->{'port'}=$port;
+ $newSock->{'connection'}->{'remoteIP'}=inet_ntoa($iaddr);
+ }
+
+ return $newSock;
+}
+
+sub setNonBlocking {
+ my $self=shift;
+
+ my $flags=fcntl($self->{'handle'},F_GETFL,0)
+ or die("Can't get flags for the socket: $!");
+ fcntl($self->{'handle'},F_SETFL,$flags|O_NONBLOCK)
+ or die("Can't set flags for the socket: $!");
+}
+
+sub close {
+ my $self=shift;
+
+ if(exists($self->{'handle'}))
+ {
+ close($self->{'handle'});
+ delete($self->{'handle'});
+ }
+}
+
+###############################################################################
+# Utility functions
+###############################################################################
+sub nextHandle {
+ no strict 'refs';
+
+ my $name='MSHandle'.$Meteor::Socket::handleNum++;
+ my $pack='Meteor::Socket::';
+ my $handle=\*{$pack.$name};
+ delete $$pack{$name};
+
+ $handle;
+}
+
+sub sselect {
+ my $result;
+ my $to=$_[3];
+ my $time=time;
+ while(1)
+ {
+ $result=CORE::select($_[0],$_[1],$_[2],$to);
+ if($result<0)
+ {
+ last unless(${!}==EINTR);
+ return 0 if($::HUP || $::TERM || $::USR1 || $::USR2);
+ my $tn=time;
+ $to-=($tn-$time);
+ $time=$tn;
+ $to=1 if($to<1);
+ }
+ else
+ {
+ last;
+ }
+ }
+
+ $result;
+}
+
+sub saccept {
+ my($dhandle,$shandle)=@_;
+
+ my $result;
+ while(1)
+ {
+ $result=CORE::accept($dhandle,$shandle);
+ unless($result)
+ {
+ last unless(${!}==EINTR);
+ return 0 if($::HUP || $::TERM || $::USR1 || $::USR2);
+ }
+ else
+ {
+ last;
+ }
+ }
+
+ $result;
+}
+
+sub fileno {
+ CORE::fileno(shift->{'handle'});
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# A Meteor Subscriber
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Subscriber;
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ use Meteor::Connection;
+ use Meteor::Channel;
+ use Meteor::Document;
+
+ @Meteor::Subscriber::ISA=qw(Meteor::Connection);
+
+ our %PersistentConnections=();
+ our $NumAcceptedConnections=0;
+
+
+###############################################################################
+# Factory methods
+###############################################################################
+sub newFromServer {
+ my $class=shift;
+
+ my $self=$class->SUPER::newFromServer(shift);
+
+ $self->{'headerBuffer'}='';
+ $self->{'MessageCount'}=0;
+ $self->{'MaxMessageCount'}=0;
+
+ $self->{'ConnectionStart'}=time;
+ my $maxTime=$::CONF{'MaxTime'};
+ if($maxTime>0)
+ {
+ $self->{'ConnectionTimeLimit'}=$self->{'ConnectionStart'}+$maxTime;
+ }
+
+ $::Statistics->{'current_subscribers'}++;
+ $::Statistics->{'subscriber_connections_accepted'}++;
+
+ $self;
+}
+
+###############################################################################
+# Class methods
+###############################################################################
+sub deleteSubscriberWithID {
+ my $class=shift;
+ my $id=shift;
+
+ if(exists($PersistentConnections{$id}))
+ {
+ $PersistentConnections{$id}->close(0,'newSubscriberWithSameID');
+ }
+}
+
+sub pingPersistentConnections {
+ my $class=shift;
+
+ my @cons=values %PersistentConnections;
+
+ map { $_->ping() } @cons;
+}
+
+sub checkPersistentConnectionsForMaxTime {
+ my $class=shift;
+
+ my $time=time;
+ my @cons=values %PersistentConnections;
+
+ map { $_->checkForMaxTime($time) } @cons;
+}
+
+sub numSubscribers {
+
+ return scalar(keys %PersistentConnections);
+}
+
+###############################################################################
+# Instance methods
+###############################################################################
+sub processLine {
+ my $self=shift;
+ my $line=shift;
+
+ # Once the header was processed we ignore any input
+ return unless(exists($self->{'headerBuffer'}));
+
+ if($line ne '')
+ {
+ #
+ # Accumulate header
+ #
+ $self->{'headerBuffer'}.="$line\n";
+ }
+ else
+ {
+ #
+ # Empty line signals end of header.
+ # Analyze header, register with appropiate channel
+ # and send pending messages.
+ #
+ # GET $::CONF{'SubscriberDynamicPageAddress'}/hostid/streamtype/channeldefs HTTP/1.1
+ #
+ # Find the 'GET' line
+ #
+ if($self->{'headerBuffer'}=~/GET\s+$::CONF{'SubscriberDynamicPageAddress'}\/([0-9a-z]+)\/([0-9a-z]+)\/([a-z0-9_\-\%\.\/]+).*?/i)
+ {
+ $self->{'subscriberID'}=$1;
+ $self->{'mode'}=$2;
+ my $persist=$self->getConf('Persist');
+ my $maxTime=$self->getConf('MaxTime');
+ $self->{'ConnectionTimeLimit'} = ($self->{'ConnectionStart'}+$maxTime) if ($maxTime>0);
+
+ my @channelData=split('/',$3);
+ my $channels={};
+ my $channelName;
+ my $offset;
+ foreach my $chandef (@channelData) {
+ if($chandef=~/^([a-z0-9_\-\%]+)(.(r|b|h)([0-9]*))?$/i) {
+ $channelName = $1;
+ $channels->{$channelName}->{'startIndex'} = undef;
+ if ($3) {
+ $offset = $4;
+ if ($3 eq 'r') { $channels->{$channelName}->{'startIndex'} = $offset; }
+ if ($3 eq 'b') { $channels->{$channelName}->{'startIndex'} = -$offset; }
+ if ($3 eq 'h') { $channels->{$channelName}->{'startIndex'} = 0; }
+ }
+ }
+ }
+ my $useragent = ($self->{'headerBuffer'}=~/User-Agent: (.+)/i) ? $1 : "-";
+
+ delete($self->{'headerBuffer'});
+
+ if ($persist) {
+ $self->deleteSubscriberWithID($self->{'subscriberID'});
+ $PersistentConnections{$self->{'subscriberID'}}=$self;
+ }
+
+ if(scalar(keys %{$channels})) {
+
+ $self->{'channelinfo'} = '';
+ my $citemplate = $self->getConf('ChannelInfoTemplate');
+ foreach $channelName (keys %{$channels}) {
+ my $channel=Meteor::Channel->channelWithName($channelName);
+ $self->{'channels'}->{$channelName}=$channel;
+ $self->{'channelinfo'} .= $channel->descriptionWithTemplate($citemplate);
+
+ }
+ $self->emitOKHeader();
+ foreach $channelName (keys %{$channels}) {
+ my $startIndex=$channels->{$channelName}->{'startIndex'};
+ $self->{'channels'}->{$channelName}->addSubscriber($self,$startIndex,$persist,$self->{'mode'},$useragent);
+ }
+ delete ($self->{'channels'}) unless($persist);
+ $self->close(1, 'responseComplete') unless($persist);
+ return;
+ }
+ }
+ elsif($self->{'headerBuffer'}=~/GET\s+\/disconnect\/(\S+)/)
+ {
+ $self->deleteSubscriberWithID($1);
+ $self->emitOKHeader();
+ $self->close(1, 'disconnectRequested');
+ return;
+ }
+ elsif($self->{'headerBuffer'}=~/GET\s+([^\s\?]+)/)
+ {
+ Meteor::Document->serveFileToClient($1,$self);
+ $self->close(1, 'responseComplete');
+ return;
+ }
+
+ #
+ # If we fall through we did not understand the request
+ #
+ $self->emitErrorHeader();
+ }
+}
+
+sub emitOKHeader {
+ my $self=shift;
+
+ $self->emitHeader('200 OK');
+}
+
+sub emitErrorHeader {
+ my $self=shift;
+
+ $self->emitHeader('404 Not Found');
+ $::Statistics->{'errors_served'}++;
+
+ # close up shop here!
+ $self->close(0, 'error');
+}
+
+sub emitHeader {
+ my $self=shift;
+ my $status=shift;
+
+ my $header=$self->getConf('HeaderTemplate');
+
+ $header=~s/~([^~]*)~/
+ if(!defined($1) || $1 eq '') {
+ '~';
+ } elsif($1 eq 'server') {
+ $::PGM;
+ } elsif($1 eq 'status') {
+ $status;
+ } elsif($1 eq 'servertime') {
+ time;
+ } elsif($1 eq 'channelinfo') {
+ $self->{'channelinfo'};
+ } else {
+ '';
+ }
+ /gex;
+
+ $self->write($header);
+}
+
+sub sendMessages {
+ my $self=shift;
+
+ my $numMessages=0;
+ my $msgTemplate=$self->getConf('MessageTemplate');
+ my $msgData='';
+
+ foreach my $message (@_)
+ {
+ $msgData.=$message->messageWithTemplate($msgTemplate);
+ $numMessages++;
+ }
+
+ return if($numMessages<1);
+
+ $self->write($msgData);
+
+ $::Statistics->{'messages_served'}+=$numMessages;
+
+ my $msgCount=$self->{'MessageCount'};
+ $msgCount+=$numMessages;
+ $self->{'MessageCount'}=$msgCount;
+
+ my $maxMsg=$self->getConf('MaxMessages');
+ if(defined($maxMsg) && $maxMsg>0 && $msgCount>=$maxMsg)
+ {
+ $self->close(1, 'maxMessageCountReached');
+ }
+
+ if($self->{'MaxMessageCount'}>0 && $msgCount>=$self->{'MaxMessageCount'})
+ {
+ $self->close(1, 'maxMessageCountReached');
+ }
+
+}
+
+sub ping {
+ my $self=shift;
+ my $msg=$self->getConf('PingMessage');
+
+ $self->write($msg);
+}
+
+sub closeChannel {
+ my $self=shift;
+ my $channelName=shift;
+
+ return unless(exists($self->{'channels'}->{$channelName}));
+
+ my $channel=$self->{'channels'}->{$channelName};
+ $channel->removeSubscriber($self,'channelClose');
+
+ delete($self->{'channels'}->{$channelName});
+
+ $self->close(0,'channelClose') if(scalar(keys %{$self->{'channels'}})==0);
+}
+
+sub close {
+ my $self=shift;
+ my $noShutdownMsg=shift;
+ my $reason=shift;
+
+ foreach my $channelName (keys %{$self->{'channels'}})
+ {
+ my $channel=$self->{'channels'}->{$channelName};
+ $channel->removeSubscriber($self,$reason);
+ }
+ delete($self->{'channels'});
+
+ # If this connection is in the PersistentConnections array, delete it, then anonymise
+ # it so that if we have to wait for the write buffer to empty before close, it's only
+ # removed once.
+ if(exists($self->{'subscriberID'})) {
+ delete($PersistentConnections{$self->{'subscriberID'}});
+ delete($self->{'subscriberID'});
+ }
+
+ # Send shutdown message unless remote closed or
+ # connection not yet established
+ unless($noShutdownMsg || $self->{'remoteClosed'} || exists($self->{'headerBuffer'}))
+ {
+ my $msg=$self->getConf('SubscriberShutdownMsg');
+ if(defined($msg) && $msg ne '')
+ {
+ $self->write($msg);
+ }
+ }
+
+ $self->SUPER::close();
+}
+
+sub didClose {
+
+ $::Statistics->{'current_subscribers'}--;
+}
+
+sub checkForMaxTime {
+ my $self=shift;
+ my $time=shift;
+
+ $self->close(1,'maxTime') if(exists($self->{'ConnectionTimeLimit'}) && $self->{'ConnectionTimeLimit'}<$time);
+}
+
+sub getConf {
+ my $self=shift;
+ my $key=shift;
+
+ if(exists($self->{'mode'}) && $self->{'mode'} ne '')
+ {
+ my $k=$key.$self->{'mode'};
+
+ if(exists($::CONF{$k})) {
+ return $::CONF{$k};
+ }
+ }
+
+ $::CONF{$key};
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# Subscriber.pm
+#
+# Description:
+# Convenience interface to syslog
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+package Meteor::Syslog;
+###############################################################################
+
+ use strict;
+ use Sys::Syslog;
+
+###############################################################################
+# Configuration
+###############################################################################
+
+ $Meteor::Syslog::DEFAULT_FACILITY='daemon';
+
+ $Meteor::Syslog::_open=0; # set to 1 by _open
+
+###############################################################################
+# Implementation
+###############################################################################
+sub ::syslog {
+
+ my $debug=$::CONF{'Debug'};
+
+ my $priority=shift;
+ return if($priority eq 'debug' && !$debug);
+
+ my $format=shift;
+ my @args=@_;
+
+ if($format eq '')
+ {
+ my $txt=join("\t",@args);
+ $format='%s';
+ @args=($txt);
+ }
+
+ my $facility=$::CONF{'SyslogFacility'} || $Meteor::Syslog::DEFAULT_FACILITY;
+
+ if($debug || $facility eq 'none')
+ {
+ $format=~s/\%m/$!/g;
+
+ my $time = ($::CONF{'LogTimeFormat'} eq 'unix') ? time : localtime(time);
+
+ print STDERR "$time\t$priority\t";
+ print STDERR sprintf($format,@args);
+ print STDERR "\n" unless(substr($format,-1) eq "\n");
+
+ return;
+ }
+
+ unless($Meteor::Syslog::_open)
+ {
+ my $facility=$::CONF{'SyslogFacility'} || $Meteor::Syslog::DEFAULT_FACILITY;
+ openlog($::PGM,0,$facility);
+ $Meteor::Syslog::_open=1;
+ }
+
+ syslog($priority,$format,@args);
+}
+
+sub myWarn {
+ local $SIG{'__DIE__'}='';
+ local $SIG{'__WARN__'}='';
+
+ &::syslog('warning',$_[0]);
+}
+
+sub myDie {
+ local $SIG{'__DIE__'}='';
+ local $SIG{'__WARN__'}='';
+
+ my $inEval=0;
+ my $i=0;
+ my $sub;
+ while((undef,undef,undef,$sub)=caller(++$i))
+ {
+ $inEval=1, last if $sub eq '(eval)';
+ }
+
+ unless($inEval)
+ {
+ &::syslog('err',$_[0]);
+ $Meteor::Socket::NO_WARN_ON_CLOSE=1;
+ exit;
+ }
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+# description: Runs meteord
+# chkconfig: 2345 99 00
+
+# Source function library.
+. /etc/init.d/functions
+
+case "$1" in
+'start')
+echo -n "Starting Meteord: "
+echo 65535 > /proc/sys/fs/file-max
+ulimit -n 65535
+cd /usr/local/meteor
+sudo -u meteor ./meteord >/var/log/meteord 2>&1 &
+echo
+;;
+'stop')
+echo -n "Stopping Meteord: "
+killall meteord && success || failure
+;;
+'reload')
+echo -n "Reloading Meteord configuration: "
+killall -s SIGHUP meteord && success || failure
+;;
+*)
+echo "Usage: $0 { start | stop | reload }"
+;;
+esac
+exit 0
--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# Meteor
+# An HTTP server for the 2.0 web
+# Copyright (c) 2006 contributing authors
+#
+# The Meteor daemon
+#
+# Main program should call Meteor::Config::setCommandLineParameters(@ARGV),.
+# Afterwards anybody can access $::CONF{<parameterName>}, where
+# <parameterName> is any valid parameter (except 'Help') listed in the
+# @DEFAULTS array below.
+#
+###############################################################################
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# For more information visit www.meteorserver.org
+#
+###############################################################################
+
+###############################################################################
+# meterod version
+################################################################################
+
+ $::VERSION='1.06';
+ $::RELEASE_DATE='2008-03-02';
+
+###############################################################################
+# Configuration
+###############################################################################
+
+ use strict;
+
+ use Socket;
+
+ use Meteor::Syslog;
+
+ use Meteor::Socket;
+ use Meteor::Connection;
+ use Meteor::Controller;
+ use Meteor::Subscriber;
+ use Meteor::Channel;
+ use Meteor::Document;
+ use Meteor::Config;
+
+ $::CRLF="\r\n"; # Line separator to be used throughout all modules
+
+ our $CONTROL_QUEUE_SIZE=5;
+ our $SUBSCRIBER_QUEUE_SIZE=20;
+
+ our $MAIN_LOOP_TIMEOUT=60;
+ our $AGE_CHECK_INTERVALL=60;
+
+ our $MAX_EXIT_DELAY=120;
+
+ our $UDP_MAX_MESSAGE_SIZE=8192;
+
+###############################################################################
+# Main
+###############################################################################
+
+ #
+ # Record startup time
+ #
+ $::STARTUP_TIME=time;
+ $::STARTUP_TIME+=0; # avoid warning
+
+ #
+ # Program name
+ #
+ $::PGM=$0;
+ $::PGM=~s/^.*\///;
+
+ #
+ # Handle command line options and config file
+ #
+ Meteor::Config->setCommandLineParameters(@ARGV);
+
+ #
+ # Do something about warn and die
+ #
+ unless($::CONF{'Debug'})
+ {
+ $SIG{'__WARN__'}=\&Meteor::Syslog::myWarn;
+ $SIG{'__DIE__'}=\&Meteor::Syslog::myDie;
+ }
+
+ &::syslog('info',"$::PGM launched!");
+
+ #
+ # Daemonize
+ #
+ {
+ $0="$::PGM daemon";
+
+ my $facility=$::CONF{'SyslogFacility'} || $Meteor::Syslog::DEFAULT_FACILITY;
+
+ unless($::CONF{'Debug'} || $facility eq 'none')
+ {
+ # close standard file descriptors
+ close(STDIN);
+ close(STDOUT);
+ close(STDERR);
+ chdir("/");
+ umask(0);
+ # fork and exit parent
+ exit if fork;
+ setpgrp(0, $$) if defined $SIG{TTOU};
+ $SIG{TTOU}='ignore' if defined $SIG{TTOU};
+
+ # Avoid 'stdin reopened for output' warning with newer perls
+ open(NULL,'/dev/null');
+ <NULL> if(0);
+
+ open(OUT,">/var/run/$::PGM.pid");
+ print OUT "$$\n";
+ close(OUT);
+ }
+ else
+ {
+ &::syslog('info',"PID\t%s",$$);
+ }
+ }
+
+ #
+ # Signal handlers
+ #
+ $::HUP=$::TERM=$::USR1=$::USR2=0;
+ $SIG{'HUP'}=sub{$::HUP=1};
+ $SIG{'TERM'}=sub{$::TERM=1};
+ $SIG{'USR1'}=sub{$::USR1=1};
+ $SIG{'USR2'}=sub{$::USR2=1};
+
+ #
+ # Run server
+ #
+ my $con_counter=0;
+ my $con;
+
+ my $controlServer=Meteor::Socket->newServer(
+ $::CONF{'ControllerPort'},
+ $CONTROL_QUEUE_SIZE,
+ $::CONF{'ControllerIP'}
+ );
+ my $controlServerFN=$controlServer->fileno();
+
+ my $subscriberServer=Meteor::Socket->newServer(
+ $::CONF{'SubscriberPort'},
+ $SUBSCRIBER_QUEUE_SIZE,
+ $::CONF{'SubscriberIP'}
+ );
+ my $subscriberServerFN=$subscriberServer->fileno();
+
+ my $udpServer=undef;
+ my $udpPort=$::CONF{'UDPPort'};
+ my $udpServerFN=undef;
+ if($udpPort && $udpPort>0)
+ {
+ $udpServer=Meteor::Socket->newUDPServer(
+ $udpPort,
+ $::CONF{'UDPIP'}
+ );
+ $udpServerFN=$udpServer->fileno();
+ }
+
+ my $serverVector='';
+ vec($serverVector,$controlServerFN,1)=1;
+ vec($serverVector,$subscriberServerFN,1)=1;
+ vec($serverVector,$udpServerFN,1)=1 if(defined($udpServerFN));
+
+ my $lastAgeCheck=time;
+
+ my $nextPing=undef;
+ if(exists($::CONF{'PingInterval'}) && $::CONF{'PingInterval'}>2)
+ {
+ $nextPing=$::CONF{'PingInterval'}+$lastAgeCheck;
+ }
+
+ while(!$::TERM)
+ {
+ eval
+ {
+ while(!$::TERM)
+ {
+ my $rVec=$serverVector;
+ my $wVec='';
+ my $eVec='';
+
+ my $rout;
+ my $wout;
+ my $eout;
+
+ Meteor::Connection->addAllHandleBits(\$rVec,\$wVec,\$eVec);
+
+ my $timeout=$MAIN_LOOP_TIMEOUT;
+ if(defined($nextPing))
+ {
+ $timeout=$nextPing-time;
+ }
+
+ my $result=0;
+ if($timeout>0)
+ {
+ $result=&Meteor::Socket::sselect($rout=$rVec,$wout=$wVec,$eout=$eVec,$timeout);
+ }
+
+ if($result>0)
+ {
+ if(vec($rout,$controlServerFN,1))
+ {
+ Meteor::Controller->newFromServer($controlServer);
+ }
+ if(vec($rout,$subscriberServerFN,1))
+ {
+ Meteor::Subscriber->newFromServer($subscriberServer);
+ }
+ if(defined($udpServerFN) && vec($rout,$udpServerFN,1))
+ {
+ &handleUPD($udpServer);
+ }
+
+ Meteor::Connection->checkAllHandleBits($rout,$wout,$eout);
+ }
+ elsif($result<0)
+ {
+ &::syslog('crit',"Select failed: $!");
+ sleep(30);
+ }
+
+ if($::HUP)
+ {
+ $::HUP=0;
+
+ &::syslog('info',"Received SIGHUP, re-reading config and clearing document cache!");
+
+ Meteor::Config->readConfig();
+ Meteor::Config->updateConfig();
+
+ Meteor::Document->clearDocuments()
+ }
+
+ if($::USR1)
+ {
+ $::USR1=0;
+
+ &::syslog('info',"Received SIGUSR1, clearing channel buffers!");
+
+ Meteor::Channel->clearAllBuffers();
+ }
+
+ if($::USR2)
+ {
+ $::USR2=0;
+
+ &::syslog('info',"Received SIGUSR2, clearing document cache!");
+
+ Meteor::Document->clearDocuments()
+ }
+
+ my $t=time;
+ if($t>$lastAgeCheck+$AGE_CHECK_INTERVALL)
+ {
+ my $minTimeStap=time-$::CONF{'MaxMessageAge'};
+ Meteor::Channel->trimMessageStoresByTimestamp($minTimeStap);
+ $lastAgeCheck=time;
+ $t=$lastAgeCheck;
+
+ Meteor::Subscriber->checkPersistentConnectionsForMaxTime();
+ }
+
+ if(defined($nextPing) && $nextPing<=$t)
+ {
+ $nextPing=undef;
+
+ Meteor::Subscriber->pingPersistentConnections();
+
+ if(exists($::CONF{'MaxMessageAge'}) && $::CONF{'MaxMessageAge'}>2)
+ {
+ $nextPing=$::CONF{'PingInterval'}+time;
+ }
+ }
+ }
+ };
+ unless($::TERM)
+ {
+ &::syslog('alert',"$::PGM loop died (will restart in 2 seconds): $@");
+ sleep(2);
+ }
+ }
+
+ #
+ # Proper shutdown
+ #
+ if($::TERM)
+ {
+ &::syslog('info',"Received SIGTERM, begin shutdown!");
+
+ $subscriberServer->close();
+ $controlServer->close();
+
+ unlink("/var/run/$::PGM.pid") unless($::CONF{'Debug'});
+
+ Meteor::Connection->closeAllConnections();
+
+ my $timoutAt=time+$MAX_EXIT_DELAY;
+
+ while(Meteor::Connection->connectionCount() && time<$timoutAt)
+ {
+ my $rVec='';
+ my $wVec='';
+ my $eVec='';
+
+ my $rout;
+ my $wout;
+ my $eout;
+
+ Meteor::Connection->addAllHandleBits(\$rVec,\$wVec,\$eVec);
+
+ my $result=&Meteor::Socket::sselect($rout=$rVec,$wout=$wVec,$eout=$eVec,$timoutAt-time);
+
+ if($result>0)
+ {
+ Meteor::Connection->checkAllHandleBits($rout,$wout,$eout);
+ }
+ }
+
+ if(my $cnt=Meteor::Connection->connectionCount())
+ {
+ &::syslog('info',"$cnt client(s) unresponsive, will shutdown anyway");
+
+ exit(1);
+ }
+
+ &::syslog('info',"shutdown succeeded");
+
+ exit(0);
+ }
+
+ &::syslog('emerg',"$::PGM loop exited");
+
+###############################################################################
+# Subroutines
+###############################################################################
+sub handleUPD {
+ $udpServer=shift;
+
+ my $line;
+ my $hispaddr=recv($udpServer->{'handle'},$line,$::UDP_MAX_MESSAGE_SIZE,0);
+
+ &::syslog('debug',"udp message received: %s",$line);
+
+ return unless($line=~s/^(\S+)\s//);
+
+ my $cmd=$1;
+
+ if($cmd eq 'ADDMESSAGE')
+ {
+ return unless($line=~s/^(\S+)\s//);
+
+ my $channelName=$1;
+ my $channel=Meteor::Channel->channelWithName($channelName);
+ my $msg=$channel->addMessage($line);
+ my $msgID=$msg->id();
+ &::syslog('debug',"udp: new message added, ID %s",$msgID);
+ }
+}
+
+1;
+############################################################################EOF
\ No newline at end of file
--- /dev/null
+MaxTime 240
+PingInterval 3
+Debug 0
+SyslogFacility none
+
+[iframe]
+HeaderTemplate HTTP/1.1 ~status~\r\nServer: ~server~\r\nContent-Type: text/html; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\n\r\n<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r\n<meta http-equiv="Cache-Control" content="no-store">\r\n<meta http-equiv="Cache-Control" content="no-cache">\r\n<meta http-equiv="Pragma" content="no-cache">\r\n<meta http-equiv="Expires" content="Thu, 1 Jan 1970 00:00:00 GMT">\r\n<script type="text/javascript">\r\nwindow.onError = null;\r\nvar domainparts = document.domain.split(".");\r\ndocument.domain = domainparts[domainparts.length-2]+"."+domainparts[domainparts.length-1];\r\nparent.Meteor.register(this);\r\n</script>\r\n</head>\r\n<body onload="try { parent.Meteor.reset(this) } catch (e) {}">\r\n~channelinfo~\r\n
+Persist 1
+
+[xhrinteractive]
+HeaderTemplate HTTP/1.1 ~status~\r\nServer: ~server~\r\nContent-Type: text/html; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\n\r\n.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................\r\n~channelinfo~\r\n
+Persist 1
+
+[simplepoll]
+HeaderTemplate HTTP/1.1 ~status~\r\nServer: ~server~\r\nContent-Type: text/html; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\n\r\n~channelinfo~
+
+[smartpoll]
+HeaderTemplate HTTP/1.1 ~status~\r\nServer: ~server~\r\nContent-Type: text/html; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\n\r\n~channelinfo~
+
+[longpoll]
+HeaderTemplate HTTP/1.1 ~status~\r\nServer: ~server~\r\nContent-Type: text/html; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\n\r\n~channelinfo~
+Persist 1
+MaxMessages 1
+
+[flash]
+HeaderTemplate ~channelinfo~
+Persist 1
+MessageTemplate ~id~/~channel~/~text~\0
+PingMessage .\0
+SubscriberShutdownMsg x\0
+ChannelInfoTemplate CH/~name~/~lastMsgID~\0
+
+[flashloadvars]
+HeaderTemplate HTTP/1.1 ~status~\r\nServer: ~server~\r\nContent-Type: text/html; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\n\r\n~channelinfo~
+MessageTemplate ~id~/~channel~/~text~\n
+Persist 1
+MaxMessages 1
+PingMessage
+ChannelInfoTemplate CH/~name~/~lastMsgID~\n
+SubscriberShutdownMsg x\n
--- /dev/null
+<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
+<cross-domain-policy>
+<allow-access-from domain="*" to-ports="*" />
+</cross-domain-policy>
--- /dev/null
+/*\r
+stream: xhrinteractive, iframe, serversent\r
+longpoll\r
+smartpoll\r
+simplepoll\r
+*/\r
+\r
+Meteor = {\r
+\r
+ callbacks: {\r
+ process: function() {},\r
+ reset: function() {},\r
+ eof: function() {},\r
+ statuschanged: function() {},\r
+ changemode: function() {}\r
+ },\r
+ channelcount: 0,\r
+ channels: {},\r
+ debugmode: false,\r
+ frameref: null,\r
+ host: null,\r
+ hostid: null,\r
+ maxpollfreq: 60000,\r
+ minpollfreq: 2000,\r
+ mode: "stream",\r
+ pingtimeout: 20000,\r
+ pingtimer: null,\r
+ pollfreq: 3000,\r
+ port: 80,\r
+ polltimeout: 30000,\r
+ recvtimes: [],\r
+ status: 0,\r
+ updatepollfreqtimer: null,\r
+\r
+ register: function(ifr) {\r
+ ifr.p = Meteor.process;\r
+ ifr.r = Meteor.reset;\r
+ ifr.eof = Meteor.eof;\r
+ ifr.ch = Meteor.channelInfo;\r
+ clearTimeout(Meteor.frameloadtimer);\r
+ Meteor.setstatus(4);\r
+ Meteor.log("Frame registered");\r
+ },\r
+\r
+ joinChannel: function(channelname, backtrack) {\r
+ if (typeof(Meteor.channels[channelname]) != "undefined") throw "Cannot join channel "+channelname+": already subscribed";\r
+ Meteor.channels[channelname] = {backtrack:backtrack, lastmsgreceived:0};\r
+ Meteor.log("Joined channel "+channelname);\r
+ Meteor.channelcount++;\r
+ if (Meteor.status != 0) Meteor.connect();\r
+ },\r
+\r
+ leaveChannel: function(channelname) {\r
+ if (typeof(Meteor.channels[channelname]) == "undefined") throw "Cannot leave channel "+channelname+": not subscribed";\r
+ delete Meteor.channels[channelname];\r
+ Meteor.log("Left channel "+channelname);\r
+ if (Meteor.status != 0) Meteor.connect();\r
+ Meteor.channelcount--;\r
+ },\r
+\r
+ connect: function() {\r
+ Meteor.log("Connecting");\r
+ if (!Meteor.host) throw "Meteor host not specified";\r
+ if (isNaN(Meteor.port)) throw "Meteor port not specified";\r
+ if (!Meteor.channelcount) throw "No channels specified";\r
+ if (Meteor.status) Meteor.disconnect();\r
+ Meteor.setstatus(1);\r
+ var now = new Date();\r
+ var t = now.getTime();\r
+ if (!Meteor.hostid) Meteor.hostid = t+""+Math.floor(Math.random()*1000000)\r
+ document.domain = Meteor.extract_xss_domain(document.domain);\r
+ if (Meteor.mode=="stream") Meteor.mode = Meteor.selectStreamTransport();\r
+ Meteor.log("Selected "+Meteor.mode+" transport");\r
+ if (Meteor.mode=="xhrinteractive" || Meteor.mode=="iframe" || Meteor.mode=="serversent") {\r
+ if (Meteor.mode == "iframe") {\r
+ Meteor.loadFrame(Meteor.getSubsUrl());\r
+ } else {\r
+ Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/stream.html");\r
+ }\r
+ clearTimeout(Meteor.pingtimer);\r
+ Meteor.pingtimer = setTimeout(Meteor.pollmode, Meteor.pingtimeout);\r
+\r
+ } else {\r
+ Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/poll.html");\r
+ Meteor.recvtimes[0] = t;\r
+ if (Meteor.updatepollfreqtimer) clearTimeout(Meteor.updatepollfreqtimer);\r
+ if (Meteor.mode=='smartpoll') Meteor.updatepollfreqtimer = setInterval(Meteor.updatepollfreq, 2500);\r
+ if (Meteor.mode=='longpoll') Meteor.pollfreq = Meteor.minpollfreq;\r
+ }\r
+ Meteor.lastrequest = t;\r
+ },\r
+\r
+ disconnect: function() {\r
+ if (Meteor.status) {\r
+ clearTimeout(Meteor.pingtimer);\r
+ clearTimeout(Meteor.updatepollfreqtimer);\r
+ clearTimeout(Meteor.frameloadtimer);\r
+ if (typeof CollectGarbage == 'function') CollectGarbage();\r
+ if (Meteor.status != 6) Meteor.setstatus(0);\r
+ Meteor.log("Disconnected");\r
+ }\r
+ },\r
+ \r
+ selectStreamTransport: function() {\r
+ try {\r
+ var test = ActiveXObject;\r
+ return "iframe";\r
+ } catch (e) {}\r
+ if ((typeof window.addEventStream) == "function") return "iframe";\r
+ return "xhrinteractive";\r
+ },\r
+\r
+ getSubsUrl: function() {\r
+ var surl = "http://" + Meteor.host + ((Meteor.port==80)?"":":"+Meteor.port) + "/push/" + Meteor.hostid + "/" + Meteor.mode;\r
+ for (var c in Meteor.channels) {\r
+ surl += "/"+c;\r
+ if (Meteor.channels[c].lastmsgreceived > 0) {\r
+ surl += ".r"+(Meteor.channels[c].lastmsgreceived+1);\r
+ } else if (Meteor.channels[c].backtrack > 0) {\r
+ surl += ".b"+Meteor.channels[c].backtrack;\r
+ } else if (Meteor.channels[c].backtrack < 0 || isNaN(Meteor.channels[c].backtrack)) {\r
+ surl += ".h";\r
+ }\r
+ }\r
+ var now = new Date();\r
+ surl += "?nc="+now.getTime();\r
+ return surl;\r
+ },\r
+\r
+ loadFrame: function(url) {\r
+ try {\r
+ if (!Meteor.frameref) {\r
+ var transferDoc = new ActiveXObject("htmlfile");\r
+ Meteor.frameref = transferDoc;\r
+ }\r
+ Meteor.frameref.open();\r
+ Meteor.frameref.write("<html><script>");\r
+ Meteor.frameref.write("document.domain=\""+(document.domain)+"\";");\r
+ Meteor.frameref.write("</"+"script></html>");\r
+ Meteor.frameref.parentWindow.Meteor = Meteor;\r
+ Meteor.frameref.close();\r
+ var ifrDiv = Meteor.frameref.createElement("div");\r
+ Meteor.frameref.appendChild(ifrDiv);\r
+ ifrDiv.innerHTML = "<iframe src=\""+url+"\"></iframe>";\r
+ } catch (e) {\r
+ if (!Meteor.frameref) {\r
+ var ifr = document.createElement("IFRAME");\r
+ ifr.style.width = "10px";\r
+ ifr.style.height = "10px";\r
+ ifr.style.border = "none";\r
+ ifr.style.position = "absolute";\r
+ ifr.style.top = "-10px";\r
+ ifr.style.marginTop = "-10px";\r
+ ifr.style.zIndex = "-20";\r
+ ifr.Meteor = Meteor;\r
+ document.body.appendChild(ifr);\r
+ Meteor.frameref = ifr;\r
+ }\r
+ Meteor.frameref.setAttribute("src", url);\r
+ }\r
+ Meteor.log("Loading URL '"+url+"' into frame...");\r
+ Meteor.frameloadtimer = setTimeout(Meteor.frameloadtimeout, 5000);\r
+ },\r
+\r
+ pollmode: function() {\r
+ Meteor.log("Ping timeout");\r
+ Meteor.mode="smartpoll";\r
+ clearTimeout(Meteor.pingtimer);\r
+ Meteor.callbacks["changemode"]("poll");\r
+ Meteor.lastpingtime = false;\r
+ Meteor.connect();\r
+ },\r
+\r
+ process: function(id, channel, data) {\r
+ if (id == -1) {\r
+ Meteor.log("Ping");\r
+ Meteor.ping();\r
+ } else if (typeof(Meteor.channels[channel]) != "undefined") {\r
+ Meteor.log("Message "+id+" received on channel "+channel+" (last id on channel: "+Meteor.channels[channel].lastmsgreceived+")\n"+data);\r
+ Meteor.callbacks["process"](data);\r
+ Meteor.channels[channel].lastmsgreceived = id;\r
+ if (Meteor.mode=="smartpoll") {\r
+ var now = new Date();\r
+ Meteor.recvtimes[Meteor.recvtimes.length] = now.getTime();\r
+ while (Meteor.recvtimes.length > 5) Meteor.recvtimes.shift();\r
+ }\r
+ }\r
+ Meteor.setstatus(5);\r
+ },\r
+\r
+ ping: function() {\r
+ if (Meteor.pingtimer) {\r
+ clearTimeout(Meteor.pingtimer);\r
+ Meteor.pingtimer = setTimeout(Meteor.pollmode, Meteor.pingtimeout);\r
+ var now = new Date();\r
+ Meteor.lastpingtime = now.getTime();\r
+ }\r
+ Meteor.setstatus(5);\r
+ },\r
+\r
+ reset: function() {\r
+ if (Meteor.status != 6) {\r
+ Meteor.log("Stream reset");\r
+ Meteor.ping();\r
+ Meteor.callbacks["reset"]();\r
+ var now = new Date();\r
+ var t = now.getTime();\r
+ var x = Meteor.pollfreq - (t-Meteor.lastrequest);\r
+ if (x < 10) x = 10;\r
+ setTimeout(Meteor.connect, x);\r
+ }\r
+ },\r
+\r
+ eof: function() {\r
+ Meteor.log("Received end of stream, will not reconnect");\r
+ Meteor.callbacks["eof"]();\r
+ Meteor.setstatus(6);\r
+ Meteor.disconnect();\r
+ },\r
+\r
+ channelInfo: function(channel, id) {\r
+ Meteor.channels[channel].lastmsgreceived = id;\r
+ Meteor.log("Received channel info for channel "+channel+": resume from "+id);\r
+ },\r
+\r
+ updatepollfreq: function() {\r
+ var now = new Date();\r
+ var t = now.getTime();\r
+ var avg = 0;\r
+ for (var i=1; i<Meteor.recvtimes.length; i++) {\r
+ avg += (Meteor.recvtimes[i]-Meteor.recvtimes[i-1]);\r
+ }\r
+ avg += (t-Meteor.recvtimes[Meteor.recvtimes.length-1]);\r
+ avg /= Meteor.recvtimes.length;\r
+ var target = avg/2;\r
+ if (target < Meteor.pollfreq && Meteor.pollfreq > Meteor.minpollfreq) Meteor.pollfreq = Math.ceil(Meteor.pollfreq*0.9);\r
+ if (target > Meteor.pollfreq && Meteor.pollfreq < Meteor.maxpollfreq) Meteor.pollfreq = Math.floor(Meteor.pollfreq*1.05);\r
+ },\r
+\r
+ registerEventCallback: function(evt, funcRef) {\r
+ Function.prototype.andThen=function(g) {\r
+ var f=this;\r
+ var a=Meteor.arguments\r
+ return function(args) {\r
+ f(a);g(args);\r
+ }\r
+ };\r
+ if (typeof Meteor.callbacks[evt] == "function") {\r
+ Meteor.callbacks[evt] = (Meteor.callbacks[evt]).andThen(funcRef);\r
+ } else {\r
+ Meteor.callbacks[evt] = funcRef;\r
+ }\r
+ },\r
+\r
+ frameloadtimeout: function() {\r
+ Meteor.log("Frame load timeout");\r
+ if (Meteor.frameloadtimer) clearTimeout(Meteor.frameloadtimer);\r
+ Meteor.setstatus(3);\r
+ Meteor.pollmode();\r
+ },\r
+\r
+ extract_xss_domain: function(old_domain) {\r
+ if (old_domain.match(/^(\d{1,3}\.){3}\d{1,3}$/)) return old_domain;\r
+ domain_pieces = old_domain.split('.');\r
+ return domain_pieces.slice(-2, domain_pieces.length).join(".");\r
+ },\r
+\r
+ setstatus: function(newstatus) {\r
+ // Statuses: 0 = Uninitialised,\r
+ // 1 = Loading stream,\r
+ // 2 = Loading controller frame,\r
+ // 3 = Controller frame timeout, retrying.\r
+ // 4 = Controller frame loaded and ready\r
+ // 5 = Receiving data\r
+ // 6 = End of stream, will not reconnect\r
+\r
+ if (Meteor.status != newstatus) {\r
+ Meteor.status = newstatus;\r
+ Meteor.callbacks["statuschanged"](newstatus);\r
+ }\r
+ },\r
+\r
+ log: function(logstr) {\r
+ if (Meteor.debugmode) {\r
+ if (window.console) {\r
+ window.console.log(logstr);\r
+ } else if (document.getElementById("meteorlogoutput")) {\r
+ document.getElementById("meteorlogoutput").innerHTML += logstr+"<br/>";\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+var oldonunload = window.onunload;\r
+if (typeof window.onunload != 'function') {\r
+ window.onunload = Meteor.disconnect;\r
+} else {\r
+ window.onunload = function() {\r
+ if (oldonunload) oldonunload();\r
+ Meteor.disconnect();\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+<html>
+<body>
+<script type="text/javascript">
+window.onError = null;
+var domainparts = document.domain.split(".");
+var thisdomain = document.domain;
+var topdomain = domainparts[domainparts.length-2]+"."+domainparts[domainparts.length-1];
+document.domain = topdomain;
+var lastrequesttime = 0;
+var pollreq = null;
+var polltimer = false;
+var i=0;
+var isaborted = 0;
+parent.Meteor.register(this);
+
+function newXmlHttp() {
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
+ try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
+ try { return new XMLHttpRequest(); } catch(e) {}
+ return null;
+}
+
+function poll() {
+ isaborted = 0;
+ pollreq = null;
+ try {
+ clearTimeout(polltimer);
+ } catch (e) {}
+ var byteoffset = 0;
+ var newdata;
+ try {
+ var url = parent.Meteor.getSubsUrl();
+ } catch(e) {
+ return false;
+ }
+ if (typeof(url)!="undefined") {
+ pollreq = newXmlHttp();
+ pollreq.onreadystatechange = handleresponse;
+ pollreq.open("GET", url, true);
+ var now = new Date();
+ var t = now.getTime();
+ lastrequesttime = t;
+ if (parent.Meteor.polltimeout) polltimer = setTimeout(ptimeout, parent.Meteor.polltimeout);
+ pollreq.send(null);
+ }
+}
+
+function handleresponse() {
+ if (typeof(pollreq) == "object" && pollreq.readyState == 4 && !isaborted) {
+ try {
+ clearTimeout(polltimer);
+ } catch (e) {}
+ try {
+ var y = parent.Meteor.getSubsUrl();
+ } catch (e) { return false };
+ newdata = pollreq.responseText;
+ while (1) {
+ var x = newdata.indexOf("<s"+"cript>");
+ if (x != -1) {
+ y = newdata.indexOf("</"+"script>", x);
+ if (y != -1) {
+ eval(newdata.substring((x+8),y));
+ newdata = newdata.substring(y+9);
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (parent.Meteor.status) {
+ var now = new Date();
+ var t = now.getTime();
+ var x = parent.Meteor.pollfreq - (t-lastrequesttime);
+ if (x < 10) x = 10;
+ pollreq = null;
+ setTimeout(poll, x);
+ }
+ }
+}
+
+function ptimeout() {
+ isaborted = 1;
+ pollreq.abort();
+ pollreq = null;
+ clearTimeout(polltimer);
+ var now = new Date();
+ var t = now.getTime();
+ var x = parent.Meteor.pollfreq - (t-lastrequesttime);
+ if (x < 10) x = 10;
+ setTimeout(poll, x);
+}
+
+poll();
+
+
+</script>
+</body>
+</html>
--- /dev/null
+<html>\r
+<body>\r
+<script type="text/javascript">\r
+window.onError = null;\r
+var domainparts = document.domain.split(".");\r
+document.domain = domainparts[domainparts.length-2]+"."+domainparts[domainparts.length-1];\r
+parent.Meteor.register(this);\r
+var streamreq;\r
+var byteoffset;\r
+var newdata;\r
+\r
+function abort() {\r
+ streamreq.abort();\r
+}\r
+\r
+function newXmlHttp() {\r
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}\r
+ try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}\r
+ try { return new XMLHttpRequest(); } catch(e) {}\r
+ return null;\r
+}\r
+\r
+function startstream() {\r
+ streamreq = newXmlHttp();\r
+ byteoffset = 0;\r
+ newdata = "";\r
+ var url = parent.Meteor.getSubsUrl();\r
+ streamreq.open("GET", url, true);\r
+ streamreq.onreadystatechange = function() {\r
+ if (typeof streamreq == "undefined") return;\r
+ if (streamreq.readyState == 3) {\r
+ extractEvents(streamreq.responseText);\r
+ } else if (streamreq.readyState == 4) {\r
+ extractEvents(streamreq.responseText);\r
+ delete streamreq;\r
+ if (typeof(r)=="function") {\r
+ r();\r
+ }\r
+ }\r
+ }\r
+ streamreq.send(null);\r
+}\r
+\r
+function extractEvents(responsestr) {\r
+ newdata += responsestr.substring(byteoffset);\r
+ byteoffset = responsestr.length;\r
+ while (1) {\r
+ var x = newdata.indexOf("<s"+"cript>");\r
+ if (x != -1) {\r
+ y = newdata.indexOf("</"+"script>", x);\r
+ if (y != -1) {\r
+ eval(newdata.substring((x+8),y));\r
+ newdata = newdata.substring(y+9);\r
+ } else {\r
+\r
+ // Last message is incomplete. Ignore it and it will be processed next time\r
+ break;\r
+ }\r
+ } else {\r
+\r
+ // No more messages\r
+ break;\r
+ }\r
+ }\r
+}\r
+\r
+\r
+\r
+startstream();\r
+</script>\r
+</body>\r
+</html>
\ No newline at end of file