package Apache2::ASP::Response; use strict; use warnings 'all'; use Carp qw( cluck confess croak ); use Apache2::Const "-compile" => ':common'; use HTTP::Date qw( time2iso str2time time2str ); use Apache2::ASP::ApacheRequest; our $MAX_BUFFER_LENGTH = 1024 ** 2; #============================================================================== sub new { my ($s, $asp) = @_; return bless { # asp => $asp, _buffer => [ ], _buffer_length => 0, r => $asp->r, q => $asp->q, _headers => [ {name => 'connection', value => 'close'} ], _sent_headers => 0, Buffer => 1, ContentType => 'text/html', Status => 200, ApacheStatus => Apache2::Const::OK, Expires => 0, ExpiresAbsolute => time2str(time), }, $s; }# end new() sub asp { $_[0]->{asp} || $main::_ASP::ASP } #============================================================================== sub Declined { my $s = shift; return -1; }# end Declined() #============================================================================== sub Buffer { my $s = shift; if( @_ ) { return $s->{Buffer} = shift; } else { return $s->{Buffer}; }# end if() }# end Buffer() #============================================================================== sub Expires { my $s = shift; if( @_ ) { $s->{Expires} = shift; $s->ExpiresAbsolute( time2str( time() + $s->{Expires} ) ); return $s->{Expires}; } else { return $s->{Expires}; }# end if() }# end Expires() #============================================================================== sub ExpiresAbsolute { my $s = shift; if( @_ ) { return $s->{ExpiresAbsolute} = shift; } else { return $s->{ExpiresAbsolute}; }# end if() }# end Expires() #============================================================================== sub AddHeader { my ($s, $key, $val) = @_; push @{$s->{_headers}}, { name => $key, value => $val, }; }# end AddHeader() #============================================================================== sub Headers { my $s = shift; return { map { $_->{name} => $_->{value} } @{$s->{_headers}} }; }# end Headers() #============================================================================== sub Cookies { my ($s, $name, $value) = @_; no warnings 'uninitialized'; my $escape = $s->{q}->can('escape') ? sub { $s->{q}->escape(@_) } : sub { $s->{q}->url_encode(@_) }; return $s->AddHeader( "Set-Cookie" => "$name=" . $escape->("$value") ); }# end Cookies() #============================================================================== sub Write { my ($s, $str) = @_; $str = "" unless defined($str); my $len = length($str); $str =~ s/_____TILDE_____/\~/g; no warnings 'uninitialized'; push @{$s->{_buffer}}, $str; if( $s->{Buffer} ) { $s->{_buffer_length} += $len; if( $s->{_buffer_length} >= $MAX_BUFFER_LENGTH ) { $s->Flush; }# end if() } else { $s->Flush; }# end if() }# end Write() #============================================================================== sub Flush { my $s = shift; my $buffer = join '', @{delete( $s->{_buffer} )}; $s->{_buffer} = [ ]; $s->{_buffer_length} = 0; if( $s->asp->{handler} && $s->asp->{handler}->isa('Apache2::ASP::PageHandler') && $s->asp->global_asa ) { $s->asp->global_asa->can('Script_OnFlush')->( \$buffer ) unless $s->{is_subrequest}; }# end if() $s->_print_headers(); no warnings 'uninitialized'; $s->{r}->print( $buffer ); $s->{r}->rflush(); }# end Flush() #============================================================================== sub End { my $s = shift; $s->Flush; # Cancel execution and force the server to stop processing this request. my $sock = $s->{r}->connection->client_socket; eval { $sock->close() }; $s->asp->{did_end} = 1; }# end End() #============================================================================== sub Clear { my $s = shift; $s->{_buffer} = [ ]; $s->{_buffer_length} = 0; }# end Clear() #============================================================================== sub Redirect { my ($s, $location) = @_; if( $s->{_sent_headers} ) { croak "Response.Redirect: Cannot redirect to '$location' after headers have been sent."; }# end if() $s->Clear(); $s->{ContentType} = ''; $s->{Status} = 302; # '302 Found'; $s->AddHeader('Location' => $location); $s->Flush(); $s->End; return 302; }# end Redirect() #============================================================================== sub Include { my ($s, $script, @args) = @_; no warnings 'uninitialized'; unless( -f $script ) { $s->Write("[ Cannot Response.Include '$script': File not found ]"); croak "Cannot Response.Include '$script': File not found"; }# end unless() my $uri = $script; my $root = $s->asp->config->www_root; $uri =~ s/^$root//; my $r = Apache2::ASP::ApacheRequest->new( r => $s->asp->r, status => 200, filename => $script, uri => $uri ); my $asp = ref($s->asp)->new( $s->asp->config ); $asp->{ $_ } = $s->asp->{ $_ } foreach grep { exists($s->asp->{$_}) } qw/ session application service subservice registry_member /; $asp->setup_request( $r, $s->asp->q() ); eval { $asp->execute( 1, @args ); $s->Write( $r->buffer ); }; if( $@ ) { croak "Cannot Include script '$script': $@"; }# end if() }# end Include() #============================================================================== sub TrapInclude { my ($s, $script, @args) = @_; no warnings 'uninitialized'; unless( -f $script ) { $s->Write("[ Cannot Response.TrapInclude '$script': File not found ]"); croak "Cannot Response.TrapInclude '$script': File not found"; }# end unless() my $uri = $script; my $root = $s->asp->config->www_root; $uri =~ s/^$root//; my $r = Apache2::ASP::ApacheRequest->new( r => $s->asp->r, status => 200,# '200 OK', filename => $script, uri => $uri ); my $asp = ref($s->asp)->new( $s->asp->config ); $asp->{ $_ } = $s->asp->{ $_ } foreach grep { exists($s->asp->{$_}) } qw/ session application service subservice registry_member /; $asp->setup_request( $r, $s->asp->q() ); my $include = eval { $asp->execute( 1, @args ); $asp->response->End; $r->buffer; }; if( $@ ) { croak "Cannot TrapInclude script '$script': $@"; }# end if() return $include; }# end TrapInclude() #============================================================================== sub IsClientConnected { my $s = shift; return ! $s->{r}->connection->aborted; }# end IsClientConnected() #============================================================================== sub _print_headers { my $s = shift; return if $s->{_sent_headers}; $s->{r}->content_type( $s->{ContentType} || 'text/html' ); my ($status) = $s->{Status} =~ m/^(\d+)/; $s->{r}->status( $status ) if defined( $status ); my $headers = $s->{r}->headers_out; foreach my $header ( @{$s->{_headers}} ) { $headers->{ $header->{name} } = $header->{value}; }# end foreach() $headers->{Expires} = $s->{ExpiresAbsolute}; $s->{r}->headers_out( $headers ); $s->{_sent_headers} = 1; }# end _print_headers() #============================================================================== sub DESTROY { }# end DESTROY() 1;# return true: __END__ =pod =head1 NAME Apache2::ASP::Response - Interact with the client. =head1 SYNOPSIS <% # Add a cookie: $Response->Cookies( cookiename => "cookie value" ); # Add another HTTP header: $Response->AddHeader( 'x-micro-payment-required' => '0.001' ); # Set the content-type header: $Response->{ContentType} = 'text/html'; # Set the expiration date to 3 minutes ago: $Response->{Expires} = -3; # Print data to the client: $Response->Write("Welcome to the web page.
"); # Include another file: $Response->Include( $Server->MapPath("/my-script.asp"), {arg => 'value'} ); # Get the output from another file: my $result = $Response->TrapInclude( $Server->MapPath("/another-script.asp") ); # Get a server variable: my $host = $Request->ServerVariables("HTTP_HOST"); # Redirect: $Response->Redirect( "/new/page.asp" ); # End processing and stop transmission: $Response->End; # Flush data to the client: $Response->Flush; # Clear the buffer: $Response->Clear(); # Force auto-flush (no buffering): $Response->{Buffer} = 0; # Do something that takes a long time: while( not_done_yet() && $Response->IsClientConnected ) { # do stuff... }# end while() %> =head1 DESCRIPTION The global C<$Response> object is an instance of C. =head1 PUBLIC METHODS =head2 new( $asp ) =head2 AddHeader( $name, $value ) Adds a new header to the HTTP response For example, the following: <% $Response->AddHeader( "funny-factor" => "funny" ); %> Sends the following in the HTTP response: funny-factor: funny =head2 Headers( ) Returns a name/value hash of all the HTTP headers that have been set via C. =head2 Cookies( $name, $value ) Sends a cookie to the client. =head2 Write( $str ) Writes data to the client. If buffering is enabled, the output will be deferred until C is finally called (automatically or manually). If buffering is disabled, the output will be sent immediately. =head2 Flush( ) Causes the response buffer to be printed to the client immediately. If the HTTP headers have not been sent, they are sent first before the response buffer is sent. =head2 End( ) Stops processing and closes the connection to the client. The script will abort right after calling C. =head2 Clear( ) Empties the response buffer. If C has already been called, an exception is thrown instead. =head2 Redirect( $url ) Causes the client to be redirected to C<$url>. If C has already been called, an exception is thrown instead. =head2 Include( $path, %args ) Executes the script located at C<$path> and passes C<%args> to the script. The result of the included script is included into the current response buffer. The contents of C<%args> are available to the included script as C<@_>. =head2 TrapInclude( $path ) Executes the ASP script located at C<$path> and returns its results as a string. =head2 IsClientConnected( ) Checks to see if the client is still connected. Returns 1 if connected, 0 if not. =head2 Expires( [$minutes] ) Set/get the number of minutes between now and when the content will expire. Negative values are permitted. Default is C<0>. =head2 ExpiresAbsolute( [$http_datetime] ) Set/get the date in HTTP date format when the content will expire. Default is now. =head2 Buffer( [$bool] ) Gets/sets the buffering behavior. Default value is C<1>. # Turn off buffering, forcing output to be flushed to the client immediately: $Response->Buffer(0); # Turn on buffering. Wait until the request is finished before the buffer is sent: $Response->Buffer(1); =head2 Declined( ) Intended for use within an L subclass, C returns a value of C<-1>, which is equivallent to C. =head1 BUGS It's possible that some bugs have found their way into this release. Use RT L to submit bug reports. =head1 HOMEPAGE v of Apache2::ASP in action. =head1 AUTHOR John Drago L =head1 COPYRIGHT AND LICENSE Copyright 2007 John Drago, All rights reserved. This software is free software. It may be used and distributed under the same terms as Perl itself. =cut