diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index e80d8ffd1..ea14c01f3 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -4,7 +4,7 @@ on: push: branches: - "master" - - "docker/**" + - "docker**" schedule: - cron: '0 0 1 * *' # first of every month @@ -26,7 +26,7 @@ jobs: # always overwrite the latest version with the CRAN version tags: "v1.0.0,latest" ref: "v1.0.0" - + # always rebuild legacy to pick up newer R library builds - name: "v0.4.6" tags: "v0.4.6" diff --git a/Dockerfile b/Dockerfile index 7f87a8bbc..8fa4f85d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ ARG PLUMBER_REF=master RUN Rscript -e "remotes::install_github('rstudio/plumber@${PLUMBER_REF}')" EXPOSE 8000 -ENTRYPOINT ["R", "-e", "pr <- plumber::plumb(rev(commandArgs())[1]); args <- list(host = '0.0.0.0', port = 8000); if (packageVersion('plumber') >= 1.0.0) { pr$setDocs(TRUE) } else { args$swagger <- TRUE }; do.call(pr$run, args)"] +ENTRYPOINT ["R", "-e", "pr <- plumber::plumb(rev(commandArgs())[1]); args <- list(host = '0.0.0.0', port = 8000); if (packageVersion('plumber') >= '1.0.0') { pr$setDocs(TRUE) } else { args$swagger <- TRUE }; do.call(pr$run, args)"] # Copy installed example to default file at ~/plumber.R ARG ENTRYPOINT_FILE=/usr/local/lib/R/site-library/plumber/plumber/04-mean-sum/plumber.R diff --git a/NEWS.md b/NEWS.md index 51922da4b..27135fdf8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -25,6 +25,8 @@ plumber 1.0.0.9999 Development version ### Bug fixes +* Fixed bug where `httpuv` would return a status of `500` with body `An exception occurred` if no headers were set on the response object. (#745) + * Fixed bug where all `pr_*()` returned invisibly. Now all `pr_*()` methods will print the router if displayed in the console. (#740) * Ignore regular comments in block parsing (@meztez #718) @@ -41,6 +43,8 @@ plumber 1.0.0.9999 Development version * Plumber will now display a circular reference if one is found while printing. (#738) +* Changed `future::plan()` from `multiprocess` to `multisession` in example API `14-future` as "Strategy 'multiprocess' is deprecated in future (>= 1.20.0)". (#747) + plumber 1.0.0 -------------------------------------------------------------------------------- diff --git a/R/plumber-response.R b/R/plumber-response.R index 536ab0d06..37a382ce6 100644 --- a/R/plumber-response.R +++ b/R/plumber-response.R @@ -7,7 +7,7 @@ PlumberResponse <- R6Class( }, status = 200L, body = NULL, - headers = list(), + headers = stats::setNames(list(), character()), serializer = NULL, setHeader = function(name, value){ he <- list() diff --git a/R/plumber.R b/R/plumber.R index 177a01d3d..99bd63de3 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -413,7 +413,7 @@ Plumber <- R6Class( as.character(length(self$mounts)), " sub-router", ifelse(length(self$mounts) == 1, "", "s"),".\n", sep="")) if(topLevel){ - cat(prefix, crayon::silver("# Call run() on this object to start the API.\n"), sep="") + cat(prefix, crayon::silver("# Use `pr_run()` on this object to start the API.\n"), sep="") } # Filters diff --git a/inst/plumber/14-future/plumber.R b/inst/plumber/14-future/plumber.R index 3d057bd69..b7f9a797e 100644 --- a/inst/plumber/14-future/plumber.R +++ b/inst/plumber/14-future/plumber.R @@ -2,8 +2,8 @@ library(promises) library(future) -future::plan("multiprocess") # use all available cores -# future::plan(future::multiprocess(workers = 2)) # only two cores +future::plan("multisession") # a worker for each core +# future::plan(future::multisession(workers = 2)) # only two workers # Quick manual test: # Within 10 seconds... diff --git a/inst/plumber/14-future/test-future.R b/inst/plumber/14-future/test-future.R index ee941ece4..58d635f96 100644 --- a/inst/plumber/14-future/test-future.R +++ b/inst/plumber/14-future/test-future.R @@ -69,7 +69,7 @@ cat(readLines(log_file), sep = "\n") })) # -------------------------- -## Sample output using future::plan(future::multiprocess(workers = 2)) # only two cores +## Sample output using future::plan(future::multisession(workers = 2)) # only two workers # --START route requests # "/sync; 2019-10-07 13:11:06; pid:82424" - 1 # "/sync; 2019-10-07 13:11:07; pid:82424" - 3 @@ -88,7 +88,7 @@ cat(readLines(log_file), sep = "\n") # -------------------------- -## Sample output using future::plan("multiprocess") # use all available cores +## Sample output using future::plan("multisession") # a worker for each core # --START route requests # "/sync; 2019-10-07 13:16:22; pid:82424" - 1 # "/sync; 2019-10-07 13:16:23; pid:82424" - 3 diff --git a/tests/testthat/test-plumber-print.R b/tests/testthat/test-plumber-print.R index 1b888308a..bd185ac4a 100644 --- a/tests/testthat/test-plumber-print.R +++ b/tests/testthat/test-plumber-print.R @@ -19,7 +19,7 @@ test_that("prints correctly", { expected_output <- c( "# Plumber router with 2 endpoints, 4 filters, and 2 sub-routers.", - "# Call run() on this object to start the API.", + "# Use `pr_run()` on this object to start the API.", "├──[queryString]", "├──[body]", "├──[cookieParser]", @@ -43,7 +43,7 @@ test_that("prints correctly", { expected_output2 <- c( "# Plumber router with 1 endpoint, 4 filters, and 0 sub-routers.", - "# Call run() on this object to start the API.", + "# Use `pr_run()` on this object to start the API.", "├──[queryString]", "├──[body]", "├──[cookieParser]", @@ -74,7 +74,7 @@ test_that("prints correctly", { expected_output <- c( "# Plumber router with 0 endpoints, 4 filters, and 1 sub-router.", - "# Call run() on this object to start the API.", + "# Use `pr_run()` on this object to start the API.", "├──[queryString]", "├──[body]", "├──[cookieParser]", diff --git a/vignettes/tips-and-tricks.Rmd b/vignettes/tips-and-tricks.Rmd index 88903e6fd..e7779cfda 100644 --- a/vignettes/tips-and-tricks.Rmd +++ b/vignettes/tips-and-tricks.Rmd @@ -25,6 +25,72 @@ Most programmers first approach debugging by adding print statements to their co This approach is equally viable with Plumber. When developing your Plumber API in an interactive environment, this debugging output will be logged to the same terminal where you called `run()` on your API. In a non-interactive production environment, these messages will be included in the API server logs for later inspection. +### Router Stage Debugging + +Similar to print debugging, we can output what plumber knows at each stage of the processing pipeline. You can do this by adding [hooks](./programmatic-usage.html#router-hooks) to two key stages: `"postroute"` and `"postserialize"`. + +For example, we can add these lines to our `plumber.R` file: + +```r +#* @plumber +function(pr) { + pr %>% + pr_hook("postroute", function(req, value) { + # Print stage information + str(list( + stage = "postroute", + type = req$REQUEST_METHOD, + path = req$PATH_INFO, + value = value + )) + # Must return the `value` since we took one in + value + }) %>% + pr_hook("postserialize", function(req, value) { + # Print stage information + str(list( + stage = "postserialize", + type = req$REQUEST_METHOD, + path = req$PATH_INFO, + value = value + )) + # Must return the `value` since we took one in + value + }) +} +``` + +If we were to execute a `GET` request on `/stage_debug` + +```r +#* @get /stage_debug +function(req, res) { + return(42) +} +``` +, we would expect to see output like: + +``` +List of 4 + $ stage: chr "postroute" + $ type : chr "GET" + $ path : chr "/stage_debug" + $ value: num 42 +List of 4 + $ stage: chr "postserialize" + $ type : chr "GET" + $ path : chr "/stage_debug" + $ value:List of 3 + ..$ status : int 200 + ..$ headers:List of 1 + .. ..$ Content-Type: chr "application/json" + ..$ body : 'json' chr "[42]" +``` + +This output shows that the route `/stage_debug` calculated the value `42` and that the value was serialized using json. We should expect to see that the received response has a status of `200` and the body containing JSON matching `[42]`. + + + ### Interactive Debugging Print debugging is an obvious starting point, but most developers eventually wish for something more powerful. In R, this capacity is built in to the `browser()` function. If you're unfamiliar, `browser()` pauses the execution of some function and gives you an interactive session in which you can inspect the current value of internal variables or even proceed through your function one statement at a time.