2 ###############################################################################
4 # An HTTP server for the 2.0 web
5 # Copyright (c) 2006 contributing authors
12 ###############################################################################
14 # This program is free software; you can redistribute it and/or modify it
15 # under the terms of the GNU General Public License as published by the Free
16 # Software Foundation; either version 2 of the License, or (at your option)
19 # This program is distributed in the hope that it will be useful, but WITHOUT
20 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
24 # You should have received a copy of the GNU General Public License along
25 # with this program; if not, write to the Free Software Foundation, Inc.,
26 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 # For more information visit www.meteorserver.org
30 ###############################################################################
32 package Meteor
::Subscriber
;
33 ###############################################################################
35 ###############################################################################
39 use Meteor
::Connection
;
43 @Meteor::Subscriber
::ISA
=qw(Meteor::Connection);
45 our %PersistentConnections=();
46 our $NumAcceptedConnections=0;
49 ###############################################################################
51 ###############################################################################
55 my $self=$class->SUPER::newFromServer
(shift);
57 $self->{'headerBuffer'}='';
58 $self->{'MessageCount'}=0;
59 $self->{'MaxMessageCount'}=0;
61 $self->{'ConnectionStart'}=time;
62 my $maxTime=$::CONF
{'MaxTime'};
65 $self->{'ConnectionTimeLimit'}=$self->{'ConnectionStart'}+$maxTime;
68 $::Statistics-
>{'current_subscribers'}++;
69 $::Statistics-
>{'subscriber_connections_accepted'}++;
74 ###############################################################################
76 ###############################################################################
77 sub deleteSubscriberWithID
{
81 if(exists($PersistentConnections{$id}))
83 $PersistentConnections{$id}->close(0,'newSubscriberWithSameID');
87 sub pingPersistentConnections
{
90 my @cons=values %PersistentConnections;
92 map { $_->ping() } @cons;
95 sub checkPersistentConnectionsForMaxTime
{
99 my @cons=values %PersistentConnections;
101 map { $_->checkForMaxTime($time) } @cons;
106 return scalar(keys %PersistentConnections);
109 ###############################################################################
111 ###############################################################################
116 # Once the header was processed we ignore any input
117 return unless(exists($self->{'headerBuffer'}));
124 $self->{'headerBuffer'}.="$line\n";
129 # Empty line signals end of header.
130 # Analyze header, register with appropiate channel
131 # and send pending messages.
133 # GET $::CONF{'SubscriberDynamicPageAddress'}/hostid/streamtype/channeldefs HTTP/1.1
135 # Find the 'GET' line
137 if($self->{'headerBuffer'}=~/GET\s+$::CONF{'SubscriberDynamicPageAddress'}\/([0-9a-z
]+)\
/([0-9a-z]+)\/([a-z0-9_\
-\
%\
.\
/]+).*?/i)
139 $self->{'subscriberID'}=$1;
141 my $persist=$self->getConf('Persist');
142 my $maxTime=$self->getConf('MaxTime');
143 $self->{'ConnectionTimeLimit'} = ($self->{'ConnectionStart'}+$maxTime) if ($maxTime>0);
145 my @channelData=split('/',$3);
149 foreach my $chandef (@channelData) {
150 if($chandef=~/^([a-z0-9_\-\%]+)(.(r|b|h)([0-9]*))?$/i) {
152 $channels->{$channelName}->{'startIndex'} = undef;
155 if ($3 eq 'r') { $channels->{$channelName}->{'startIndex'} = $offset; }
156 if ($3 eq 'b') { $channels->{$channelName}->{'startIndex'} = -$offset; }
157 if ($3 eq 'h') { $channels->{$channelName}->{'startIndex'} = 0; }
161 my $useragent = ($self->{'headerBuffer'}=~/User-Agent: (.+)/i) ? $1 : "-";
163 delete($self->{'headerBuffer'});
166 $self->deleteSubscriberWithID($self->{'subscriberID'});
167 $PersistentConnections{$self->{'subscriberID'}}=$self;
170 if(scalar(keys %{$channels})) {
172 $self->{'channelinfo'} = '';
173 my $citemplate = $self->getConf('ChannelInfoTemplate');
174 foreach $channelName (keys %{$channels}) {
175 my $channel=Meteor
::Channel-
>channelWithName($channelName);
176 $self->{'channels'}->{$channelName}=$channel;
177 $self->{'channelinfo'} .= $channel->descriptionWithTemplate($citemplate);
180 $self->emitOKHeader();
181 foreach $channelName (keys %{$channels}) {
182 my $startIndex=$channels->{$channelName}->{'startIndex'};
183 $self->{'channels'}->{$channelName}->addSubscriber($self,$startIndex,$persist,$self->{'mode'},$useragent);
185 delete ($self->{'channels'}) unless($persist);
186 $self->close(1, 'responseComplete') unless($persist);
190 elsif($self->{'headerBuffer'}=~/GET\s+\/disconnect\
/(\S+)/)
192 $self->deleteSubscriberWithID($1);
193 $self->emitOKHeader();
194 $self->close(1, 'disconnectRequested');
197 elsif($self->{'headerBuffer'}=~/GET\s+([^\s\?]+)/)
199 Meteor
::Document-
>serveFileToClient($1,$self);
200 $self->close(1, 'responseComplete');
205 # If we fall through we did not understand the request
207 $self->emitErrorHeader();
214 $self->emitHeader('200 OK');
217 sub emitErrorHeader
{
220 $self->emitHeader('404 Not Found');
221 $::Statistics-
>{'errors_served'}++;
223 # close up shop here!
224 $self->close(0, 'error');
231 my $header=$self->getConf('HeaderTemplate');
233 $header=~s
/~([^~]*)~/
234 if(!defined($1) || $1 eq '') {
236 } elsif($1 eq 'server') {
238 } elsif($1 eq 'status') {
240 } elsif($1 eq 'servertime') {
242 } elsif($1 eq 'channelinfo') {
243 $self->{'channelinfo'};
249 $self->write($header);
256 my $msgTemplate=$self->getConf('MessageTemplate');
259 foreach my $message (@_)
261 $msgData.=$message->messageWithTemplate($msgTemplate);
265 return if($numMessages<1);
267 $self->write($msgData);
269 $::Statistics-
>{'messages_served'}+=$numMessages;
271 my $msgCount=$self->{'MessageCount'};
272 $msgCount+=$numMessages;
273 $self->{'MessageCount'}=$msgCount;
275 my $maxMsg=$self->getConf('MaxMessages');
276 if(defined($maxMsg) && $maxMsg>0 && $msgCount>=$maxMsg)
278 $self->close(1, 'maxMessageCountReached');
281 if($self->{'MaxMessageCount'}>0 && $msgCount>=$self->{'MaxMessageCount'})
283 $self->close(1, 'maxMessageCountReached');
290 my $msg=$self->getConf('PingMessage');
297 my $channelName=shift;
299 return unless(exists($self->{'channels'}->{$channelName}));
301 my $channel=$self->{'channels'}->{$channelName};
302 $channel->removeSubscriber($self,'channelClose');
304 delete($self->{'channels'}->{$channelName});
306 $self->close(0,'channelClose') if(scalar(keys %{$self->{'channels'}})==0);
311 my $noShutdownMsg=shift;
314 foreach my $channelName (keys %{$self->{'channels'}})
316 my $channel=$self->{'channels'}->{$channelName};
317 $channel->removeSubscriber($self,$reason);
319 delete($self->{'channels'});
321 # If this connection is in the PersistentConnections array, delete it, then anonymise
322 # it so that if we have to wait for the write buffer to empty before close, it's only
324 if(exists($self->{'subscriberID'})) {
325 delete($PersistentConnections{$self->{'subscriberID'}});
326 delete($self->{'subscriberID'});
329 # Send shutdown message unless remote closed or
330 # connection not yet established
331 unless($noShutdownMsg || $self->{'remoteClosed'} || exists($self->{'headerBuffer'}))
333 my $msg=$self->getConf('SubscriberShutdownMsg');
334 if(defined($msg) && $msg ne '')
340 $self->SUPER::close();
345 $::Statistics-
>{'current_subscribers'}--;
348 sub checkForMaxTime
{
352 $self->close(1,'maxTime') if(exists($self->{'ConnectionTimeLimit'}) && $self->{'ConnectionTimeLimit'}<$time);
359 if(exists($self->{'mode'}) && $self->{'mode'} ne '')
361 my $k=$key.$self->{'mode'};
363 if(exists($::CONF
{$k})) {
372 ############################################################################EOF