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
  • David Christensen
    ... 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
    Message 1 of 15 , Jul 5, 2005
    • 0 Attachment
      Michael Peters wrote:

      > no_cache() should work (or at least it has for me in the past when IE
      > has given me similar headaches).
      > That's odd. no_cache should not be sending the 'Expires' header.
      > Instead it uses the more forceful 'Pragma: no-cache' and
      > 'Cache-control: no-cache'. These are not showing up in your headers.
      > The only thing I can think of is that no_cache(1) is not being
      > executed?

      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.


      Any suggestions?


      TIA,

      David
      --



      #######################################################################
      # $Id: RandomPicture.pm,v 1.6 2005/07/06 00:56:53 dpchrist Exp $
      #
      # Redirect to random picture per [1] pp. 123-128.
      #
      # Copyright 2005 by David Christensen <dpchrist@...>
      #
      # References:
      #
      # [1] Lincoln Stein & Doug MacEachern, 1999, "Wring 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);
      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 %headers_out = %{$r->headers_out};
      $r->log_error(sprintf("%s (%s %s): ",
      (caller(0))[3], __FILE__, __LINE__),
      "calling no_cache(1)...",
      Data::Dumper->Dump([\%headers_out], [qw(*headers_out)]));
      my @no_cache = $r->no_cache(1);
      %headers_out = %{$r->headers_out};
      $r->log_error(sprintf("%s (%s %s): ",
      (caller(0))[3], __FILE__, __LINE__),
      "no_cache(1) returned ",
      Data::Dumper->Dump([\@no_cache], [qw(*no_cache)]),
      Data::Dumper->Dump([\%headers_out], [qw(*headers_out)]));

      $r->log_error(sprintf("%s (%s %s): ",
      (caller(0))[3], __FILE__, __LINE__),
      "redirecting to '$lucky_one'");
      $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__

      #######################################################################



      dpchrist@p42800e:~$ wget -s -nv http://192.168.254.3/random/picture
      18:01:48 URL:http://192.168.254.3/random/picture [32752/32752] -> "pictu
      re.4" [1]



      dpchrist@p42800e:~$ head -n 12 picture.4
      HTTP/1.1 200 OK
      Date: Wed, 06 Jul 2005 01:01:10 GMT
      Server: Apache/1.3.33 (Debian GNU/Linux) mod_perl/1.29
      Last-Modified: Wed, 04 Jun 2003 06:53:10 GMT
      ETag: "1064a-7ff0-3edd9756;42c4b3fd"
      Accept-Ranges: bytes
      Content-Length: 32752
      Keep-Alive: timeout=15, max=100
      Connection: Keep-Alive
      Content-Type: image/jpeg
      Expires: Wed, 06 Jul 2005 01:01:10 GMT



      root@p166v:~# tail -n 8 /var/log/apache-perl/error.log | grep no_cache
      [Tue Jul 5 18:01:10 2005] [error] Apache::RandomPicture::handler (/home
      /dpchrist/eagle-book/lib/perl/Apache/RandomPicture.pm 117): calling no_c
      ache(1)...%headers_out = ();
      [Tue Jul 5 18:01:10 2005] [error] Apache::RandomPicture::handler (/home
      /dpchrist/eagle-book/lib/perl/Apache/RandomPicture.pm 123): no_cache(1)
      returned @no_cache = (0);%headers_out = ('Cache-control' => 'no-cache','
      Pragma' => 'no-cache');
    • 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 2 of 15 , Jul 6, 2005
      • 0 Attachment
        > 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 3 of 15 , Jul 6, 2005
        • 0 Attachment
          > 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 4 of 15 , Jul 6, 2005
          • 0 Attachment
            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 5 of 15 , Jul 16, 2005
            • 0 Attachment
              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.