rexyai / RestRserve

R web API framework for building high-performance microservices and app backends
https://restrserve.org
271 stars 31 forks source link

add ETag Middleware #182

Closed DavZim closed 2 years ago

DavZim commented 2 years ago

This PR adds Middleware for ETags to RestRServe.

ETags are supported for static files but also for other returned objects. Note, that this PR adds digest as a dependency, which can be included as a Suggests if needed.

Headers currently implemented are:

Similar implementations can be found for Go fiber or fastapi.

A simple example of usage is found in the code example:

# 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)
last_modified = file.info(file_path)[["mtime"]]
file_hash = digest::digest(file = file_path, algo = "crc32")
file_hash
#> [1] "4425b673"

#############################################################################
# setup the Application with the ETag Middleware
app = Application$new(middleware = list(ETagMiddleware$new()))
app$add_static(path = "/", static_dir)

# 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/0.4.1001
#>     Last-Modified: 2022-03-23T14:48:10Z
#>     ETag: 4425b673

# 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/0.4.1001

# 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/0.4.1001
#>     Last-Modified: 2022-03-23T14:48:10Z
#>     ETag: 4425b673

# alternatively, you can provide a timestamp in the If-Modified-Since header
# => 304 Not Modified (Can be cached)
modified_since = format(last_modified + 1, "%FT%TZ")
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/0.4.1001

# 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, "%FT%TZ")
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/0.4.1001
#>     Last-Modified: 2022-03-23T14:48:10Z
#>     ETag: 4425b673
DavZim commented 2 years ago

This also closes the side-discussion of #181 around ETags

dselivanov commented 2 years ago

@DavZim Thanks for PR. It will take some time to review. I will try this week. In the meantime I will submit v 1.0.0 to CRAN aa they asked to fix failing tests before 2022-03-30. We can include this PR to the next 1.1.0 version.

DavZim commented 2 years ago

@dselivanov I know you are busy and I highly appreciate your free maintenance of open source projects. Do you have a rough time horizon for this PR? Also, is there anything I can take off your shoulders here to speed up the process??

dselivanov commented 2 years ago

@artemklevtsov any comments?

artemklevtsov commented 2 years ago

Looks good for me.

codecov[bot] commented 2 years ago

Codecov Report

Merging #182 (550d788) into dev (96513b9) will decrease coverage by 0.20%. The diff coverage is 90.62%.

:exclamation: Current head 550d788 differs from pull request most recent head 8db7150. Consider uploading reports for the commit 8db7150 to get more accurate results

@@            Coverage Diff             @@
##              dev     #182      +/-   ##
==========================================
- Coverage   95.07%   94.86%   -0.21%     
==========================================
  Files          27       28       +1     
  Lines        1300     1364      +64     
==========================================
+ Hits         1236     1294      +58     
- Misses         64       70       +6     
Impacted Files Coverage Δ
R/Application.R 96.71% <ø> (ø)
R/AuthBackend.R 100.00% <ø> (ø)
R/ETagMiddleware.R 89.83% <89.83%> (ø)
R/ContentHandlersFactory.R 97.05% <100.00%> (+1.47%) :arrow_up:
R/EncodeDecodeMiddleware.R 100.00% <100.00%> (ø)
src/format_cookies.cpp 96.15% <0.00%> (-3.85%) :arrow_down:
src/parse_multipart.cpp 99.00% <0.00%> (+0.01%) :arrow_up:
src/utils.cpp 55.55% <0.00%> (+1.01%) :arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 96513b9...8db7150. Read the comment docs.

dselivanov commented 2 years ago

@DavZim RestRserve 1.1.0 is on CRAN - https://cran.r-project.org/web/packages/RestRserve/