XQuery/Setting HTTP Headers

Motivation edit

You want to put information in your outgoing HTTP headers to control aspects such as web caching and ETAGS.

Sample Module edit

The following module was provided by Thomas White.

File tw_stream-binary-cached.xql

xquery version "1.0" encoding "UTF-8";

module namespace cached-binary = "http://www.thomas-white.net/xqm/stream-binary-cached.1.0" ;

declare default function namespace "http://www.w3.org/2005/xpath-functions";

import module namespace xdb="http://exist-db.org/xquery/xmldb";
import module namespace cache = "http://exist-db.org/xquery/cache";
import module namespace datetime = "http://exist-db.org/xquery/datetime";
import module namespace util = "http://exist-db.org/xquery/util";

declare option exist:serialize "method=xml media-type=text/xml"; 

declare function  cached-binary:eTag( $pathToBinaryResource as xs:string, $last-modified as xs:dateTime, $domain-tag as xs:string ) as xs:string{
    concat( $domain-tag, '-', 
            util:document-id( $pathToBinaryResource ) ,'-', 
            fn:translate( fn:substring($last-modified,1,19),':-T' , '')   )
};

declare function  cached-binary:eTag-from-uri( $pathToBinaryResource as xs:string, $domain-tag as xs:string ) as xs:string{
    cached-binary:eTag( $pathToBinaryResource,  
                        xdb:last-modified( util:collection-name( $pathToBinaryResource ), util:document-name( $pathToBinaryResource )), 
                       $domain-tag)
};

declare function  cached-binary:stream-binary-with-cache-headers( 
      $original-path as xs:string?,
      $pathToBinaryResource as xs:string, 
      $expiresAfter as xs:dayTimeDuration?, 
      $must-revalidate as xs:boolean, 
      $doNotCache as xs:string,
      $domain as xs:string?
) {   
    
    if( fn:string-length($pathToBinaryResource) = 0 or not( util:binary-doc-available( $pathToBinaryResource )) ) then (
          response:set-status-code( 404 ),
          concat( $original-path, ' ( ', $pathToBinaryResource, ' ) not found!')   (: ($original-path, $pathToBinaryResource)[1] :)          
    ) else ( 
             
             let $coll := util:collection-name( $pathToBinaryResource )
             let $file := util:document-name(   $pathToBinaryResource )
             let $last-modified :=  xdb:last-modified( $coll, $file) 
             let $ETag := cached-binary:eTag( $pathToBinaryResource, $last-modified, $domain ) 
             let $if-modified-since := request:get-header('If-Modified-Since')
             let $expire-after  := if( empty($expiresAfter) ) then  xs:dayTimeDuration( "P365D" ) else $expiresAfter (: 365 Day expiry period :) 
             
             let $content-type:= (
                util:declare-option('exist:serialize', concat("media-type=", xdb:get-mime-type( xs:anyURI( $pathToBinaryResource) )) ),
                response:set-header( "Pragma", 'o' )
             )             
            
             return if( not($doNotCache = 'true') and (
                        ( request:get-header('If-None-Match') = $ETag ) or (:  ETag :)
                        (fn:string-length($if-modified-since) > 0 and datetime:parse-dateTime( $if-modified-since, 'EEE, d MMM yyyy HH:mm:ss Z' ) <= $last-modified ) 
                     )) then (                              
                        response:set-status-code( 304 ),
                        response:set-header( "Cache-Control", concat('public, max-age=', $expire-after div xs:dayTimeDuration('PT1S')  )) (:   24h=86,400  , must-revalidate :)
                    ) else (                      
                      let $maxAge        := $expire-after  div xs:dayTimeDuration('PT1S')
                      let $headers := (                          
                           response:set-header( "ETag", $ETag ),
                           response:set-header( "Last-Modified",  datetime:format-dateTime( $last-modified, 'EEE, d MMM yyyy HH:mm:ss Z' )),
                           response:set-header( "Expires",        datetime:format-dateTime( dateTime(current-date(), util:system-time()) + $expire-after, 'EEE, d MMM yyyy HH:mm:ss Z' )), 
                           
                           if( $doNotCache = 'true' ) then (
                              response:set-header( "Cache-Control",  'no-cache, no-store, max-age=0, must-revalidate' ),
                              response:set-header( "X-Content-Type-Options", 'nosniff' )
                           )else 
                              response:set-header( "Cache-Control", concat( 'public, max-age=', $maxAge,  if( $must-revalidate ) then ', must-revalidate' else '' ))
                       )
                       return response:stream-binary(
                                util:binary-doc( xs:anyURI($pathToBinaryResource ) ), 
                                xdb:get-mime-type( xs:anyURI( $pathToBinaryResource) ),
                                xs:anyURI( ($original-path, $pathToBinaryResource)[1] ) 
                             )    
                    ) 

     )                                       
};

(:

HTTP/1.1 200 OK
Date: Fri, 30 Oct 1998 13:19:41 GMT
Server: Apache/1.3.3 (Unix)
Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT
ETag: "3e86-410-3596fbbc"
Content-Length: 1040
Content-Type: text/html

Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT
ETag: "3e86-410-3596fbbc"

Cache-Control: public, max-age=1728000
Expires: Thu, 06 Aug 2009 10:04:13 GMT
Date: Fri, 17 Jul 2009 10:04:13 GMT
Content-Type: text/javascript; charset=UTF-8
ETag: "ih2h6n8r44hc"
Last-Modified: Fri, 05 Sep 2003 02:11:15 GMT
X-Content-Type-Options: nosniff
:)
xquery version "1.0"  encoding "UTF-8";

declare default function namespace "http://www.w3.org/2005/xpath-functions";

import module namespace request  = "http://exist-db.org/xquery/request";
import module namespace cached-binary = "http://www.thomas-white.net/xqm/stream-binary-cached.1.0"   at "tw_stream-binary-cached.xql"; 
 
cached-binary:stream-binary-with-cache-headers( 
        request:get-parameter("url", ()), 
        request:get-parameter("uri", 'no-uri'), 
        xs:dayTimeDuration(request:get-parameter("expire", 'P30D')), 
        xs:boolean(request:get-parameter("must-revalidate", 'false') = 'true'), 
        request:get-parameter("doNotCache", ''), 
        request:get-parameter("domain", '')
       )