Changeset 7719


Ignore:
Timestamp:
06/10/10 13:57:33 (2 years ago)
Author:
CrawfordCurrie
Message:

Item2521: restore the ability to generate an unknown length response, by flushing the output with the headers as they have been completed up to that point. This restores the old ability for statistics to generate a progress report as it goes along, rather than compiling the page in memory and emitting it en masse. I've only enabled this on the statistics reporting.

Location:
trunk/core/lib/Foswiki
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/core/lib/Foswiki/Engine.pm

    r7453 r7719  
    4646sub run { 
    4747    my $this = shift; 
    48     my $req  = $this->prepare; 
     48    my $req  = $this->prepare(); 
    4949    if ( defined $req ) { 
    5050        my $res = Foswiki::UI::handleRequest($req); 
     
    8888            $res->print($html); 
    8989        } 
    90         $this->finalizeError($res); 
     90        $this->finalizeError($res, $req); 
    9191        return $e->{status}; 
    9292    } 
     
    115115            $res->print($text); 
    116116        } 
    117         $this->finalizeError($res); 
     117        $this->finalizeError($res, $req); 
    118118        return 500;    # Internal server error 
    119119    }; 
     
    268268sub finalize { 
    269269    my ( $this, $res, $req ) = @_; 
    270     $this->finalizeUploads( $res, $req ); 
    271     $this->finalizeHeaders( $res, $req ); 
    272     $this->finalizeBody($res); 
     270    if ($res->outputHasStarted()) { 
     271        $this->flush($res, $req); 
     272    } else { 
     273        $this->finalizeUploads( $res, $req ); 
     274        $this->finalizeHeaders( $res, $req ); 
     275        $this->finalizeBody($res); 
     276    } 
    273277} 
    274278 
     
    289293=begin TML 
    290294 
    291 ---++ ObjectMethod finalizeError( $res ) 
     295---++ ObjectMethod finalizeError( $res, $req ) 
    292296 
    293297Called if some engine especific error happens. 
    294298 
    295299   * =$res= - Foswiki::Response object to get data from 
     300   * =$req= - Foswiki::Request object to get data from 
    296301 
    297302=cut 
    298303 
    299304sub finalizeError { 
    300     my ( $this, $res ) = @_; 
    301     $this->finalizeHeaders($res); 
    302     $this->finalizeBody($res); 
     305    my ( $this, $res, $req ) = @_; 
     306    $this->finalizeHeaders($res, $req); 
     307    $this->finalizeBody($res, $req); 
    303308} 
    304309 
     
    351356=begin TML 
    352357 
    353 ---++ ObjectMethod finalizeBody( $res ) 
     358---++ ObjectMethod finalizeBody( $res, $req ) 
    354359 
    355360   * =$res= - Foswiki::Response object to get data from 
     361   * =$req= - Foswiki::Request object to get data from 
    356362 
    357363Should send $res' body to client. This method calls =write()= 
     
    361367 
    362368sub finalizeBody { 
    363     my ( $this, $res ) = @_; 
     369    my ( $this, $res, $req ) = @_; 
    364370    my $body = $res->body; 
    365371    return unless $body; 
     
    381387=begin TML 
    382388 
     389---++ flush($res, $req) 
     390 
     391Forces the response headers to be emitted if they haven't already been sent 
     392(note that this may in some circumstances result in cookies being missed) 
     393before flushing what is in the body so far. 
     394 
     395Before headers are sent, any Content-length is removed, as a call to 
     396flush is a statement that there's more to follow, but we don't know 
     397how much at this point. 
     398 
     399This function should be used with great care! It requires that the output 
     400headers are fully complete before it is first called. Once it *has* been 
     401called, the response object will refuse any modifications that would alter 
     402the header. 
     403 
     404=cut 
     405 
     406sub flush { 
     407    my ($this, $res, $req) = @_; 
     408 
     409    unless ($res->outputHasStarted()) { 
     410        $res->deleteHeader('Content-Length'); 
     411        $this->finalizeUploads( $res, $req ); 
     412        $this->finalizeHeaders( $res, $req ); 
     413        $this->prepareWrite($res); 
     414        $res->outputHasStarted(1); 
     415    } 
     416 
     417    my $body = $res->body(); 
     418 
     419    if ( Scalar::Util::blessed($body) || ref( $body ) eq 'GLOB' ) { 
     420        throw Foswiki::EngineException('Cannot flush non-text response body'); 
     421    } 
     422 
     423    $this->write($body); 
     424    $res->body(''); 
     425} 
     426 
     427=begin TML 
     428 
    383429---++ ObjectMethod prepareWrite( $res ) 
    384430 
    385 Abstract method, must be defined by inherited classes. 
     431Abstract method, may be defined by inherited classes. 
    386432   * =$res= - Foswiki::Response object to get data from 
    387433 
    388 Shold perform any task needed before writing. 
     434Should perform any task needed before writing. 
    389435That's ok if none needed ;-) 
    390436 
  • trunk/core/lib/Foswiki/Response.pm

    r7709 r7719  
    1616 
    1717package Foswiki::Response; 
     18 
    1819use strict; 
    1920use warnings; 
    2021use Assert; 
    21 use CGI::Util qw(rearrange expires); 
     22 
     23use CGI::Util (); 
    2224 
    2325=begin TML 
     
    2830 
    2931=cut 
    30  
    31 # NOTE: CHECK_ORDER is used to indicate when the body assembly has started. 
    32 # By associating an assert with this action we can ensure that headers are 
    33 # fully assembled before the body print starts - an essential precondition 
    34 # for early flushing of output. 
    35 sub CHECK_ORDER { 
    36     ASSERT( !$_[0]->{startedPrinting} ) if DEBUG; 
    37 } 
    3832 
    3933sub new { 
     
    4135    my $class = ref($proto) || $proto; 
    4236    my $this  = { 
    43         status          => undef, 
    44         headers         => {}, 
    45         body            => undef, 
    46         charset         => 'ISO-8859-1', 
    47         cookies         => [], 
    48         startedPrinting => 0, 
     37        status           => undef, 
     38        headers          => {}, 
     39        body             => undef, 
     40        charset          => 'ISO-8859-1', 
     41        cookies          => [], 
     42        outputHasStarted => 0, 
    4943    }; 
    5044 
     
    6761    my ( $this, $status ) = @_; 
    6862    if ($status) { 
    69         CHECK_ORDER() if DEBUG; 
     63        ASSERT(!$this->{outputHasStarted}, 'Too late to change status') 
     64          if DEBUG; 
    7065        $this->{status} = $status =~ /^\d{3}/ ? $status : undef; 
    7166    } 
     
    105100    my (@header); 
    106101 
    107     CHECK_ORDER; 
     102    ASSERT(!$this->{outputHasStarted}, 'Too late to change headers') if DEBUG; 
    108103 
    109104    # Ugly hack to avoid html escape in CGI::Util::rearrange 
    110105    local $CGI::Q = { escape => 0 }; 
    111106    my ( $type, $status, $cookie, $charset, $expires, $attachment, @other ) = 
    112       rearrange( 
     107      CGI::Util::rearrange( 
    113108        [ 
    114109            [ 'TYPE',   'CONTENT_TYPE', 'CONTENT-TYPE' ], 'STATUS', 
     
    163158        $this->cookies( \@cookies ); 
    164159    } 
    165     $this->{headers}->{Expires} = expires( $expires, 'http' ) 
     160    $this->{headers}->{Expires} = CGI::Util::expires( $expires, 'http' ) 
    166161      if ( defined $expires ); 
    167     $this->{headers}->{Date} = expires( 0, 'http' ) 
     162    $this->{headers}->{Date} = CGI::Util::expires( 0, 'http' ) 
    168163      if defined $expires || $cookie; 
    169164    $this->{headers}->{'Content-Disposition'} = 
     
    186181    my ( $this, $hdr ) = @_; 
    187182    if ($hdr) { 
    188         CHECK_ORDER; 
     183        ASSERT(!$this->{outputHasStarted}, 'Too late to change headers') 
     184          if DEBUG; 
    189185        my %headers = (); 
    190186        while ( my ( $key, $value ) = each %$hdr ) { 
     
    192188            $headers{$key} = $value; 
    193189        } 
    194         $headers{Expires} = expires( $headers{Expires}, 'http' ) 
     190        $headers{Expires} = CGI::Util::expires( $headers{Expires}, 'http' ) 
    195191          if defined $headers{Expires}; 
    196         $headers{Date} = expires( 0, 'http' ) 
     192        $headers{Date} = CGI::Util::expires( 0, 'http' ) 
    197193          if defined $headers{'Set-Cookie'} || defined $headers{Expires}; 
    198194        if ( defined $headers{'Set-Cookie'} ) { 
     
    253249            } 
    254250            elsif ( $hdr eq 'Expires' ) { 
    255                 $value = expires( $value, 'http' ); 
     251                $value = CGI::Util::expires( $value, 'http' ); 
    256252            } 
    257253            elsif ( $hdr eq 'Set-Cookie' ) { 
     
    262258        } 
    263259    } 
    264     $this->{headers}{Date} = expires( 0, 'http' ) 
     260    $this->{headers}{Date} = CGI::Util::expires( 0, 'http' ) 
    265261      if !exists $this->{headers}{Date} 
    266262          && (   defined $this->{headers}{Expires} 
     
    302298    my $this = shift; 
    303299 
    304     CHECK_ORDER; 
     300    ASSERT(!$this->{outputHasStarted}, 'Too late to change headers') if DEBUG; 
    305301 
    306302    foreach (@_) { 
     
    321317    my ( $this, $hdr, $value ) = @_; 
    322318 
    323     CHECK_ORDER; 
     319    ASSERT(!$this->{outputHasStarted}, 'Too late to change headers') if DEBUG; 
    324320 
    325321    $hdr =~ s/(?:^|(?<=-))(.)([^-]*)/\u$1\L$2\E/g; 
     
    398394sub redirect { 
    399395    my ( $this, @p ) = @_; 
     396    ASSERT(!$this->{outputHasStarted}, 'Too late to redirect') if DEBUG; 
    400397    my ( $url, $status, $cookies ) = 
    401       rearrange( [ [qw(LOCATION URL URI)], 'STATUS', [qw(COOKIE COOKIES)], ], 
    402         @p ); 
     398      CGI::Util::rearrange( 
     399          [ [qw(LOCATION URL URI)], 'STATUS', [qw(COOKIE COOKIES)], ], 
     400          @p ); 
    403401 
    404402    return unless $url; 
     
    415413---++ ObjectMethod print(...) 
    416414 
    417 Add content to the end of the body. The print may not be flushed until the 
    418 body is complete. 
     415Add content to the end of the body. 
    419416 
    420417=cut 
     
    422419sub print { 
    423420    my $this = shift; 
    424     $this->{startedPrinting} = 1; 
    425421    $this->body( ( $this->{body} || '' ) . join( '', @_ ) ); 
     422} 
     423 
     424=begin TML 
     425 
     426---++ ObjectMethod outputHasStarted([$boolean]) 
     427 
     428Get/set the output-has-started flag. This is used by the Foswiki::Engine 
     429to separate header and body output. Once output has started, the headers 
     430cannot be changed (though the body can be modified) 
     431 
     432=cut 
     433 
     434sub outputHasStarted { 
     435    my ($this, $flag ) = @_; 
     436    $this->{outputHasStarted} = $flag if defined $flag; 
     437    return $this->{outputHasStarted}; 
    426438} 
    427439 
  • trunk/core/lib/Foswiki/UI.pm

    r7682 r7719  
    385385            $res->print($html); 
    386386        } 
    387         $Foswiki::engine->finalizeError($res); 
     387        $Foswiki::engine->finalizeError($res, $session->{request}); 
    388388        return $e->{status}; 
    389389    } 
     
    397397        $res ||= new Foswiki::Response(); 
    398398 
    399         $res->header( -type => 'text/plain' ); 
     399        $res->header( -type => 'text/plain' ) 
     400          unless $res->outputHasStarted(); 
    400401        if (DEBUG) { 
    401402 
  • trunk/core/lib/Foswiki/UI/Statistics.pm

    r7682 r7719  
    496496    } 
    497497    $session->{response}->print( $msg . "\n" ) if $msg; 
     498    $Foswiki::engine->flush($session->{response}, $session->{request}); 
    498499} 
    499500 
Note: See TracChangeset for help on using the changeset viewer.