Loading ...
Sorry, an error occurred while loading the content.
 

RE: Eagle book RandPicture.pm, $r->internal_redirect, and IE 6.0 showing same image every time

Expand Messages
  • Frank Maas
    ... Well good luck. IMHO no_cache(1) is working fine and is this a client-side problem . Webcam viewers have the same problem. There it is solved by appending
    Message 1 of 15 , Jul 6, 2005
      > Frank Maas wrote:
      >
      >> What you can try is to update the Last-Modified header on each
      >> request. Bear in mind that this won't work for requests that are made
      >> within the same second. To probably state the obvious: it is
      >> necessary that you set the header to the timestamp of the request,
      >> not to the timestamp of the underlying image!
      >
      > I'd prefer to get no_cache(1) working, but will keep the various
      > date-forcing ideas as backups.

      Well good luck. IMHO no_cache(1) is working fine and is this a client-side
      "problem". Webcam viewers have the same problem. There it is solved by
      appending a dummy time-related variable to the request, like

      <img src="/ImageServer/Image&dummy=1234567">

      For some browsers the mere fact that the request was made with parameters
      is enough reason to never use it again, for others (that remember the
      complete request) you need to change the value for dummy. There are
      Javascript bits to do this.

      Again good luck!

      Frank
    • Geoffrey Young
      ... alright, I think I see what s going on here. I ll explain technically and then try to explain the rationale I think is behind it. the reason
      Message 2 of 15 , Jul 6, 2005
        > I added some debug logging to see headers_out() before and after calling
        > no_cache(1). no_cache(1) appears to be setting the specified headers and
        > internal_redirect() appears to be ignoring them.

        alright, I think I see what's going on here. I'll explain technically and
        then try to explain the rationale I think is behind it.

        the reason $r->no_cache(1) isn't affecting your code is that an
        internal_redirect() is purposefully ignoring the headers_out table when it
        issues the redirect. to paraphrase the code, when you call
        $r->internal_redirect apache creates a _new_ request and sends that request
        through the standard request cycle, starting with uri translation. when the
        new request is created it copies various things from the current request,
        one of which is _not_ the headers_out table. here's the code from
        http_request.c in 1.3:

        request_rec *new;
        ...
        new->headers_in = r->headers_in;
        new->headers_out = ap_make_table(r->pool, 12);

        so, when you call $r->internal_redirect() you get the properties of the
        incoming request, but inherit none of the properties of current response.

        please note this is an apache thing, not a mod_perl thing, so it's nothing
        that mod_perl is doing "wrong." as with almost all that mod_perl does, the
        API of internal_redirect() is an apache C one that mod_perl merely passes on
        to you in perl.

        ok, now for the rationale. I can only suspect that the reason for this
        behavior is that the point of internal_redirect() is to act just like a full
        external redirect (ie returning REDIRECT with a Location header) except
        you're not letting the browser know about it. in other words,
        internal_redirect() is a transparent replacement of the current content with
        content from a different URI, cache headers and all.

        in truth, I think the behavior you're observing is the desirable one for the
        vast majority of cases of using an internal redirect. for example, sending
        the ETag header from the current request when calling internal_redirect() to
        another URI would probably break the rules surrounding ETag, which is
        supposed to be unique for each resource.

        but of course, none of this helps your code. hopefully it at least helps
        your understanding :)

        if you're just playing around I guess the workaround is to use a subrequest
        instead of internal_redirect(). for example

        my $sub = $r->lookup_uri($lucky);
        $sub->no_cache(1);
        $sub->run;

        if you really want to use internal_redirect() I have a rather complex
        solution that _might_ work. take this module:
        http://www.modperlcookbook.org/code/ch03/Cookbook-SimpleRequest-0.01.tar.gz

        crack it open and s/assbackwards/no_local_copy/. then from your code you
        would call $r->no_local_copy(1) instead of $r->no_cache(1). it's not
        guaranteed to work but it probably will :) of course, in mod_perl 2.0 you
        get access to no_local_copy() without the acrobatics.

        HTH

        --Geoff
      • David Christensen
        ... Okay. ... It looks like $r- run() is the best answer. (BTW it works without the call to no_cache(1).) Thanks! :-) David
        Message 3 of 15 , Jul 6, 2005
          Geoffrey Young wrote:
          > ... here's the code from http_request.c in 1.3:
          > request_rec *new;
          > ...
          > new->headers_in = r->headers_in;
          > new->headers_out = ap_make_table(r->pool, 12);
          > ... this is an apache thing, not a mod_perl thing,

          Okay.


          > my $sub = $r->lookup_uri($lucky);
          > $sub->no_cache(1);
          > $sub->run;

          It looks like $r->run() is the best answer. (BTW it works without the call to
          no_cache(1).)


          Thanks! :-)

          David
        • David Christensen
          modperl: I found confirmation regarding the issue of headers getting lost on internal redirection on p. 172 of the Eagle book: Another important detail about
          Message 4 of 15 , Jul 16, 2005
            modperl:

            I found confirmation regarding the issue of headers getting lost on internal
            redirection on p. 172 of the Eagle book:

            "Another important detail about error handling is that Apache
            ignores the fields that you set with header_out() when your module
            generates an error status or invokes an internal redirect."


            Reading further:

            "For these cases, call the request object's err_header_out()
            method. It has identical syntax to header_out(), but the fields
            that you set with it are sent to the browser only when an error has
            occurred. Unlike ordinary headers, the fields set with
            err_header_out() persist across internal redirections..."


            RTM perldoc Apache under $r->err_headers_out:

            "The difference between headers_out and err_headers_out is that the
            latter are printed even on error, and persist across internal redi-
            rects (so the headers printed for ErrorDocument handlers will have
            them).


            So, I tried using err_headers_out to set "Pragma: no-cache" and "Cache-control:
            no-cache" headers. wget sees them, but IE still displays the same picture. :-(


            STFW:

            http://support.microsoft.com/kb/q222064/


            Interesting, but an unworkable "RESOLUTION". Looking some more:

            http://support.microsoft.com/kb/q234067/


            So, I added an "Expires: -1" header. Strange. It seemed to work for a few
            clicks, and then got stuck. I guess this first few were the different Apache
            children sending different pictures, but once the newest picture was sent, the
            browser got stuck on its cache (?). Dig some more:

            http://psacake.com/web/s.asp


            Implement "Expires: 0" and Expiresabsolute as time() - 1. Nope. Not even a
            teaser.


            Try setting Expires to time() -1. Nope.


            Try setting Expires to time() - 24*3600. Nope.


            Dig some more:

            http://www.phpbuilder.com/tips/item.php?id=123


            Looks like it's worth giving a try. But, how do I set the same header three
            times? Hmmm... Looks like err_header_out() doesn't accept an array ref, and
            calling it multiple times just steps on previous values. After beating my head
            against that for a while, it turns out that "Cache-control: max-age=0" seems to
            be enough. It also looks like Expires and "Pragma: no-cache" are unnecessary.


            So, to prevent caching in IE 6.0 SP2 when using Apache 1.3.33
            internal_redirect(), call err_header_out() and set the "Last-Modified" header to
            the current time and the "Cache-control" header to "max-age=0".


            HTH,

            David



            #######################################################################
            # $Id: RandomPicture.pm,v 1.7 2005/07/17 03:03:17 dpchrist Exp $
            #
            # Redirect to random picture per [1] pp. 123-128 using internal
            # redirection and err_header_out().
            #
            # Copyright 2005 by David Christensen <dpchrist@...>
            #
            # References:
            #
            # [1] Lincoln Stein & Doug MacEachern, 1999, "Writing Apache Modules
            # with Perl and C", O'Reilly, ISBN 1-56592-567-X.
            #######################################################################
            # Apache::NavBar package:
            #----------------------------------------------------------------------

            package Apache::RandomPicture;

            #######################################################################
            # uses:
            #----------------------------------------------------------------------

            use strict;
            use warnings;

            use Apache::Constants qw(:common REDIRECT DOCUMENT_FOLLOWS);
            use Data::Dumper;
            use DirHandle;

            $Data::Dumper::Indent = 0;

            #######################################################################
            # package globals:
            #----------------------------------------------------------------------

            our $debug = 1;

            our $picturedir_directive = 'PictureDir';

            #######################################################################
            # subroutines:
            #----------------------------------------------------------------------

            sub handler
            {
            $_[0]->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([\@_], [qw(*_)])) if $debug;

            my $r = shift;

            my $retval = DECLINED; ##### pessimistic execution

            my $dir_uri = $r->dir_config($picturedir_directive);
            unless ($dir_uri) {
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            "unable to find Apache configuration directive ",
            "'$picturedir_directive'");
            goto done;
            }
            $dir_uri .= '/' unless $dir_uri =~ m:/$:;
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([$dir_uri], [qw(dir_uri)])) if $debug;

            my $subr = $r->lookup_uri($dir_uri);
            my $dir = $subr->filename;
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([$dir], [qw(dir)])) if $debug;
            my $dh = DirHandle->new($dir);
            unless ($dh) {
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            "unable to read directory '$dir': $!");
            goto done;
            }

            my @files;
            for my $entry ($dh->read) {
            my $rr = $subr->lookup_uri($entry);
            my $type = $rr->content_type;
            next unless $type =~ m:^image/:;
            push @files, $rr->uri;
            }
            $dh->close;
            unless (scalar @files) {
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            "no image files found in directory '$dir'");
            goto done;
            }
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([\@files], [qw(*files)])) if $debug;

            my $lucky_one = $files[rand scalar @files];
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([$lucky_one], [qw(lucky_one)])) if $debug;

            my $lucky_uri = $r->lookup_uri($lucky_one);
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([$lucky_uri], [qw(lucky_uri)])) if $debug;
            unless ($lucky_uri->status == DOCUMENT_FOLLOWS) {
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            "error looking up URI '$lucky_one'");
            goto done;
            }

            $r->content_type($lucky_uri->content_type);
            if ($r->header_only) {
            $r->send_http_header;
            }
            else {
            my %err_headers_out = $r->err_headers_out;
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([\%err_headers_out],
            [qw(err_headers_out)])) if $debug;

            ##### After much pain and suffering, these are the headers needed for
            ##### IE 6.0 SP2 to display the picture Apache was sending, rather than
            ##### using it's cache. The clue came from here:
            ##### http://www.phpbuilder.com/tips/item.php?id=123

            ##### This one doesn't seem to be necessary.
            # $r->err_header_out("Expires" => 0);

            ##### Subrequest will send a "Last-Modified" header per the document
            ##### date/time stamp. Commenting this out causes IE to cache. I
            ##### guess IE uses the most recent of the two headers (?), so we
            ##### need this:
            $r->err_header_out("Last-Modified" => time());

            ##### err_header_out() doesn't seem to accept multiple values for the
            ##### same header (I tried an array ref; no luck). Subsequent calls
            ##### step on the value (scalar) set by previous calls. The first
            ##### header alone doesn't work. The second and third headers alone
            ##### seem to stop caching. Use the third one.
            # $r->err_header_out("Cache-control"=>"no-cache");
            # $r->err_header_out("Cache-control"=>"post-check=0,pre-check=0");
            $r->err_header_out("Cache-control"=>"max-age=0");

            ##### This one doesn't seem to be necessary.
            # $r->err_header_out("Pragma" => "no-cache");

            %err_headers_out = $r->err_headers_out;
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([\%err_headers_out],
            [qw(err_headers_out)])) if $debug;
            $r->internal_redirect($lucky_one);
            }

            $retval = OK;

            done:
            $r->log_error(sprintf("%s (%s %s): ",
            (caller(0))[3], __FILE__, __LINE__),
            Data::Dumper->Dump([$retval], [qw(retval)])) if $debug;
            return $retval;
            }

            #######################################################################
            # end of code:
            #----------------------------------------------------------------------

            1;

            __END__

            #######################################################################
          Your message has been successfully submitted and would be delivered to recipients shortly.