Commit | Line | Data |
54310121 |
1 | package CGI::Push; |
2 | |
3 | # See the bottom of this file for the POD documentation. Search for the |
4 | # string '=head'. |
5 | |
6 | # You can run this file through either pod2man or pod2html to produce pretty |
7 | # documentation in manual or html file format (these utilities are part of the |
8 | # Perl 5 distribution). |
9 | |
10 | # Copyright 1995,1996, Lincoln D. Stein. All rights reserved. |
11 | # It may be used and modified freely, but I do request that this copyright |
12 | # notice remain attached to the file. You may modify this module as you |
13 | # wish, but if you redistribute a modified version, please attach a note |
14 | # listing the modifications you have made. |
15 | |
16 | # The most recent version and complete docs are available at: |
71f3e297 |
17 | # http://stein.cshl.org/WWW/software/CGI/ |
54310121 |
18 | |
424ec8fa |
19 | $CGI::Push::VERSION='1.01'; |
54310121 |
20 | use CGI; |
21 | @ISA = ('CGI'); |
22 | |
424ec8fa |
23 | $CGI::DefaultClass = 'CGI::Push'; |
24 | $CGI::Push::AutoloadClass = 'CGI'; |
25 | |
26 | # add do_push() and push_delay() to exported tags |
27 | push(@{$CGI::EXPORT_TAGS{':standard'}},'do_push','push_delay'); |
54310121 |
28 | |
29 | sub do_push { |
424ec8fa |
30 | my ($self,@p) = CGI::self_or_default(@_); |
54310121 |
31 | |
32 | # unbuffer output |
33 | $| = 1; |
34 | srand; |
424ec8fa |
35 | my ($random) = sprintf("%16.0f",rand()*1E16); |
54310121 |
36 | my ($boundary) = "----------------------------------$random"; |
37 | |
38 | my (@header); |
39 | my ($type,$callback,$delay,$last_page,$cookie,$target,$expires,@other) = |
40 | $self->rearrange([TYPE,NEXT_PAGE,DELAY,LAST_PAGE,[COOKIE,COOKIES],TARGET,EXPIRES],@p); |
41 | $type = 'text/html' unless $type; |
42 | $callback = \&simple_counter unless $callback && ref($callback) eq 'CODE'; |
43 | $delay = 1 unless defined($delay); |
424ec8fa |
44 | $self->push_delay($delay); |
54310121 |
45 | |
46 | my(@o); |
47 | foreach (@other) { push(@o,split("=")); } |
48 | push(@o,'-Target'=>$target) if defined($target); |
49 | push(@o,'-Cookie'=>$cookie) if defined($cookie); |
50 | push(@o,'-Type'=>"multipart/x-mixed-replace; boundary=$boundary"); |
51 | push(@o,'-Server'=>"CGI.pm Push Module"); |
52 | push(@o,'-Status'=>'200 OK'); |
53 | push(@o,'-nph'=>1); |
54 | print $self->header(@o); |
55 | print "${boundary}$CGI::CRLF"; |
56 | |
57 | # now we enter a little loop |
58 | my @contents; |
59 | while (1) { |
60 | last unless (@contents = &$callback($self,++$COUNTER)) && defined($contents[0]); |
424ec8fa |
61 | print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" |
62 | unless $type eq 'dynamic'; |
54310121 |
63 | print @contents,"$CGI::CRLF"; |
64 | print "${boundary}$CGI::CRLF"; |
424ec8fa |
65 | do_sleep($self->push_delay()) if $self->push_delay(); |
66 | } |
67 | |
68 | # Optional last page |
69 | if ($last_page && ref($last_page) eq 'CODE') { |
70 | print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" unless $type =~ /^dynamic|heterogeneous$/i; |
71 | print &$last_page($self,$COUNTER),"$CGI::CRLF${boundary}$CGI::CRLF"; |
54310121 |
72 | } |
54310121 |
73 | } |
74 | |
75 | sub simple_counter { |
76 | my ($self,$count) = @_; |
77 | return ( |
78 | CGI->start_html("CGI::Push Default Counter"), |
79 | CGI->h1("CGI::Push Default Counter"), |
80 | "This page has been updated ",CGI->strong($count)," times.", |
81 | CGI->hr(), |
82 | CGI->a({'-href'=>'http://www.genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html'},'CGI.pm home page'), |
83 | CGI->end_html |
84 | ); |
85 | } |
86 | |
87 | sub do_sleep { |
88 | my $delay = shift; |
89 | if ( ($delay >= 1) && ($delay!~/\./) ){ |
90 | sleep($delay); |
91 | } else { |
92 | select(undef,undef,undef,$delay); |
93 | } |
94 | } |
95 | |
424ec8fa |
96 | sub push_delay { |
97 | my ($self,$delay) = CGI::self_or_default(@_); |
98 | return defined($delay) ? $self->{'.delay'} = |
99 | $delay : $self->{'.delay'}; |
100 | } |
101 | |
54310121 |
102 | 1; |
103 | |
104 | =head1 NAME |
105 | |
106 | CGI::Push - Simple Interface to Server Push |
107 | |
108 | =head1 SYNOPSIS |
109 | |
110 | use CGI::Push qw(:standard); |
111 | |
112 | do_push(-next_page=>\&next_page, |
113 | -last_page=>\&last_page, |
114 | -delay=>0.5); |
115 | |
116 | sub next_page { |
117 | my($q,$counter) = @_; |
118 | return undef if $counter >= 10; |
119 | return start_html('Test'), |
120 | h1('Visible'),"\n", |
121 | "This page has been called ", strong($counter)," times", |
122 | end_html(); |
123 | } |
124 | |
125 | sub last_page { |
126 | my($q,$counter) = @_; |
127 | return start_html('Done'), |
128 | h1('Finished'), |
129 | strong($counter),' iterations.', |
130 | end_html; |
131 | } |
132 | |
133 | =head1 DESCRIPTION |
134 | |
135 | CGI::Push is a subclass of the CGI object created by CGI.pm. It is |
136 | specialized for server push operations, which allow you to create |
137 | animated pages whose content changes at regular intervals. |
138 | |
139 | You provide CGI::Push with a pointer to a subroutine that will draw |
140 | one page. Every time your subroutine is called, it generates a new |
141 | page. The contents of the page will be transmitted to the browser |
142 | in such a way that it will replace what was there beforehand. The |
143 | technique will work with HTML pages as well as with graphics files, |
144 | allowing you to create animated GIFs. |
145 | |
146 | =head1 USING CGI::Push |
147 | |
148 | CGI::Push adds one new method to the standard CGI suite, do_push(). |
149 | When you call this method, you pass it a reference to a subroutine |
150 | that is responsible for drawing each new page, an interval delay, and |
151 | an optional subroutine for drawing the last page. Other optional |
152 | parameters include most of those recognized by the CGI header() |
153 | method. |
154 | |
155 | You may call do_push() in the object oriented manner or not, as you |
156 | prefer: |
157 | |
158 | use CGI::Push; |
159 | $q = new CGI::Push; |
160 | $q->do_push(-next_page=>\&draw_a_page); |
161 | |
162 | -or- |
3e3baf6d |
163 | |
54310121 |
164 | use CGI::Push qw(:standard); |
165 | do_push(-next_page=>\&draw_a_page); |
166 | |
167 | Parameters are as follows: |
168 | |
169 | =over 4 |
3e3baf6d |
170 | |
54310121 |
171 | =item -next_page |
172 | |
173 | do_push(-next_page=>\&my_draw_routine); |
174 | |
175 | This required parameter points to a reference to a subroutine responsible for |
176 | drawing each new page. The subroutine should expect two parameters |
177 | consisting of the CGI object and a counter indicating the number |
178 | of times the subroutine has been called. It should return the |
179 | contents of the page as an B<array> of one or more items to print. |
180 | It can return a false value (or an empty array) in order to abort the |
181 | redrawing loop and print out the final page (if any) |
182 | |
183 | sub my_draw_routine { |
184 | my($q,$counter) = @_; |
185 | return undef if $counter > 100; |
186 | return start_html('testing'), |
187 | h1('testing'), |
188 | "This page called $counter times"; |
189 | } |
190 | |
424ec8fa |
191 | You are of course free to refer to create and use global variables |
192 | within your draw routine in order to achieve special effects. |
193 | |
54310121 |
194 | =item -last_page |
195 | |
196 | This optional parameter points to a reference to the subroutine |
197 | responsible for drawing the last page of the series. It is called |
198 | after the -next_page routine returns a false value. The subroutine |
199 | itself should have exactly the same calling conventions as the |
200 | -next_page routine. |
201 | |
202 | =item -type |
203 | |
204 | This optional parameter indicates the content type of each page. It |
424ec8fa |
205 | defaults to "text/html". Normally the module assumes that each page |
71f3e297 |
206 | is of a homogenous MIME type. However if you provide either of the |
424ec8fa |
207 | magic values "heterogeneous" or "dynamic" (the latter provided for the |
208 | convenience of those who hate long parameter names), you can specify |
209 | the MIME type -- and other header fields -- on a per-page basis. See |
210 | "heterogeneous pages" for more details. |
54310121 |
211 | |
212 | =item -delay |
213 | |
214 | This indicates the delay, in seconds, between frames. Smaller delays |
215 | refresh the page faster. Fractional values are allowed. |
216 | |
217 | B<If not specified, -delay will default to 1 second> |
218 | |
219 | =item -cookie, -target, -expires |
220 | |
221 | These have the same meaning as the like-named parameters in |
222 | CGI::header(). |
223 | |
224 | =back |
225 | |
424ec8fa |
226 | =head2 Heterogeneous Pages |
227 | |
228 | Ordinarily all pages displayed by CGI::Push share a common MIME type. |
229 | However by providing a value of "heterogeneous" or "dynamic" in the |
230 | do_push() -type parameter, you can specify the MIME type of each page |
231 | on a case-by-case basis. |
232 | |
233 | If you use this option, you will be responsible for producing the |
234 | HTTP header for each page. Simply modify your draw routine to |
235 | look like this: |
236 | |
237 | sub my_draw_routine { |
238 | my($q,$counter) = @_; |
239 | return header('text/html'), # note we're producing the header here |
240 | start_html('testing'), |
241 | h1('testing'), |
242 | "This page called $counter times"; |
243 | } |
244 | |
245 | You can add any header fields that you like, but some (cookies and |
246 | status fields included) may not be interpreted by the browser. One |
247 | interesting effect is to display a series of pages, then, after the |
248 | last page, to redirect the browser to a new URL. Because redirect() |
249 | does b<not> work, the easiest way is with a -refresh header field, |
250 | as shown below: |
251 | |
252 | sub my_draw_routine { |
253 | my($q,$counter) = @_; |
254 | return undef if $counter > 10; |
255 | return header('text/html'), # note we're producing the header here |
256 | start_html('testing'), |
257 | h1('testing'), |
258 | "This page called $counter times"; |
259 | } |
3cb6de81 |
260 | |
424ec8fa |
261 | sub my_last_page { |
262 | header(-refresh=>'5; URL=http://somewhere.else/finished.html', |
263 | -type=>'text/html'), |
264 | start_html('Moved'), |
265 | h1('This is the last page'), |
266 | 'Goodbye!' |
267 | hr, |
268 | end_html; |
269 | } |
270 | |
271 | =head2 Changing the Page Delay on the Fly |
272 | |
273 | If you would like to control the delay between pages on a page-by-page |
274 | basis, call push_delay() from within your draw routine. push_delay() |
275 | takes a single numeric argument representing the number of seconds you |
276 | wish to delay after the current page is displayed and before |
277 | displaying the next one. The delay may be fractional. Without |
278 | parameters, push_delay() just returns the current delay. |
279 | |
54310121 |
280 | =head1 INSTALLING CGI::Push SCRIPTS |
281 | |
282 | Server push scripts B<must> be installed as no-parsed-header (NPH) |
283 | scripts in order to work correctly. On Unix systems, this is most |
284 | often accomplished by prefixing the script's name with "nph-". |
285 | Recognition of NPH scripts happens automatically with WebSTAR and |
286 | Microsoft IIS. Users of other servers should see their documentation |
287 | for help. |
288 | |
54310121 |
289 | =head1 AUTHOR INFORMATION |
290 | |
71f3e297 |
291 | Copyright 1995-1998, Lincoln D. Stein. All rights reserved. |
292 | |
293 | This library is free software; you can redistribute it and/or modify |
294 | it under the same terms as Perl itself. |
54310121 |
295 | |
71f3e297 |
296 | Address bug reports and comments to: lstein@cshl.org |
54310121 |
297 | |
298 | =head1 BUGS |
299 | |
300 | This section intentionally left blank. |
301 | |
302 | =head1 SEE ALSO |
303 | |
304 | L<CGI::Carp>, L<CGI> |
3e3baf6d |
305 | |
54310121 |
306 | =cut |
307 | |