Adds ETag to an Application.

ETags are header information that enable the caching of content. If enabled, RestRserve will return an ETag (eg a hash of a file) alongside the last time it was modified. When a request is sent, additional headers such as If-None-Match, If-Match, If-Modified-Since, and If-Unmodified-Since, can be passed to the server as well.

If the conditions are met (different hash in case of a If-None-Match header or a later file modification in case of a given If-Modified-Since header), the server does not send the requested file but returns a 304 status code, indicating, that the data on the requesting device is up-to-date.

Note that if both headers are provided, the If-None-Match header takes precedence.

Furthermore, the middleware also supports the headers If-Match, which returns the object if the hash matches (it also supports "*" to always return the file), as well as If-Unmodified-Since, which returns the object if it has not been modified since a certain time. If the conditions are not met, a 412 status code is returned (Precondition Failed). See examples below.

References

MDN

Super class

RestRserve::Middleware -> EtagMiddleware

Public fields

hash_function

Function that takes an object or file and computes the hash of it

last_modified_function

Function that takes an object or file and computes the last time it was modified

Methods

Inherited methods


    Method new()

    Creates ETag middleware object

    Usage

    ETagMiddleware$new(
      routes = "/",
      match = "partial",
      id = "ETagMiddleware",
      hash_function = function(body) {     if ("file" %in% names(body)) {        
        digest::digest(file = body[["file"]], algo = "crc32")     }     else {        
        digest::digest(body, algo = "crc32")     } },
      last_modified_function = function(body) {     if ("file" %in% names(body)) {       
         as.POSIXlt(file.info(body[["file"]])[["mtime"]], tz = "GMT")     }     else {       
         as.POSIXlt(Sys.time(), tz = "GMT")     } }
    )

    Arguments

    routes

    Routes paths to protect.

    match

    How routes will be matched: exact or partial (as prefix).

    id

    Middleware id.

    hash_function

    a function that generates the ETag hash. The function takes the body of the response and returns a single character. Default is crc32 using digest::digest.

    last_modified_function

    a function that takes the body of the response and returns the last time this was changed. The default is to take the mtime (last time the file was modified) if its a file, if the body does not contain a file, the current time is returned ( resulting in no caching)


    Method clone()

    The objects of this class are cloneable with this method.

    Usage

    ETagMiddleware$clone(deep = FALSE)

    Arguments

    deep

    Whether to make a deep clone.

    Examples

    #############################################################################
    # setup a static directory with ETag caching
    
    static_dir = file.path(tempdir(), "static")
    if (!dir.exists(static_dir)) dir.create(static_dir)
    
    file_path = file.path(static_dir, "example.txt")
    writeLines("Hello World", file_path)
    
    # get the time the file was last modified in UTC time
    last_modified = as.POSIXlt(file.info(file_path)[["mtime"]], tz = "UTC")
    file_hash = digest::digest(file = file_path, algo = "crc32")
    
    time_fmt = "%a, %d %b %Y %H:%M:%S GMT"
    
    
    
    #############################################################################
    # setup the Application with the ETag Middleware
    app = Application$new()
    app$append_middleware(ETagMiddleware$new())
    app$add_static(path = "/", static_dir)
    
    
    
    #############################################################################
    # Example Requests
    
    # Request the file returns the file with ETag headers
    req = Request$new(path = "/example.txt")
    # note that it also returns the Last-Modified and ETag headers
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 200 OK
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    #>     Last-Modified: Thu, 09 Jun 2022 10:23:52 GMT
    #>     ETag: b095e5e3
    
    
    # provide matching hash of the file in the If-None-Match header to check Etag
    # => 304 Not Modified (Can be cached)
    req = Request$new(path = "/example.txt",
                      headers = list("If-None-Match" = file_hash))
    # note status_code 304 Not Modified
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 304 Not Modified
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    
    
    # provide a wrong hash, returns the file normally
    req = Request$new(path = "/example.txt",
                      headers = list("If-None-Match" = "WRONG HASH"))
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 200 OK
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    #>     Last-Modified: Thu, 09 Jun 2022 10:23:52 GMT
    #>     ETag: b095e5e3
    
    
    # alternatively, you can provide a timestamp in the If-Modified-Since header
    # => 304 Not Modified (Can be cached)
    modified_since = format(last_modified + 1, time_fmt)
    req = Request$new(path = "/example.txt",
                      headers = list("If-Modified-Since" = modified_since))
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 304 Not Modified
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    
    
    # provide both headers: If-None-Match takes precedence
    # in this case:
    #  - if none match => modified (No cache)
    #  - if modified since => NOT MODIFIED (cached)
    # => Overall: modified = no cache
    modified_since = format(last_modified + 1, time_fmt)
    req = Request$new(path = "/example.txt",
                      headers = list("If-None-Match" = "CLEARLY WRONG",
                                     "If-Modified-Since" = modified_since))
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 200 OK
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    #>     Last-Modified: Thu, 09 Jun 2022 10:23:52 GMT
    #>     ETag: b095e5e3
    
    
    # provide matching hash of the file in the If-Match header to check Etag
    # => 412 Precondition Failed
    req = Request$new(path = "/example.txt",
                      headers = list("If-Match" = "OTHER HASH"))
    # note status_code 412 Precondition Failed
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 412 Precondition Failed
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    
    
    # Use If-Unmodified-Since
    unmodified_since = format(last_modified - 1, time_fmt)
    req = Request$new(path = "/example.txt",
                      headers = list("If-Unmodified-Since" = unmodified_since)
    )
    # note status_code 412 Precondition Failed
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 412 Precondition Failed
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    
    
    
    #############################################################################
    
    # use an alternative hash function (use name of the file)
    hash_on_filename = function(x) x
    # also use an alternate last_modified time function
    always_1900 = function(x) as.POSIXlt("1900-01-01 12:34:56", tz = "GMT")
    
    
    # setup the app again
    app = Application$new(middleware = list(
      ETagMiddleware$new(hash_function = hash_on_filename,
                         last_modified_function = always_1900)
    ))
    app$add_static(path = "/", file_path = static_dir)
    
    
    # test the requests
    req = Request$new(path = "/example.txt")
    (res = app$process_request(req))
    #> <RestRserve Response>
    #>   status code: 200 OK
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10
    #>     Last-Modified: Mon, 01 Jan 1900 12:34:56 GMT
    #>     ETag: /private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/RtmpQBx991/static/example.txt
    
    filename = res$body[["file"]]
    req = Request$new(path = "/example.txt",
                      headers = list("If-None-Match" = filename))
    app$process_request(req)
    #> <RestRserve Response>
    #>   status code: 304 Not Modified
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.0; Rserve/1.8.10