From 0da2e5b905a5c002cd69c5648c62564b37f72d92 Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 2 May 2022 16:36:55 +0200 Subject: [PATCH 01/19] Add further ETag headers (#188) * fix IMS header for wrongfully cut date * allow multiple INM values * add If-Match Header * add If-Unmodified-Since Header * add to documentation Co-authored-by: David Zimmermann-Kollenda --- R/ETagMiddleware.R | 108 +++++++++++++++++++++++++++++++++----- inst/tinytest/test-etag.R | 88 +++++++++++++++++++++++++++++++ man/ETagMiddleware.Rd | 68 +++++++++++++++++++++--- 3 files changed, 243 insertions(+), 21 deletions(-) diff --git a/R/ETagMiddleware.R b/R/ETagMiddleware.R index b538721..0fe1b6a 100644 --- a/R/ETagMiddleware.R +++ b/R/ETagMiddleware.R @@ -2,14 +2,17 @@ #' #' #' @description -#' Adds ETag to [Application]. TBD. \cr +#' Adds ETag to an [Application]. \cr #' #' 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`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) -#' or [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since) +#' [`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match), +#' [`If-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match), +#' [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since), +#' and +#' [`If-Unmodified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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 @@ -21,6 +24,15 @@ #' 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412) status +#' code is returned (Precondition Failed). +#' See examples below. +#' #' See examples below for further clarifications. #' #' @export @@ -32,24 +44,40 @@ #' [MDN](https://developer.mozilla.org/en/docs/Web/HTTP/Headers/ETag) #' #' @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) -#' last_modified = file.info(file_path)[["mtime"]] +#' +#' # 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(middleware = list(ETagMiddleware$new())) +#' 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) #' +#' #' # 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", @@ -57,29 +85,51 @@ #' # note status_code 304 Not Modified #' app$process_request(req) #' +#' #' # 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) #' +#' #' # 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") +#' 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) #' +#' #' # 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") +#' 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) #' +#' +#' # 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) +#' +#' +#' # 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) +#' +#' +#' #' ############################################################################# #' #' # use an alternative hash function (use name of the file) @@ -87,12 +137,16 @@ #' # 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)) #' @@ -195,12 +249,19 @@ ETagMiddleware = R6::R6Class( inm = request$get_header("if-none-match", NULL) actual_hash = self$hash_function(response$body) - if (!is.null(inm)) { - if (inm == actual_hash) { - response$set_body(NULL) - response$set_status_code(304) - return() - } + if (!is.null(inm) && actual_hash %in% inm) { + response$set_body(NULL) + response$set_status_code(304) + return() + } + + + # Check If-Match Header + im = request$get_header("if-match", NULL) + if (!is.null(im) && !actual_hash %in% im && !"*" %in% im) { + response$set_body(NULL) + response$set_status_code(412) + return() } @@ -211,7 +272,7 @@ ETagMiddleware = R6::R6Class( # INM takes precedence over IMS, # that is if IMS is only checked if INM is NOT GIVEN! if (!is.null(ims) && is.null(inm)) { - ims_date = as.POSIXlt(ims, tryFormats = c( + ims_date = as.POSIXlt(paste(ims, sep = ", "), tryFormats = c( time_fmt, "%FT%TZ", "%Y-%m-%d %H:%M:%OS", "%Y/%m/%d %H:%M:%OS", "%Y-%m-%d %H:%M", "%Y/%m/%d %H:%M", "%Y-%m-%d", "%Y/%m/%d" ), tz = "GMT") @@ -225,6 +286,25 @@ ETagMiddleware = R6::R6Class( } + # Check If-Unmodified-Since Header + ius = request$get_header("if-unmodified-since", NULL) + + if (!is.null(ius) && is.null(inm)) { + ius_date = as.POSIXlt(paste(ius, sep = ", "), tryFormats = c( + time_fmt, "%FT%TZ", "%Y-%m-%d %H:%M:%OS", "%Y/%m/%d %H:%M:%OS", + "%Y-%m-%d %H:%M", "%Y/%m/%d %H:%M", "%Y-%m-%d", "%Y/%m/%d" + ), tz = "GMT") + + # if ius_date is after modified date, it triggers + if (last_modified > ius_date) { + response$set_body(NULL) + response$set_status_code(412) + return() + } + } + + + # No Caching... Add Last Modified and ETag header response$set_header("Last-Modified", format(last_modified, time_fmt)) response$set_header("ETag", actual_hash) diff --git a/inst/tinytest/test-etag.R b/inst/tinytest/test-etag.R index a285b41..5eae410 100644 --- a/inst/tinytest/test-etag.R +++ b/inst/tinytest/test-etag.R @@ -139,6 +139,17 @@ expect_cached(rs) +# Multiple If-None-Match +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-None-Match" = c("SOME HASH", actual_hash, "OTHER HASH")) +) +rs = app$process_request(req) +expect_cached(rs) + + + # Only If None Match but WRONG resulting in the file to be returned req = Request$new( path = "/static/example.txt", @@ -150,6 +161,83 @@ expect_no_cached_file(rs, file_path, last_modified) +# Check If-Match Header +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-Match" = actual_hash) +) +rs = app$process_request(req) +expect_no_cached_file(rs, file_path, last_modified) + + + +# Check If-Match Header with other hash +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-Match" = "OTHER HASH") +) +rs = app$process_request(req) +expect_equal(rs$status_code, 412) + + + +# Check If-Match Header with multiple values +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-Match" = c("SOME HASH", actual_hash, "OTHER HASH")) +) +rs = app$process_request(req) +expect_no_cached_file(rs, file_path, last_modified) + + + +# Check If-Match Header matching any +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-Match" = "*") +) +rs = app$process_request(req) +expect_no_cached_file(rs, file_path, last_modified) + + + +# Check If-Match Header wrong hash +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-Match" = "WRONG HASH") +) +rs = app$process_request(req) +expect_equal(rs$status_code, 412) + + + +# Check If-Unmodified-Since Header +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-Unmodified-Since" = format(last_modified - 1, time_fmt)) +) +rs = app$process_request(req) +expect_equal(rs$status_code, 412) + + + +# Check If-Unmodified-Since Header other case +req = Request$new( + path = "/static/example.txt", + method = "GET", + headers = list("If-Unmodified-Since" = format(last_modified + 1, time_fmt)) +) +rs = app$process_request(req) +expect_no_cached_file(rs, file_path, last_modified) + + + # if-none-match takes precedence over If-modified-since # In this test, the if-none-match header is clearly wrong # but if-modified-since overrides the if-none-match header resulting in a 340 diff --git a/man/ETagMiddleware.Rd b/man/ETagMiddleware.Rd index 384060e..9501f57 100644 --- a/man/ETagMiddleware.Rd +++ b/man/ETagMiddleware.Rd @@ -4,14 +4,17 @@ \alias{ETagMiddleware} \title{Creates ETag middleware object} \description{ -Adds ETag to \link{Application}. TBD. \cr +Adds ETag to an \link{Application}. \cr 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 -\href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match}{\code{If-None-Match}} -or \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since}{\code{If-Modified-Since}} +\href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match}{\code{If-None-Match}}, +\href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match}{\code{If-Match}}, +\href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since}{\code{If-Modified-Since}}, +and +\href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-UnModified-Since}{\code{If-Unmodified-Since}}, can be passed to the server as well. If the conditions are met (different hash in case of a \code{If-None-Match} header @@ -23,27 +26,52 @@ code, indicating, that the data on the requesting device is up-to-date. Note that if both headers are provided, the \code{If-None-Match} header takes precedence. +Furthermore, the middleware also supports the headers \code{If-Match}, which +returns the object if the hash matches (it also supports "*" to always return +the file), as well as \code{If-Unmodified-Since}, which returns the object if it +has not been modified since a certain time. +If the conditions are not met, a +\href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412}{412} status +code is returned (Precondition Failed). +See examples below. + See examples below for further clarifications. } \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) -last_modified = file.info(file_path)[["mtime"]] + +# 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(middleware = list(ETagMiddleware$new())) +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) + # 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", @@ -51,29 +79,51 @@ req = Request$new(path = "/example.txt", # note status_code 304 Not Modified app$process_request(req) + # 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) + # 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") +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) + # 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") +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) + +# 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) + + +# 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) + + + ############################################################################# # use an alternative hash function (use name of the file) @@ -81,12 +131,16 @@ 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)) From 3c801541cc5ca9dd0067b8bbf189423fb2398c87 Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 16 May 2022 16:33:51 +0200 Subject: [PATCH 02/19] ETag extensions and small bug fix (#191) * fix ETag encoding problem, cached are text/plain mime * fix documentation Co-authored-by: David Zimmermann-Kollenda --- R/ETagMiddleware.R | 6 ++++-- inst/tinytest/test-etag.R | 1 + man/ETagMiddleware.Rd | 2 -- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/R/ETagMiddleware.R b/R/ETagMiddleware.R index 0fe1b6a..020f787 100644 --- a/R/ETagMiddleware.R +++ b/R/ETagMiddleware.R @@ -33,8 +33,6 @@ #' code is returned (Precondition Failed). #' See examples below. #' -#' See examples below for further clarifications. -#' #' @export #' #' @seealso @@ -252,6 +250,7 @@ ETagMiddleware = R6::R6Class( if (!is.null(inm) && actual_hash %in% inm) { response$set_body(NULL) response$set_status_code(304) + response$set_content_type("text/plain") return() } @@ -261,6 +260,7 @@ ETagMiddleware = R6::R6Class( if (!is.null(im) && !actual_hash %in% im && !"*" %in% im) { response$set_body(NULL) response$set_status_code(412) + response$set_content_type("text/plain") return() } @@ -281,6 +281,7 @@ ETagMiddleware = R6::R6Class( if (last_modified <= ims_date) { response$set_body(NULL) response$set_status_code(304) + response$set_content_type("text/plain") return() } } @@ -299,6 +300,7 @@ ETagMiddleware = R6::R6Class( if (last_modified > ius_date) { response$set_body(NULL) response$set_status_code(412) + response$set_content_type("text/plain") return() } } diff --git a/inst/tinytest/test-etag.R b/inst/tinytest/test-etag.R index 5eae410..f8c45e8 100644 --- a/inst/tinytest/test-etag.R +++ b/inst/tinytest/test-etag.R @@ -10,6 +10,7 @@ source("setup.R") expect_cached = function(rs) { expect_equal(rs$status_code, 304) expect_true(!any(c("ETag", "Last-Modified") %in% names(rs$headers))) + expect_equal(rs$content_type, "text/plain") expect_equal(rs$body, NULL) } diff --git a/man/ETagMiddleware.Rd b/man/ETagMiddleware.Rd index 9501f57..74685df 100644 --- a/man/ETagMiddleware.Rd +++ b/man/ETagMiddleware.Rd @@ -34,8 +34,6 @@ If the conditions are not met, a \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412}{412} status code is returned (Precondition Failed). See examples below. - -See examples below for further clarifications. } \examples{ ############################################################################# From 240b64c6913d0ef62a554d57f6a020473430cbf6 Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Wed, 8 Jun 2022 07:47:23 +0400 Subject: [PATCH 03/19] update examples considering png/jpeg availability --- inst/examples/content-handlers/app.R | 17 +++++++++++------ inst/examples/plot-base/app.R | 25 ++++++++++++++++--------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/inst/examples/content-handlers/app.R b/inst/examples/content-handlers/app.R index f7d3e92..5e00dce 100644 --- a/inst/examples/content-handlers/app.R +++ b/inst/examples/content-handlers/app.R @@ -14,13 +14,18 @@ data = data.frame( static_dir = file.path(tempdir(check = TRUE), "static") if (!dir.exists(static_dir)) dir.create(static_dir) +cpb = capabilities() a = capture.output({ - png(file.path(static_dir, "testplot.png")) - plot(data$x, data$y, type = "l") - dev.off() - jpeg(file.path(static_dir, "testplot.jpg")) - plot(data$x, data$y, type = "l") - dev.off() + if (isTRUE(cpb[['png']])) { + png(file.path(static_dir, "testplot.png")) + plot(data$x, data$y, type = "l") + dev.off() + } + if (isTRUE(cpb[['jpeg']])) { + jpeg(file.path(static_dir, "testplot.jpg")) + plot(data$x, data$y, type = "l") + dev.off() + } pdf(file.path(static_dir, "testplot.pdf")) plot(data$x, data$y, type = "l") dev.off() diff --git a/inst/examples/plot-base/app.R b/inst/examples/plot-base/app.R index 0e20489..0aa4081 100644 --- a/inst/examples/plot-base/app.R +++ b/inst/examples/plot-base/app.R @@ -6,18 +6,25 @@ library(RestRserve) ## ---- create handler for the HTTP requests ---- +cpb = capabilities() plot_handler = function(request, response) { # make plot and save it in temp file - tmp = tempfile(fileext = ".png") - png(tmp, bg = "transparent") - plot(1:10) - rect(1, 5, 3, 7, col = "white") - dev.off() - # on.exit(unlink(tmp)) - # response$body = readBin(tmp, raw(), file.size(tmp)) - response$body = c("tmpfile" = tmp) - response$encode = identity + if (isTRUE(cpb[['png']])) { + tmp = tempfile(fileext = ".png") + png(tmp, bg = "transparent") + plot(1:10) + rect(1, 5, 3, 7, col = "white") + dev.off() + # on.exit(unlink(tmp)) + # response$body = readBin(tmp, raw(), file.size(tmp)) + response$body = c("tmpfile" = tmp) + response$encode = identity + } else { + raise(HTTPError$internal_server_error( + body = list(error = "png device is not available") + )) + } } From 3c90989796b243b5dc497c7f2a9b40128b240786 Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Wed, 8 Jun 2022 07:47:41 +0400 Subject: [PATCH 04/19] update docker files --- docker/Dockerfile | 4 ++-- docker/Dockerfile-r-minimal | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 63dd4cf..2921195 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ -FROM rocker/r-ver:3.6.2 +FROM r-base:4.2.0 ENV R_FORGE_PKGS Rserve -ENV R_CRAN_PKGS Rcpp R6 uuid checkmate mime jsonlite +ENV R_CRAN_PKGS Rcpp R6 uuid checkmate mime jsonlite digest RUN apt-get update && \ apt-get install -y --no-install-recommends libssl-dev libjemalloc-dev wget && \ diff --git a/docker/Dockerfile-r-minimal b/docker/Dockerfile-r-minimal index 848eb33..372f08b 100644 --- a/docker/Dockerfile-r-minimal +++ b/docker/Dockerfile-r-minimal @@ -1,9 +1,11 @@ -FROM rexyai/r-minimal +FROM docker.io/rhub/r-minimal:4.2.0 -ENV R_CRAN_PKGS Rcpp R6 uuid checkmate mime jsonlite +ENV R_CRAN_PKGS Rcpp R6 uuid checkmate mime jsonlite digest COPY . /tmp/RestRserve +WORKDIR / + RUN apk update && \ apk add --no-cache --virtual .build-deps gcc g++ musl-dev openssl-dev && \ installr -d $R_CRAN_PKGS && \ @@ -14,8 +16,6 @@ RUN apk update && \ rm /RestRserve*.tar.gz && \ apk del .build-deps -WORKDIR / - EXPOSE 8080 CMD ["Rscript", "-e", "source(system.file('examples', 'hello', 'app.R', package = 'RestRserve')); backend$start(app, http_port = 8080)"] From 497a2b5907e31c22baa0e1c8cb6f686b95f6a965 Mon Sep 17 00:00:00 2001 From: DavZim Date: Wed, 8 Jun 2022 07:36:16 +0200 Subject: [PATCH 05/19] add conditional header split (#189) * add conditional header split * add test to cpp_parse_headers * add optional headers to split provided by user * move split headers to R options * explicitly convert R strings to std::string * update docs and NEWS Co-authored-by: David Zimmermann-Kollenda Co-authored-by: Dmitriy Selivanov --- NEWS.md | 4 ++++ R/Application.R | 12 ++++++++++ R/BackendRserve.R | 5 +++- R/RcppExports.R | 4 ++-- R/zzz.R | 35 +++++++++++++++++++++++++++- inst/tinytest/test-parse-headers.R | 37 ++++++++++++++++++++++++++---- man/Application.Rd | 16 +++++++++++++ src/RcppExports.cpp | 9 ++++---- src/parse_headers.cpp | 18 +++++++++++++-- 9 files changed, 126 insertions(+), 14 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9d89b8f..78aeb96 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ ## Changelog +* 2022-06-08 - 1.2.0 + * Expose option to control which HTTP headers need to be split by comma during parsing. See `options("RestRserve.headers.split")`. See #187, #189. Thanks @DavZim. + * Improved ETag Middleware - see #188. Thanks @DavZim. + * 2022-04-20 - 1.1.1 * Skip tests on the live Rserve http server on CRAN which caused spurious test errors diff --git a/R/Application.R b/R/Application.R index ae17058..22dab39 100644 --- a/R/Application.R +++ b/R/Application.R @@ -4,7 +4,19 @@ #' Creates Application object. #' Application provides an interface for building high-performance #' REST API by registering R functions as handlers http requests. +#' @details +#' There are several advanced options to control how HTTP headers are +#' processed: +#' - `options("RestRserve.headers.server")` controls response `"Server"` header +#' - `options("RestRserve.headers.split")` controls which header values +#' split by comma during parsing. See +#' [https://en.wikipedia.org/wiki/List_of_HTTP_header_fields](), +#' [https://stackoverflow.com/a/29550711/3048453]() #' +#' There is also an option to switch-off runtime types validation in +#' the Request/Response handlers. This might provide some performance gains, +#' but ultimately leads to less robust applications. Use at your own risk! +#' See `options("RestRserve.runtime.asserts")` #' @export #' #' @seealso [HTTPError] [Middleware] diff --git a/R/BackendRserve.R b/R/BackendRserve.R index cd897bb..c736ba7 100644 --- a/R/BackendRserve.R +++ b/R/BackendRserve.R @@ -22,6 +22,8 @@ BackendRserve = R6::R6Class( initialize = function(..., jit_level = 0L, precompile = FALSE) { private$jit_level = checkmate::assert_int(jit_level, lower = 0L, upper = 3L) private$precompile = checkmate::assert_logical(precompile) + headers_to_split = getOption("RestRserve.headers.split", character()) + private$headers_to_split = checkmate::assert_character(headers_to_split) invisible(self) }, #' @description @@ -229,6 +231,7 @@ BackendRserve = R6::R6Class( jit_level = NULL, precompile = NULL, request = NULL, + headers_to_split = NULL, parse_form_urlencoded = function(body, request) { if (length(body) > 0L) { # Named character vector. Body parameters key-value pairs. @@ -281,7 +284,7 @@ BackendRserve = R6::R6Class( headers = rawToChar(headers) } if (is_string(headers)) { - headers = cpp_parse_headers(headers) + headers = cpp_parse_headers(headers, private$headers_to_split) } request$headers = headers return(request) diff --git a/R/RcppExports.R b/R/RcppExports.R index ad4211e..2752816 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -13,8 +13,8 @@ cpp_parse_cookies <- function(x) { .Call(`_RestRserve_cpp_parse_cookies`, x) } -cpp_parse_headers <- function(headers) { - .Call(`_RestRserve_cpp_parse_headers`, headers) +cpp_parse_headers <- function(headers, headers_to_split = NULL) { + .Call(`_RestRserve_cpp_parse_headers`, headers, headers_to_split) } cpp_parse_multipart_boundary <- function(content_type) { diff --git a/R/zzz.R b/R/zzz.R index c9a4895..9fcc135 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -32,6 +32,38 @@ library.dynam.unload("RestRserve", libpath) } # nocov end +# https://en.wikipedia.org/wiki/List_of_HTTP_header_fields +# https://stackoverflow.com/a/29550711/3048453 +RestRserve.headers.split = c( + "accept", + "accept-charset", + "access-control-request-headers", + "accept-encoding", + "accept-language", + "accept-patch", + "accept-ranges", + "allow", + "cache-control", + "connection", + "content-encoding", + "content-language", + "cookie", + "expect", + "forwarded", + "if-match", + "if-none-match", + "pragma", + "proxy-authenticate", + "te", + "trailer", + "transfer-encoding", + "upgrade", + "vary", + "via", + "warning", + "www-authenticate", + "x-forwarded-for" +) .onLoad = function(...) { # nocov start # make it TRUE because only this way comments inside functions can be printed during # non-interactive execution (Rscript for example). Whithout comments won't be possible to parse @@ -48,7 +80,8 @@ paste("RestRserve", packageVersion("RestRserve"), sep = "/"), paste("Rserve", packageVersion("Rserve"), sep = "/"), sep='; ' - ) + ), + "RestRserve.headers.split" = RestRserve.headers.split ) toset = !(names(restrserve_options) %in% names(default_options)) diff --git a/inst/tinytest/test-parse-headers.R b/inst/tinytest/test-parse-headers.R index 636b113..cec42e1 100644 --- a/inst/tinytest/test-parse-headers.R +++ b/inst/tinytest/test-parse-headers.R @@ -1,7 +1,10 @@ # Test parse headers +split_default = getOption("RestRserve.headers.split", NULL) # import functions -cpp_parse_headers = RestRserve:::cpp_parse_headers +cpp_parse_headers = function(x) { + RestRserve:::cpp_parse_headers(x, split_default) +} # Empty input expect_error(cpp_parse_headers(NA)) @@ -25,12 +28,15 @@ Connection: keep-alive\r\n Cookie: prov=f1e12498-1231-c8f0-8f53-97a8a6b17754; notice-ctt=4%3B1560153827826; mfnes=6e32CJ0CEAMaCwishdGGwpPLNxAFIJsCKAEyCDYyZGY0OTJh; acct=t=cmBi7gQEMWgxdi6kOiPwqAVNqmbEPdVj&s=E9Ly%2bCeEeAGmK9wDx2Zaseg6tiyi2hd8; sgt=id=3f4b96f5-b5ef-4ab1-96af-5ebce2950bcc\r\n Upgrade-Insecure-Requests: 1\r\n Cache-Control: max-age=0\r\n -Custom_name: custom value -TE: Trailers\r\n\r\n" +Custom_name: custom value\r\n +TE: Trailers\r\n +If-Modified-Since: Tue, 15 Mar 2022 10:27:07 GMT\r\n +If-Match: abc, def, ghi, jkl\r\n +If-None-Match: abc, def, ghi, jkl\r\n" parsed = cpp_parse_headers(h) expect_true(inherits(parsed, "list")) -expect_equal(length(parsed), 13L) +expect_equal(length(parsed), length(strsplit(h, "\r\n")[[1]])) expect_equal(parsed[["connection"]], "keep-alive") expect_equal(parsed[["accept"]], c("text/html", "application/xhtml+xml", "application/xml")) expect_equal(parsed[["user-agent"]], "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0") @@ -39,4 +45,27 @@ expect_equal(parsed[["accept-language"]], c("ru-RU", "ru")) expect_equal(length(parsed[["cookie"]]), 5L) expect_equal(parsed[["cookie"]][[1L]], "prov=f1e12498-1231-c8f0-8f53-97a8a6b17754") expect_equal(parsed[["custom_name"]], "custom value") +expect_equal(parsed[["if-modified-since"]], "Tue, 15 Mar 2022 10:27:07 GMT") +expect_equal(parsed[["if-match"]], c("abc", "def", "ghi", "jkl")) +expect_equal(parsed[["if-none-match"]], c("abc", "def", "ghi", "jkl")) + + +# automatically test that all headers which are splittable are split correctly +for (h in split_default) { + cont = "abc,def,ghi, jkl" + sep = if (h == "cookie") "; ?" else ", ?" + exp = strsplit(cont, sep)[[1]] + parsed = cpp_parse_headers(paste0(h, ": ", cont)) + expect_equal(parsed[[h]], exp) +} + + +# cpp_parse_headers accepts header argument for which headers to split +parsed = RestRserve:::cpp_parse_headers(paste0("test-header:", "some,more,values"), headers_to_split = "test-header") +expect_equal(parsed, list("test-header" = c("some", "more", "values"))) + +# headers_to_split overrides existing headers to split... eg accept wont be split anymore +parsed = RestRserve:::cpp_parse_headers(paste0("accept:", "some,more,values"), headers_to_split = "test-header") +expect_equal(parsed, list("accept" = "some,more,values")) + # nolint end diff --git a/man/Application.Rd b/man/Application.Rd index 4b0625a..3bed3bc 100644 --- a/man/Application.Rd +++ b/man/Application.Rd @@ -8,6 +8,22 @@ Creates Application object. Application provides an interface for building high-performance REST API by registering R functions as handlers http requests. } +\details{ +There are several advanced options to control how HTTP headers are +processed: +\itemize{ +\item \code{options("RestRserve.headers.server")} controls response \code{"Server"} header +\item \code{options("RestRserve.headers.split")} controls which header values +split by comma during parsing. See +\url{https://en.wikipedia.org/wiki/List_of_HTTP_header_fields}, +\url{https://stackoverflow.com/a/29550711/3048453} +} + +There is also an option to switch-off runtime types validation in +the Request/Response handlers. This might provide some performance gains, +but ultimately leads to less robust applications. Use at your own risk! +See \code{options("RestRserve.runtime.asserts")} +} \examples{ # init logger app_logger = Logger$new() diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index bc15ae1..7509c08 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -41,12 +41,13 @@ BEGIN_RCPP END_RCPP } // cpp_parse_headers -Rcpp::List cpp_parse_headers(const char* headers); -RcppExport SEXP _RestRserve_cpp_parse_headers(SEXP headersSEXP) { +Rcpp::List cpp_parse_headers(const char* headers, Rcpp::Nullable headers_to_split); +RcppExport SEXP _RestRserve_cpp_parse_headers(SEXP headersSEXP, SEXP headers_to_splitSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::traits::input_parameter< const char* >::type headers(headersSEXP); - rcpp_result_gen = Rcpp::wrap(cpp_parse_headers(headers)); + Rcpp::traits::input_parameter< Rcpp::Nullable >::type headers_to_split(headers_to_splitSEXP); + rcpp_result_gen = Rcpp::wrap(cpp_parse_headers(headers, headers_to_split)); return rcpp_result_gen; END_RCPP } @@ -110,7 +111,7 @@ static const R_CallMethodDef CallEntries[] = { {"_RestRserve_cpp_format_cookies", (DL_FUNC) &_RestRserve_cpp_format_cookies, 1}, {"_RestRserve_cpp_format_headers", (DL_FUNC) &_RestRserve_cpp_format_headers, 1}, {"_RestRserve_cpp_parse_cookies", (DL_FUNC) &_RestRserve_cpp_parse_cookies, 1}, - {"_RestRserve_cpp_parse_headers", (DL_FUNC) &_RestRserve_cpp_parse_headers, 1}, + {"_RestRserve_cpp_parse_headers", (DL_FUNC) &_RestRserve_cpp_parse_headers, 2}, {"_RestRserve_cpp_parse_multipart_boundary", (DL_FUNC) &_RestRserve_cpp_parse_multipart_boundary, 1}, {"_RestRserve_cpp_parse_multipart_body", (DL_FUNC) &_RestRserve_cpp_parse_multipart_body, 2}, {"_RestRserve_raw_slice", (DL_FUNC) &_RestRserve_raw_slice, 3}, diff --git a/src/parse_headers.cpp b/src/parse_headers.cpp index 0357c6a..d4133ac 100644 --- a/src/parse_headers.cpp +++ b/src/parse_headers.cpp @@ -13,7 +13,15 @@ bool validate_header_name(const std::string& x) { } // [[Rcpp::export(rng=false)]] -Rcpp::List cpp_parse_headers(const char* headers) { +Rcpp::List cpp_parse_headers(const char* headers, Rcpp::Nullable headers_to_split = R_NilValue) { + std::unordered_set h_to_split; + if (headers_to_split.isNotNull()) { + const Rcpp::CharacterVector hts = Rcpp::CharacterVector(headers_to_split); + for(Rcpp::CharacterVector::const_iterator ptr = hts.begin(); ptr != hts.end(); ptr++) { + h_to_split.insert(Rcpp::as(*ptr)); + } + } + Headers res; std::istringstream stream(headers); std::string buffer; @@ -35,7 +43,13 @@ Rcpp::List cpp_parse_headers(const char* headers) { } char sep = key == "cookie" ? ';' : ','; std::vector val_vec; - str_split(val_str, val_vec, sep, true); + if (h_to_split.find(key) != h_to_split.end()) { + str_split(val_str, val_vec, sep, true); + } else { + str_trim(val_str); + val_vec.push_back(val_str); + } + if (res.find(key) != res.end()) { res[key].insert(res[key].end(), val_vec.begin(), val_vec.end()); } From 65a593850f09f83d1c7a87753ca10ab0d088b3d4 Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Wed, 8 Jun 2022 19:20:51 +0400 Subject: [PATCH 06/19] add workflow to build docker image --- .github/workflows/build-image-tag.yaml | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/build-image-tag.yaml diff --git a/.github/workflows/build-image-tag.yaml b/.github/workflows/build-image-tag.yaml new file mode 100644 index 0000000..4fa4da1 --- /dev/null +++ b/.github/workflows/build-image-tag.yaml @@ -0,0 +1,30 @@ +name: Build tag + +on: + push: + tags: + - 'v*.*.*' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install Docker + run: curl https://get.docker.com | sh + + - name: Set tag name + run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Build image + run: docker build -t ${{ secrets.DOCKER_USER }}/${{ secrets.DOCKER_REPO }}:$(echo $TAG | tr -dc '0-9\.') -f docker/Dockerfile . + + - name: Push image + run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_TOKEN }} && docker push ${{ secrets.DOCKER_USER }}/${{ secrets.DOCKER_REPO }}:$(echo $TAG | tr -dc '0-9\.') + + - name: Build minimal image + run: docker build -t ${{ secrets.DOCKER_USER }}/${{ secrets.DOCKER_REPO }}:$(echo $TAG | tr -dc '0-9\.')-minimal -f docker/Dockerfile-r-minimal . + + - name: Push minimal image + run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_TOKEN }} && docker push ${{ secrets.DOCKER_USER }}/${{ secrets.DOCKER_REPO }}:$(echo $TAG | tr -dc '0-9\.')-minimal From 9967c3414426c226093a8fd82f37c28e7ecf58c1 Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Thu, 9 Jun 2022 09:04:14 +0400 Subject: [PATCH 07/19] update GA --- .github/workflows/build-image-tag.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-tag.yaml b/.github/workflows/build-image-tag.yaml index 5a1a178..8982bbc 100644 --- a/.github/workflows/build-image-tag.yaml +++ b/.github/workflows/build-image-tag.yaml @@ -15,12 +15,15 @@ jobs: run: curl https://get.docker.com | sh - name: Set tag name - run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - run: echo "DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV + run: echo "TAG=${GITHUB_REF#refs/*/} DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV - - name: Build images + - name: Build standard image run: docker build -t $DOCKER_IMAGE -f docker/Dockerfile . + + - name: Build alpine image run: docker build -t $DOCKER_IMAGE-alpine -f docker/Dockerfile-r-minimal . - name: Push images - run: docker login -u dselivanov -p ${{ secrets.DOCKER_TOKEN }} && docker push $DOCKER_IMAGE && docker push $DOCKER_IMAGE-alpine + run: docker login -u dselivanov -p ${{ secrets.DOCKER_TOKEN }} && \ + docker push $DOCKER_IMAGE && \ + docker push $DOCKER_IMAGE-alpine From f01f5c946add9aeb1c22f107b1bbb679bf7f1fb9 Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Thu, 9 Jun 2022 09:13:55 +0400 Subject: [PATCH 08/19] update GA --- .github/workflows/build-image-tag.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-image-tag.yaml b/.github/workflows/build-image-tag.yaml index 8982bbc..ff267c9 100644 --- a/.github/workflows/build-image-tag.yaml +++ b/.github/workflows/build-image-tag.yaml @@ -15,15 +15,15 @@ jobs: run: curl https://get.docker.com | sh - name: Set tag name - run: echo "TAG=${GITHUB_REF#refs/*/} DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV + run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV && "DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV - name: Build standard image - run: docker build -t $DOCKER_IMAGE -f docker/Dockerfile . + run: docker build -t ${DOCKER_IMAGE} -f docker/Dockerfile . - name: Build alpine image - run: docker build -t $DOCKER_IMAGE-alpine -f docker/Dockerfile-r-minimal . + run: docker build -t ${DOCKER_IMAGE}-alpine -f docker/Dockerfile-r-minimal . - name: Push images run: docker login -u dselivanov -p ${{ secrets.DOCKER_TOKEN }} && \ - docker push $DOCKER_IMAGE && \ - docker push $DOCKER_IMAGE-alpine + docker push ${DOCKER_IMAGE} && \ + docker push ${DOCKER_IMAGE}-alpine From eb821827d5a7b1327ca22e67371280fda97ece3f Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Thu, 9 Jun 2022 09:20:55 +0400 Subject: [PATCH 09/19] update GA-2 --- .github/workflows/build-image-tag.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-tag.yaml b/.github/workflows/build-image-tag.yaml index ff267c9..102e4a3 100644 --- a/.github/workflows/build-image-tag.yaml +++ b/.github/workflows/build-image-tag.yaml @@ -15,7 +15,7 @@ jobs: run: curl https://get.docker.com | sh - name: Set tag name - run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV && "DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV + run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV && echo "DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV - name: Build standard image run: docker build -t ${DOCKER_IMAGE} -f docker/Dockerfile . From 24fe69ebdf7f559f9053bc7661af5e2494ce44bb Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Thu, 9 Jun 2022 09:57:22 +0400 Subject: [PATCH 10/19] update GA meta --- .github/workflows/build-image-tag.yaml | 72 +++++++++++++++++++++----- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-image-tag.yaml b/.github/workflows/build-image-tag.yaml index 102e4a3..908a206 100644 --- a/.github/workflows/build-image-tag.yaml +++ b/.github/workflows/build-image-tag.yaml @@ -3,27 +3,71 @@ name: Build tag on: push: tags: - - 'v*.*.*' + - 'v*' jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install Docker - run: curl https://get.docker.com | sh + - name: Checkout + uses: actions/checkout@v2 - - name: Set tag name - run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV && echo "DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + rexyai/restrserve + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} - - name: Build standard image - run: docker build -t ${DOCKER_IMAGE} -f docker/Dockerfile . + - name: Docker meta alpine + id: meta_alpine + uses: docker/metadata-action@v4 + with: + flavor: | + suffix=-alpine + images: | + rexyai/restrserve + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} - - name: Build alpine image - run: docker build -t ${DOCKER_IMAGE}-alpine -f docker/Dockerfile-r-minimal . + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: dselivanov + password: ${{ secrets.DOCKER_TOKEN }} - - name: Push images - run: docker login -u dselivanov -p ${{ secrets.DOCKER_TOKEN }} && \ - docker push ${DOCKER_IMAGE} && \ - docker push ${DOCKER_IMAGE}-alpine + - name: Build and push standard image + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Build and push alpine image + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/Dockerfile-r-minimal + tags: ${{ steps.meta_alpine.outputs.tags }} + labels: ${{ steps.meta_alpine.outputs.labels }} + +# - name: Set tag name +# run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV && \ +# echo "DOCKER_IMAGE=rexyai/restrserve:$(echo $TAG | tr -dc '0-9\.')" >> $GITHUB_ENV + +# - name: Build standard image +# run: docker build -t $(echo $DOCKER_IMAGE) -f docker/Dockerfile . + +# - name: Build alpine image +# run: docker build -t $(echo $DOCKER_IMAGE)-alpine -f docker/Dockerfile-r-minimal . + +# - name: Push images +# run: +# docker push $(echo $DOCKER_IMAGE) && \ +# docker push $(echo $DOCKER_IMAGE)-alpine From f34bc30d07bd3d712abcef70e695639600738471 Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Thu, 9 Jun 2022 13:01:41 +0400 Subject: [PATCH 11/19] remove haproxy from base docker --- docker/Dockerfile | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2921195..a26324c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,15 +8,6 @@ RUN apt-get update && \ install2.r -r http://www.rforge.net/ $R_FORGE_PKGS && \ install2.r $R_CRAN_PKGS -RUN cd /tmp && \ - wget http://www.haproxy.org/download/2.0/src/haproxy-2.0.8.tar.gz && \ - tar -xvzf haproxy-2.0.8.tar.gz && \ - cd haproxy-2.0.8/ && \ - make TARGET=linux-glibc && \ - chmod +x haproxy && \ - cp haproxy /bin/ && \ - rm -rf /tmp/* - COPY . /tmp/RestRserve RUN R CMD build --no-manual --no-build-vignettes /tmp/RestRserve && \ From 54574392893a3a22774e40132bee9651dd5ac1bf Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Thu, 9 Jun 2022 13:02:02 +0400 Subject: [PATCH 12/19] push image after build --- .github/workflows/build-image-tag.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-image-tag.yaml b/.github/workflows/build-image-tag.yaml index 908a206..cce5d30 100644 --- a/.github/workflows/build-image-tag.yaml +++ b/.github/workflows/build-image-tag.yaml @@ -44,6 +44,7 @@ jobs: - name: Build and push standard image uses: docker/build-push-action@v3 with: + push: true context: . file: ./docker/Dockerfile tags: ${{ steps.meta.outputs.tags }} @@ -52,6 +53,7 @@ jobs: - name: Build and push alpine image uses: docker/build-push-action@v3 with: + push: true context: . file: ./docker/Dockerfile-r-minimal tags: ${{ steps.meta_alpine.outputs.tags }} From bd8db3b12a2574c5055175339b9af1c52d4fe5eb Mon Sep 17 00:00:00 2001 From: Dmitriy Selivanov Date: Thu, 9 Jun 2022 13:45:28 +0400 Subject: [PATCH 13/19] CRAN release 1.2.0 --- .github/workflows/build-image-tag.yaml | 2 +- DESCRIPTION | 2 +- NEWS.md | 6 ++++++ README.md | 20 ++++++++++---------- cran-comments.md | 3 +-- vignettes/benchmarks/Benchmarks.Rmd | 8 ++++---- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-image-tag.yaml b/.github/workflows/build-image-tag.yaml index cce5d30..094b205 100644 --- a/.github/workflows/build-image-tag.yaml +++ b/.github/workflows/build-image-tag.yaml @@ -28,7 +28,7 @@ jobs: uses: docker/metadata-action@v4 with: flavor: | - suffix=-alpine + suffix=-alpine,onlatest=true images: | rexyai/restrserve tags: | diff --git a/DESCRIPTION b/DESCRIPTION index 8a33a91..d816c3b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,7 +8,7 @@ Description: application development. Out of the box allows to serve requests using 'Rserve' package, but flexible enough to integrate with other HTTP servers such as 'httpuv'. -Version: 1.1.1 +Version: 1.2.0 Authors@R: c( person(given = "Dmitry", family = "Selivanov", diff --git a/NEWS.md b/NEWS.md index 78aeb96..55f711a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,14 @@ +# RestRserve + ## Changelog * 2022-06-08 - 1.2.0 * Expose option to control which HTTP headers need to be split by comma during parsing. See `options("RestRserve.headers.split")`. See #187, #189. Thanks @DavZim. * Improved ETag Middleware - see #188. Thanks @DavZim. + * Fix automatic docker builds. Now builds are made with github actions. + * docker images are based on R 4.2.0 now + * minimal images are based on Alpine linux from [r-minimal](https://github.com/r-hub/r-minimal) + * removed HAproxy from standard RestRserve image * 2022-04-20 - 1.1.1 * Skip tests on the live Rserve http server on CRAN which caused spurious test errors diff --git a/README.md b/README.md index 691c35d..0cda003 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ![tinyverse](https://tinyverse.netlify.com/badge/RestRserve) -[RestRserve](https://github.com/rexyai/RestRserve) is an R web API framework for building **high-performance** AND **robust** microservices and app backends. With [Rserve](https://github.com/s-u/Rserve) backend on UNIX-like systems it is **parallel by design**. It will handle incoming requests in parallel - each request in a separate fork (all the credits should go to [Simon Urbanek](https://github.com/s-u)). +[RestRserve](https://github.com/rexyai/RestRserve) is an R web API framework for building **high-performance** AND **robust** microservices and app backends. On UNIX-like systems and [Rserve](https://github.com/s-u/Rserve) backend RestRserve handles requests in parallel: each request in a separate fork. All credits for go to [Simon Urbanek](https://github.com/s-u). ## Quick start @@ -56,17 +56,17 @@ Using convenient `.req`, `.res` names for handler arguments allows to leverage a ## Learn RestRserve - follow [quick start guide on http://restrserve.org/](https://restrserve.org/articles/RestRserve.html) for more details. -- check out "Articles" section on https://restrserve.org/ -- browse [examples on https://github.com/rexyai/RestRserve](https://github.com/rexyai/RestRserve/tree/master/inst/examples) +- see "Articles" section on https://restrserve.org/ +- check out [examples on https://github.com/rexyai/RestRserve](https://github.com/rexyai/RestRserve/tree/master/inst/examples) ## Features -- Stable, easy to install, small number of dependencies +- Stable, easy to install, few dependencies +- Concise and intuitive syntax +- Well documented, comes with **many examples** - see [inst/examples](https://github.com/rexyai/RestRserve/tree/master/inst/examples) - Fully featured http server with the **support for URL encoded and multipart forms** - Build **safe and secure applications** - RestRserve supports *https*, provides building blocks for basic/token authentication -- Concise and intuitive syntax - **Raise meaningful http errors** and allows to interrupt request handling from any place of the user code -- Well documented, comes with **many examples** - see [inst/examples](https://github.com/rexyai/RestRserve/tree/master/inst/examples) - Saves you from boilerplate code: - automatically decodes request body from the common formats - automatically encodes response body to the common formats @@ -74,7 +74,7 @@ Using convenient `.req`, `.res` names for handler arguments allows to leverage a - helps to expose OpenAPI and Swagger/Redoc/Rapidoc UI - It is [fast](https://restrserve.org/articles/benchmarks/Benchmarks.html)! -![](vignettes/img/bench-rps.png) +![](https://github.com/rexyai/RestRserve/blob/master/vignettes/img/bench-rps.png?raw=true) ## Installation @@ -85,16 +85,16 @@ install.packages("RestRserve", repos = "https://cloud.r-project.org") ### Docker -Debian and Alpine based images are available from docker-hub: [https://hub.docker.com/r/rexyai/restrserve/](https://hub.docker.com/r/rexyai/restrserve/) +Debian and Alpine based images are available on docker-hub -[https://hub.docker.com/r/rexyai/restrserve/](https://hub.docker.com/r/rexyai/restrserve/) ```sh docker pull rexyai/restrserve ``` -Or install specific version: +You can also install specific version (and we encourage to do so): ```sh -docker pull rexyai/restrserve:0.4.0-minimal +docker pull rexyai/restrserve:1.2.0-alpine ``` ## Contributing diff --git a/cran-comments.md b/cran-comments.md index 684d622..6c3f2c8 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,7 +1,6 @@ ## New submission -- fix CRAN failing test of the live Rserve http server (don't run it on CRAN as this error is spurious and related to the env/not being able to start process) -- import digest::digest +- new release adding new features ### Test environments diff --git a/vignettes/benchmarks/Benchmarks.Rmd b/vignettes/benchmarks/Benchmarks.Rmd index dee5fd9..f63d7e3 100644 --- a/vignettes/benchmarks/Benchmarks.Rmd +++ b/vignettes/benchmarks/Benchmarks.Rmd @@ -221,7 +221,7 @@ plot_results( "../img/bench-rps.png" ) ``` - + ## No keep-alive @@ -239,7 +239,7 @@ plot_results( "../img/bench-rps-no-keep-alive.png" ) ``` - + Nonetheless one can always put application behind proxy (such as [HAproxy](http://www.haproxy.org/) or [nginx](https://www.nginx.com/)). It will maintain pool of connections to RestRserve and hence won't suffer from creating new connections. @@ -359,7 +359,7 @@ plot_results( facet = TRUE ) ``` - + As can be seen `RestRserce` performs very well on every workload and scales linearly with number of cores. @@ -383,6 +383,6 @@ plot_results( ) ``` - + Due to the overhead of creating a new process for each request and [R's byte compiler overhead](https://github.com/rexyai/RestRserve/issues/149) `RestRserve` with `Rserve` backend does not perform as quickly as `plumber` when computing instantaneous routes. However, `RestRserve` still shows it's strength when executing routes that have high computational costs. No extra coding logic is needed to leverage `RestRserve`'s multi-threaded execution. Mixing `plumber` and `future` together for _high computation_ routes brings performance that scales with the number of concurrent requests, but at the cost of extra route logic and domain knowledge. From 71219d5051156b3a6bf634f37a424f1779f4293d Mon Sep 17 00:00:00 2001 From: AbrJA Date: Wed, 13 Mar 2024 17:29:39 -0600 Subject: [PATCH 14/19] Bug fix: Killing background processes --- R/BackendRserve.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/BackendRserve.R b/R/BackendRserve.R index c736ba7..a76271e 100644 --- a/R/BackendRserve.R +++ b/R/BackendRserve.R @@ -347,10 +347,10 @@ ApplicationProcess = R6::R6Class( #' Send signal to process. #' @param signal Singal code. kill = function(signal = 15L) { - # kill service - tools::pskill(self$pid, signal) - # kill childs - system(sprintf("pkill -%s -P %s", signal, self$pid), wait = FALSE) + # get childs + child_pids <- suppressWarnings(system(sprintf("pgrep -P %s", self$pid), intern = TRUE)) + # kill all + tools::pskill(c(self$pid, child_pids), signal) } ) ) From a8bf1ef9342e008745894a718ca716d0514e62ee Mon Sep 17 00:00:00 2001 From: AbrJA Date: Wed, 13 Mar 2024 17:32:04 -0600 Subject: [PATCH 15/19] Typos correction --- R/BackendRserve.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/BackendRserve.R b/R/BackendRserve.R index a76271e..3139069 100644 --- a/R/BackendRserve.R +++ b/R/BackendRserve.R @@ -330,7 +330,7 @@ BackendRserve = R6::R6Class( #' @title Creates ApplicationProcess object #' #' @description -#' Creates ApplicationProcess to hold PID of the runnung applicaiton. +#' Creates ApplicationProcess to hold PID of the running application. #' ApplicationProcess = R6::R6Class( classname = "ApplicationProcess", @@ -345,7 +345,7 @@ ApplicationProcess = R6::R6Class( }, #' @description #' Send signal to process. - #' @param signal Singal code. + #' @param signal Signal code. kill = function(signal = 15L) { # get childs child_pids <- suppressWarnings(system(sprintf("pgrep -P %s", self$pid), intern = TRUE)) From f968e313967843179846ed12cadf1602d2a31091 Mon Sep 17 00:00:00 2001 From: Dmitry Selivanov Date: Sun, 14 Apr 2024 13:47:33 +0800 Subject: [PATCH 16/19] minor - user "=" syntax --- R/BackendRserve.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/BackendRserve.R b/R/BackendRserve.R index 3139069..f9cf94c 100644 --- a/R/BackendRserve.R +++ b/R/BackendRserve.R @@ -348,7 +348,7 @@ ApplicationProcess = R6::R6Class( #' @param signal Signal code. kill = function(signal = 15L) { # get childs - child_pids <- suppressWarnings(system(sprintf("pgrep -P %s", self$pid), intern = TRUE)) + child_pids = suppressWarnings(system(sprintf("pgrep -P %s", self$pid), intern = TRUE)) # kill all tools::pskill(c(self$pid, child_pids), signal) } From 10cb8f4bb0581c668e80de3a27e85fc3fa1255d6 Mon Sep 17 00:00:00 2001 From: Dmitry Selivanov Date: Sun, 14 Apr 2024 13:52:09 +0800 Subject: [PATCH 17/19] update r-lib actions --- .github/workflows/R-CMD-check.yaml | 6 +++--- .github/workflows/pkgdown.yaml | 6 +++--- .github/workflows/test-coverage.yaml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index eeb2bf3..471dc7c 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -34,13 +34,13 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} - - uses: r-lib/actions/setup-pandoc@v1 + - uses: r-lib/actions/setup-pandoc@v2 - name: Query dependencies run: | diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index a9aef3c..dbd1731 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -11,11 +11,11 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-r@v2 - - uses: r-lib/actions/setup-pandoc@v1 + - uses: r-lib/actions/setup-pandoc@v2 - name: Query dependencies run: | diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index b191a62..743185b 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -16,11 +16,11 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: r-lib/actions/setup-r@master + - uses: r-lib/actions/setup-r@v2 - - uses: r-lib/actions/setup-pandoc@master + - uses: r-lib/actions/setup-pandoc@v2 - name: Query dependencies run: | From a40389237cdef51845ca02538e6b1a7fb2a2b93e Mon Sep 17 00:00:00 2001 From: Dmitry Selivanov Date: Sun, 14 Apr 2024 13:57:49 +0800 Subject: [PATCH 18/19] use actions cache v4 --- .github/workflows/R-CMD-check.yaml | 2 +- .github/workflows/pkgdown.yaml | 2 +- .github/workflows/test-coverage.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 471dc7c..38dfb14 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -51,7 +51,7 @@ jobs: - name: Cache R packages if: runner.os != 'Windows' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ env.R_LIBS_USER }} key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index dbd1731..c5ebf62 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -25,7 +25,7 @@ jobs: shell: Rscript {0} - name: Cache R packages - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ env.R_LIBS_USER }} key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 743185b..1876de8 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -30,7 +30,7 @@ jobs: shell: Rscript {0} - name: Cache R packages - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ env.R_LIBS_USER }} key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} From faf7585190c8d15abf8062d0161198e224677787 Mon Sep 17 00:00:00 2001 From: Dmitry Selivanov Date: Thu, 18 Apr 2024 09:39:37 +0800 Subject: [PATCH 19/19] CRAN 1.2.2 --- DESCRIPTION | 3 +-- NEWS.md | 6 +++++- R/AuthBackendBasic.R | 2 +- R/AuthBackendBearer.R | 2 +- R/HTTPDate.R | 2 +- README.md | 4 ++-- cran-comments.md | 10 +++++----- man/ApplicationProcess.Rd | 4 ++-- man/AuthBackendBasic.Rd | 4 ++-- man/AuthBackendBearer.Rd | 4 ++-- man/AuthMiddleware.Rd | 4 ++-- man/HTTPDate-class.Rd | 2 +- src/Makevars | 1 - src/Makevars.win | 1 - vignettes/Authentication.Rmd | 2 +- 15 files changed, 26 insertions(+), 25 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ac91713..19dd7d5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -52,10 +52,9 @@ Suggests: sys LinkingTo: Rcpp -SystemRequirements: C++11 ByteCompile: true KeepSource: true Encoding: UTF-8 Roxygen: list(markdown = TRUE, roclets = c("rd", "namespace", "collate")) -RoxygenNote: 7.2.1 +RoxygenNote: 7.3.1 VignetteBuilder: knitr diff --git a/NEWS.md b/NEWS.md index 1ca6728..f704f2c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# RestRserve 1.2.2 (2024-04-15) +* check inheritance from `error` Thanks @hafen for report #207 and PR #208 +* more robust kill of the child processes. Thanks @AbrJA for report #209 and PR #210 + # RestRserve 1.2.1 (2022-09-11) * update NEWS.md file to follow CRAN specification * update docs with new roxygen. Fixes CRAN notes in HTML5 compatibility @@ -45,7 +49,7 @@ * parse content-type directly from headers - see #137 # RestRserve 0.2.1 (2020-03-19) -* update code for header names validation to conform to [rfc7230](https://tools.ietf.org/html/rfc7230#section-3.2.6), see #132 +* update code for header names validation to conform to [rfc7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6), see #132 * generate documentation with roxygen2 7.1.0 which has support for R6 classes # RestRserve 0.2.0.2 (2020-03-06) diff --git a/R/AuthBackendBasic.R b/R/AuthBackendBasic.R index b5c59b4..662dd97 100644 --- a/R/AuthBackendBasic.R +++ b/R/AuthBackendBasic.R @@ -4,7 +4,7 @@ #' Creates AuthBackendBasic class object. #' #' @references -#' [RFC7617](https://tools.ietf.org/html/rfc7617) +#' [RFC7617](https://datatracker.ietf.org/doc/html/rfc7617) #' [Wikipedia](https://en.wikipedia.org/wiki/Basic_access_authentication) #' #' @export diff --git a/R/AuthBackendBearer.R b/R/AuthBackendBearer.R index 0e929a2..e3449d0 100644 --- a/R/AuthBackendBearer.R +++ b/R/AuthBackendBearer.R @@ -6,7 +6,7 @@ #' @export #' #' @references -#' [RFC6750](https://tools.ietf.org/html/rfc6750) +#' [RFC6750](https://datatracker.ietf.org/doc/html/rfc6750) #' [Specification](https://swagger.io/docs/specification/authentication/bearer-authentication/) #' #' @seealso [AuthMiddleware] [Request] [Response] diff --git a/R/HTTPDate.R b/R/HTTPDate.R index 9c1ad8e..fadeb06 100644 --- a/R/HTTPDate.R +++ b/R/HTTPDate.R @@ -11,7 +11,7 @@ #' @exportClass HTTPDate #' #' @references -#' [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.1.1) +#' [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) #' [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date) #' #' @examples diff --git a/README.md b/README.md index 15923de..08032b5 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ [![R build status](https://github.com/rexyai/RestRserve/workflows/R-CMD-check/badge.svg)](https://github.com/rexyai/RestRserve/actions) [![CRAN status](https://www.r-pkg.org/badges/version/RestRserve)](https://cran.r-project.org/package=RestRserve) -[![codecov](https://codecov.io/gh/rexyai/RestRserve/branch/master/graph/badge.svg)](https://codecov.io/gh/rexyai/RestRserve/branch/master) +[![codecov](https://codecov.io/gh/rexyai/RestRserve/branch/master/graph/badge.svg)](https://app.codecov.io/gh/rexyai/RestRserve/branch/master) [![License](https://eddelbuettel.github.io/badges/GPL2+.svg)](http://www.gnu.org/licenses/gpl-2.0.html) [![Lifecycle: stable](https://lifecycle.r-lib.org/articles/figures/lifecycle-stable.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) -[![gitter](https://img.shields.io/gitter/room/RestRserve/community.svg?color=61D6AD&style=popout)](https://gitter.im/RestRserve/community) +[![gitter](https://img.shields.io/gitter/room/RestRserve/community.svg?color=61D6AD&style=popout)](https://app.gitter.im/#/room/#RestRserve_community:gitter.im) ![tinyverse](https://tinyverse.netlify.com/badge/RestRserve) diff --git a/cran-comments.md b/cran-comments.md index 93b8ec8..9ca46b6 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,13 +1,13 @@ ## New submission -- fixed HTML validation problems discovered by CRAN checks -- fixed NEWS.md to follow CRAN format -- "Additional issues" issues are related to expired certificate on CDN. Fixed now. +- minor bugfixes +- removed SystemRequirements: C++11 from DESCRIPTION +- fixed 301 urls ### Test environments -- local mac os, R 4.0.5 -- Ubuntu 20.04 (gh-actions), R 4.0.3 +- local mac os, R 4.3.1 +- Ubuntu 20.04 (gh-actions), R 4.3.3 - win-builder (devel) ### R CMD check results diff --git a/man/ApplicationProcess.Rd b/man/ApplicationProcess.Rd index ddaaf85..7ac2e3b 100644 --- a/man/ApplicationProcess.Rd +++ b/man/ApplicationProcess.Rd @@ -4,7 +4,7 @@ \alias{ApplicationProcess} \title{Creates ApplicationProcess object} \description{ -Creates ApplicationProcess to hold PID of the runnung applicaiton. +Creates ApplicationProcess to hold PID of the running application. } \section{Public fields}{ \if{html}{\out{
}} @@ -50,7 +50,7 @@ Send signal to process. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{signal}}{Singal code.} +\item{\code{signal}}{Signal code.} } \if{html}{\out{
}} } diff --git a/man/AuthBackendBasic.Rd b/man/AuthBackendBasic.Rd index 022059c..084bae3 100644 --- a/man/AuthBackendBasic.Rd +++ b/man/AuthBackendBasic.Rd @@ -35,15 +35,15 @@ auth_backend$authenticate(rq, rs) # TRUE } \references{ -\href{https://tools.ietf.org/html/rfc7617}{RFC7617} +\href{https://datatracker.ietf.org/doc/html/rfc7617}{RFC7617} \href{https://en.wikipedia.org/wiki/Basic_access_authentication}{Wikipedia} } \seealso{ \link{AuthMiddleware} \link{Request} \link{Response} Other AuthBackend: -\code{\link{AuthBackendBearer}}, \code{\link{AuthBackend}}, +\code{\link{AuthBackendBearer}}, \code{\link{AuthMiddleware}} } \concept{AuthBackend} diff --git a/man/AuthBackendBearer.Rd b/man/AuthBackendBearer.Rd index bc01961..49a73eb 100644 --- a/man/AuthBackendBearer.Rd +++ b/man/AuthBackendBearer.Rd @@ -34,15 +34,15 @@ auth_backend$authenticate(rq, rs) # TRUE } \references{ -\href{https://tools.ietf.org/html/rfc6750}{RFC6750} +\href{https://datatracker.ietf.org/doc/html/rfc6750}{RFC6750} \href{https://swagger.io/docs/specification/authentication/bearer-authentication/}{Specification} } \seealso{ \link{AuthMiddleware} \link{Request} \link{Response} Other AuthBackend: -\code{\link{AuthBackendBasic}}, \code{\link{AuthBackend}}, +\code{\link{AuthBackendBasic}}, \code{\link{AuthMiddleware}} } \concept{AuthBackend} diff --git a/man/AuthMiddleware.Rd b/man/AuthMiddleware.Rd index 9f5cd3d..b90d688 100644 --- a/man/AuthMiddleware.Rd +++ b/man/AuthMiddleware.Rd @@ -10,9 +10,9 @@ Adds various authorizations to \link{Application}. \link{Middleware} \link{Application} Other AuthBackend: +\code{\link{AuthBackend}}, \code{\link{AuthBackendBasic}}, -\code{\link{AuthBackendBearer}}, -\code{\link{AuthBackend}} +\code{\link{AuthBackendBearer}} } \concept{AuthBackend} \section{Super class}{ diff --git a/man/HTTPDate-class.Rd b/man/HTTPDate-class.Rd index 2e6a9f8..9a0d9d5 100644 --- a/man/HTTPDate-class.Rd +++ b/man/HTTPDate-class.Rd @@ -20,6 +20,6 @@ class(dt) = "HTTPDate" as(dt, "POSIXct") } \references{ -\href{https://tools.ietf.org/html/rfc7231#section-7.1.1.1}{RFC7231} +\href{https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1}{RFC7231} \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date}{MDN} } diff --git a/src/Makevars b/src/Makevars index f7b9176..8cd93a7 100644 --- a/src/Makevars +++ b/src/Makevars @@ -1,2 +1 @@ -CXX_STD = CXX11 PKG_CXXFLAGS = -DRCPP_NO_MODULES diff --git a/src/Makevars.win b/src/Makevars.win index f7b9176..8cd93a7 100644 --- a/src/Makevars.win +++ b/src/Makevars.win @@ -1,2 +1 @@ -CXX_STD = CXX11 PKG_CXXFLAGS = -DRCPP_NO_MODULES diff --git a/vignettes/Authentication.Rmd b/vignettes/Authentication.Rmd index 55b5da3..078a771 100644 --- a/vignettes/Authentication.Rmd +++ b/vignettes/Authentication.Rmd @@ -117,7 +117,7 @@ res$body `Bearer` authentication (also called "token" authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The name "Bearer authentication" can be understood as "give access to the bearer of this token." The bearer token is a cryptic string, usually generated by the server in response to a login request. The client must send this token in the Authorization header when making requests to protected resources. -The `Bearer` authentication scheme was originally created as part of `OAuth 2.0` in [RFC 6750](https://tools.ietf.org/html/rfc6750), but is sometimes also used on its own. Similarly to Basic authentication, Bearer authentication should only be used over HTTPS (SSL). +The `Bearer` authentication scheme was originally created as part of `OAuth 2.0` in [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750), but is sometimes also used on its own. Similarly to Basic authentication, Bearer authentication should only be used over HTTPS (SSL). ```{r}