Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ETag Middleware #182

Merged
merged 9 commits into from
Apr 13, 2022
Merged

add ETag Middleware #182

merged 9 commits into from
Apr 13, 2022

Conversation

DavZim
Copy link
Contributor

@DavZim DavZim commented Mar 23, 2022

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:

  • response headers: ETag and Last-Modified
  • request headers: If-None-Match and If-Modified-Since (Not (yet) implemented are If-Match and IF-Unmodified-Since.

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
Copy link
Contributor Author

DavZim commented Mar 23, 2022

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

@DavZim DavZim changed the base branch from master to dev March 24, 2022 09:12
@dselivanov
Copy link
Collaborator

@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
Copy link
Contributor Author

DavZim commented Apr 7, 2022

@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
Copy link
Collaborator

@artemklevtsov any comments?

@artemklevtsov
Copy link
Collaborator

Looks good for me.

@codecov
Copy link

codecov bot commented Apr 13, 2022

Codecov Report

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

❗ 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%) ⬆️
R/EncodeDecodeMiddleware.R 100.00% <100.00%> (ø)
src/format_cookies.cpp 96.15% <0.00%> (-3.85%) ⬇️
src/parse_multipart.cpp 99.00% <0.00%> (+0.01%) ⬆️
src/utils.cpp 55.55% <0.00%> (+1.01%) ⬆️

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 dselivanov merged commit b5fc60d into rexyai:dev Apr 13, 2022
@DavZim DavZim deleted the etag-dev branch April 13, 2022 09:18
@dselivanov
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants