+sub cache_finish()
+{
+ # Do not: return if !$W->{"uri_args_frozen"};
+ # as we may have just gave 304 and 'exit;' without starting the caching.
+ return if !$W->{"cache_active"};
+
+ # Fill-in/check: %uri_args_frozen_to_headers_in_keys
+ my $headers_in_keys_stored_arrayref_ref=\$uri_args_frozen_to_headers_in_keys{$W->{"uri_args_frozen"}};
+ my @headers_in_keys=tied(%{$W->{"headers_in_RecordKeys"}})->accessed();
+ if (!$$headers_in_keys_stored_arrayref_ref
+ || !Data::Compare::Compare(\@headers_in_keys,$$headers_in_keys_stored_arrayref_ref)) {
+ cluck "Non-matching generated 'headers_in_keys' per 'uri_args_frozen' key:\n"
+ .Dumper(\@headers_in_keys,$$headers_in_keys_stored_arrayref_ref)
+ if $$headers_in_keys_stored_arrayref_ref;
+ # Build or possibly prevent such further warn dupes:
+ $$headers_in_keys_stored_arrayref_ref=\@headers_in_keys;
+ # Build or regenerate as obsoleted now:
+ $W->{"uri_args_headers_in_frozen"}=uri_args_headers_in_frozen_get(\@headers_in_keys);
+ }
+
+ # Prepare 'headers_out' for the future reusal:
+ my %headers_out;
+ # Do not: $W->{"digest-md5"}->b64digest();
+ # as it will not provide the trailing filling '='s.
+ # RFC 1864 is not clear if they should be there but its sample provides them.
+ # Do not try to provide canonical "\r\n" form of newlines as is said by RFC 1864.
+ # RFC 2068 (HTTP/1.1) section 14.16 says the newlines should NOT be converted for HTTP.
+ # ',""' to avoid breaking the headers by its default "\n".
+ $headers_out{"Content-MD5"}=MIME::Base64::encode_base64($W->{"digest-md5"}->digest(),"");
+ # In fact we could also use MD5 for ETag as if we know ETag we also know MD5.
+ # But this way we do not need to calculate MD5 and we still can provide such ETag. So.
+ # $W->{"r"}->set_etag() ?
+ $headers_out{"ETag"}='"'.Digest::MD5::md5_base64($W->{"uri_args_headers_in_frozen"}).'"';
+ # $W->{"r"}->set_content_length() ?
+ $headers_out{"Content-Length"}=$W->{"r"}->bytes_sent();
+ my %Vary=map(($_=>1),(@headers_in_keys));
+ for (keys(%Vary)) {
+ next if !/^_/;
+ $Vary{"*"}=1;
+ delete $Vary{$_};
+ }
+ %Vary=("*"=>1) if $Vary{"*"};
+ $headers_out{"Vary"}=join(", ",sort keys(%Vary)) if keys(%Vary);
+ # $W->{"r"}->set_last_modified() ?
+ $headers_out{"Last-Modified"}=cache_finish_last_modified();
+
+ # Fill-in/check: %uri_args_headers_in_frozen_to_headers_out
+ my $headers_out_stored_hashref_ref=\$uri_args_headers_in_frozen_to_headers_out{$W->{"uri_args_headers_in_frozen"}};
+ if (!$$headers_out_stored_hashref_ref
+ || !Data::Compare::Compare(\%headers_out,$$headers_out_stored_hashref_ref)) {
+ cluck "Non-matching generated 'headers_out' per 'uri_args_headers_in_frozen' key:\n"
+ .Dumper(\%headers_out,$$headers_out_stored_hashref_ref)
+ if $$headers_out_stored_hashref_ref;
+ # Build or possibly prevent such further warn dupes:
+ $$headers_out_stored_hashref_ref=\%headers_out;
+ }
+
+###print STDERR Dumper(\%uri_args_frozen_to_headers_in_keys,\%uri_args_headers_in_frozen_to_headers_out);
+}
+
+sub heading()
+{
+my($class)=@_;
+
+ if (!$W->{"header_only"}) {
+ header("Content-Style-Type"=>"text/css");
+ header("Content-Script-Type"=>"text/javascript");
+ # $W->{"r"}->content_languages() ?
+ do { header("Content-Language"=>$_) if $_; } for $W->{"language"};
+ }
+ # TODO: Support also: private
+ header("Cache-Control"=>"public"); # HTTP/1.1
+
+ # Use $W->{"charset"}=0 to disable charset.
+ $W->{"charset"}="us-ascii"
+ if !defined $W->{"charset"} && (!defined($W->{"content_type"}) || $W->{"content_type"});
+
+ # Workaround bug
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=120556
+ # of at least
+ # Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8b) Gecko/20050217
+ # http://validator.w3.org/ does not send ANY "Accept" headers!
+ $W->{"content_type"}="application/xhtml+xml" if !defined $W->{"content_type"}
+ && !$W->{"headers_in"}{"Accept"}
+ && ($W->{"headers_in"}{"User-Agent"}||"")=~m{^W3C_Validator/}i;
+ defined($W->{"content_type"}) or $W->{"content_type"}=$class->Negotiate_choose([
+ # Put the fallback variant as the first one.
+ # Rate both variants the same to prefer "text/html" for undecided clients.
+ # At least
+ # Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8b) Gecko/20050217
+ # prefers "application/xhtml+xml" over "text/html" itself:
+ # text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
+ negotiate_variant(
+ "id"=>"text/html",
+ "content-type"=>"text/html",
+ "qs"=>0.6,
+ (!$W->{"charset"} ? () : "charset"=>$W->{"charset"}),
+ "lang"=>$W->{"language"},
+ ),
+ negotiate_variant(
+ "id"=>"application/xhtml+xml",
+ "content-type"=>"application/xhtml+xml",
+ "qs"=>0.6,
+ (!$W->{"charset"} ? () : "charset"=>$W->{"charset"}),
+ "lang"=>$W->{"language"},
+ ),
+ # application/xml ?
+ # text/xml ?
+ ]);
+ # mod_perl doc: If you set this header via the headers_out table directly, it
+ # will be ignored by Apache. So do not do that.
+ my $type;
+ if ($W->{"content_type"}) {
+ $type=MIME::Types->new()->type($W->{"content_type"});
+ cluck "MIME::Types type '".$W->{"content_type"}."' not known" if !$type;