diff --git a/404.html b/404.html index e44be84f3..73d2ee279 100644 --- a/404.html +++ b/404.html @@ -14,8 +14,8 @@ - - + + diff --git a/CONTRIBUTING.html b/CONTRIBUTING.html index d4c823721..b452eac1a 100644 --- a/CONTRIBUTING.html +++ b/CONTRIBUTING.html @@ -1,5 +1,5 @@ -Contributing • plumberContributing • plumber @@ -67,6 +67,7 @@
+

We welcome contributions to the plumber package!

To submit a contribution:

  1. Fork the repository and make your changes.

  2. diff --git a/LICENSE-text.html b/LICENSE-text.html index bf07acd09..1c42e85db 100644 --- a/LICENSE-text.html +++ b/LICENSE-text.html @@ -1,5 +1,5 @@ -License • plumberLicense • plumber diff --git a/PULL_REQUEST_TEMPLATE.html b/PULL_REQUEST_TEMPLATE.html index 7294dc341..1ced726e4 100644 --- a/PULL_REQUEST_TEMPLATE.html +++ b/PULL_REQUEST_TEMPLATE.html @@ -1,5 +1,5 @@ -NA • plumberNA • plumber diff --git a/articles/annotations.html b/articles/annotations.html index bdbfb9a51..b7aa85b84 100644 --- a/articles/annotations.html +++ b/articles/annotations.html @@ -15,8 +15,8 @@ - - + + @@ -93,7 +93,8 @@ -
    + +
    diff --git a/pkgdown.yml b/pkgdown.yml index 87b255704..b1a350184 100644 --- a/pkgdown.yml +++ b/pkgdown.yml @@ -1,4 +1,4 @@ -pandoc: 2.7.3 +pandoc: 3.1.2 pkgdown: 2.0.7 pkgdown_sha: ~ articles: @@ -13,7 +13,7 @@ articles: routing-and-input: routing-and-input.html security: security.html tips-and-tricks: tips-and-tricks.html -last_built: 2023-03-08T19:01Z +last_built: 2023-08-11T21:07Z urls: reference: https://www.rplumber.io/reference article: https://www.rplumber.io/articles diff --git a/reference/Hookable.html b/reference/Hookable.html index ec20810da..450441de0 100644 --- a/reference/Hookable.html +++ b/reference/Hookable.html @@ -1,6 +1,6 @@ Hookable — Hookable • plumberHookable — Hookable • plumberPackage Plumber Router — Plumber • plumberPackage Plumber Router — Plumber • plumberPlumber Endpoint — PlumberEndpoint • plumberPlumber Endpoint — PlumberEndpoint • plumberStatic file router — PlumberStatic • plumberStatic file router — PlumberStatic • plumberplumber step R6 class — PlumberStep • plumberplumber step R6 class — PlumberStep • plumberReturn an attachment response — as_attachment • plumberReturn an attachment response — as_attachment • plumberDeprecated functions — addSerializer • plumberDeprecated functions — addSerializer • plumber diff --git a/reference/deprecated_r6.html b/reference/deprecated_r6.html index c7aad6d3f..a11ba6e6b 100644 --- a/reference/deprecated_r6.html +++ b/reference/deprecated_r6.html @@ -2,7 +2,7 @@ Deprecated R6 functions — hookable • plumberDeprecated R6 functions — hookable • plumberDigitalOcean Plumber server — do_provision • plumberDigitalOcean Plumber server — do_provision • plumberEndpoint Serializer with Hooks — endpoint_serializer • plumberEndpoint Serializer with Hooks — endpoint_serializer • plumberForward Request to The Next Handler — forward • plumberRequest character set — get_character_set • plumberRequest character set — get_character_set • plumber diff --git a/reference/include_file.html b/reference/include_file.html index cb96c6720..2e30f8dee 100644 --- a/reference/include_file.html +++ b/reference/include_file.html @@ -1,5 +1,5 @@ -Send File Contents as Response — include_file • plumberSend File Contents as Response — include_file • plumber diff --git a/reference/index.html b/reference/index.html index 87ffc0e9b..252eeb45d 100644 --- a/reference/index.html +++ b/reference/index.html @@ -1,5 +1,5 @@ -Function reference • plumberFunction reference • plumber diff --git a/reference/is_plumber.html b/reference/is_plumber.html index 6c8e2b6e8..b1f446a69 100644 --- a/reference/is_plumber.html +++ b/reference/is_plumber.html @@ -1,5 +1,5 @@ -Determine if Plumber object — is_plumber • plumberDetermine if Plumber object — is_plumber • plumber diff --git a/reference/options_plumber.html b/reference/options_plumber.html index fe7482c6b..15fddeba9 100644 --- a/reference/options_plumber.html +++ b/reference/options_plumber.html @@ -1,7 +1,7 @@ Plumber options — options_plumber • plumberPlumber options — options_plumber • plumberPlumber Parsers — parser_form • plumberPlumber Parsers — parser_form • plumberPipe operator — %>% • plumberPipe operator — %>% • plumber diff --git a/reference/plumb.html b/reference/plumb.html index c13976b4f..79e9cc514 100644 --- a/reference/plumb.html +++ b/reference/plumb.html @@ -1,5 +1,5 @@ -Process a Plumber API — plumb • plumberProcess a Plumber API — plumb • plumber diff --git a/reference/plumb_api.html b/reference/plumb_api.html index 256be5404..d198f810d 100644 --- a/reference/plumb_api.html +++ b/reference/plumb_api.html @@ -1,6 +1,6 @@ Process a Package's Plumber API — plumb_api • plumberProcess a Package's Plumber API — plumb_api • plumberCreate a new Plumber router — pr • plumberCreate a new Plumber router — pr • plumber diff --git a/reference/pr_cookie.html b/reference/pr_cookie.html index 53b0cf506..224de5373 100644 --- a/reference/pr_cookie.html +++ b/reference/pr_cookie.html @@ -1,6 +1,6 @@ Store session data in encrypted cookies. — pr_cookie • plumberStore session data in encrypted cookies. — pr_cookie • plumberAdd a filter to Plumber router — pr_filter • plumberAdd a filter to Plumber router — pr_filter • plumberAdd handler to Plumber router — pr_handle • plumberAdd handler to Plumber router — pr_handle • plumber diff --git a/reference/pr_hook.html b/reference/pr_hook.html index 71ca773ec..7f03bd276 100644 --- a/reference/pr_hook.html +++ b/reference/pr_hook.html @@ -11,7 +11,7 @@ can store temporary data in these hooks that can be reused by other hooks processing this same request. -'>Register a hook — pr_hook • plumberRegister a hook — pr_hook • plumberSet the handler that is called when the incoming request can't be served — pr_set_404 • plumberSet the OpenAPI Specification — pr_set_api_spec • plumberSet the OpenAPI Specification — pr_set_api_spec • plumber diff --git a/reference/pr_set_debug.html b/reference/pr_set_debug.html index 0db7ed445..813f1be95 100644 --- a/reference/pr_set_debug.html +++ b/reference/pr_set_debug.html @@ -1,6 +1,6 @@ Set debug value to include error messages of routes cause an error — pr_set_debug • plumberSet debug value to include error messages of routes cause an error — pr_set_debug • plumberSet the API visual documentation — pr_set_docs • plumberSet the API visual documentation — pr_set_docs • plumberSet the callback to tell where the API visual documentation is located — pr_set_docs_callback • plumberSet the callback to tell where the API visual documentation is located — pr_set_docs_callback • plumberSet the error handler that is invoked if any filter or endpoint generates an -error — pr_set_error • plumberSet the default endpoint parsers for the router — pr_set_parsers • plumberSet the default endpoint parsers for the router — pr_set_parsers • plumberSet the default serializer of the router — pr_set_serializer • plumberSet the default serializer of the router — pr_set_serializer • plumberAdd a static route to the plumber object — pr_static • plumberAdd a static route to the plumber object — pr_static • plumber diff --git a/reference/random_cookie_key.html b/reference/random_cookie_key.html index c1b038ff4..2890b226d 100644 --- a/reference/random_cookie_key.html +++ b/reference/random_cookie_key.html @@ -1,5 +1,5 @@ -Random cookie key generator — random_cookie_key • plumberRandom cookie key generator — random_cookie_key • plumber diff --git a/reference/register_docs.html b/reference/register_docs.html index 6d122002f..b32b8c4b8 100644 --- a/reference/register_docs.html +++ b/reference/register_docs.html @@ -1,7 +1,7 @@ Add visual documentation for plumber to use — register_docs • plumberAdd visual documentation for plumber to use — register_docs • plumberManage parsers — register_parser • plumberManage parsers — register_parser • plumberRegister a Serializer — register_serializer • plumberRegister a Serializer — register_serializer • plumberPlumber Serializers — serializer_headers • plumberPlumber Serializers — serializer_headers • plumberStore session data in encrypted cookies. — session_cookie • plumberStore session data in encrypted cookies. — session_cookie • plumberValidate OpenAPI Spec — validate_api_spec • plumberValidate OpenAPI Spec — validate_api_spec • plumber diff --git a/search.json b/search.json index 1a406e33f..7e4c57d76 100644 --- a/search.json +++ b/search.json @@ -1 +1 @@ -[{"path":"https://www.rplumber.io/CONTRIBUTING.html","id":null,"dir":"","previous_headings":"","what":"Contributing","title":"Contributing","text":"welcome contributions plumber package! submit contribution: Fork repository make changes. Submit pull request. Ensure signed contributor license agreement. appear “Check” PR comment “CLAassistant” also appear explaining whether yet sign. sign, can click “Recheck” link comment check flip reflect ’ve signed.","code":""},{"path":"https://www.rplumber.io/PULL_REQUEST_TEMPLATE.html","id":"pull-request","dir":"","previous_headings":"","what":"Pull Request","title":"NA","text":"submit pull request, please following: Add entry NEWS concisely describing changed. Add unit tests tests/testthat directory. Run Build->Check Package RStudio IDE, devtools::check(), make sure change add messages, warnings, errors. things make easier plumber development team evaluate pull request. Even , may still decide modify code even merge . Factors may prevent us merging pull request include: breaking backward compatibility adding feature consider relevant plumber hard understand hard maintain future computationally expensive intuitive people use try responsive provide feedback case decide merge pull request.","code":""},{"path":"https://www.rplumber.io/PULL_REQUEST_TEMPLATE.html","id":"minimal-reproducible-example","dir":"","previous_headings":"","what":"Minimal reproducible example","title":"NA","text":"Finally, please include minimal reprex. goal reprex make easy possible recreate problem can fix . ’ve never heard reprex , start reading https://github.com/jennybc/reprex#---reprex, follow advice page. include session info unless ’s explicitly asked , ’ve used reprex::reprex(..., si = TRUE) hide away. Delete instructions read . Brief description original problem approach behind solution. PR task list: - [ ] Update NEWS - [ ] Add tests - [ ] Update documentation devtools::document()","code":"reprex::reprex({ library(plumber) # insert reprex here }) # insert reprex here"},{"path":"https://www.rplumber.io/articles/annotations.html","id":"annotations","dir":"Articles","previous_headings":"","what":"Annotations","title":"Annotations reference","text":"Annotations specially-structured comments used plumber file create API. full annotation line starts #* #', annotation keyword @..., number space characters followed content. recommended use #* differentiate roxygen2 annotations.","code":""},{"path":"https://www.rplumber.io/articles/annotations.html","id":"global-annotations","dir":"Articles","previous_headings":"","what":"Global annotations","title":"Annotations reference","text":"Global annotations can used anywhere plumber file. independent annotations require expression.","code":""},{"path":"https://www.rplumber.io/articles/annotations.html","id":"annotations-example","dir":"Articles","previous_headings":"Global annotations","what":"Annotations example","title":"Annotations reference","text":"","code":"#* @apiTitle Sample Pet Store App #* @apiDescription This is a sample server for a pet store. #* @apiTOS http://example.com/terms/ #* @apiContact list(name = \"API Support\", url = \"http://www.example.com/support\", email = \"support@example.com\") #* @apiLicense list(name = \"Apache 2.0\", url = \"https://www.apache.org/licenses/LICENSE-2.0.html\") #* @apiVersion 1.0.1 #* @apiTag pet Pets operations #* @apiTag toy Toys operations #* @apiTag \"toy space\" Toys operations"},{"path":"https://www.rplumber.io/articles/annotations.html","id":"equivalent-programmatic-usage","dir":"Articles","previous_headings":"Global annotations","what":"Equivalent programmatic usage","title":"Annotations reference","text":"","code":"pr() %>% pr_set_api_spec(function(spec) { spec$info <- list( title = \"Sample Pet Store App\", description = \"This is a sample server for a pet store.\", termsOfService = \"http://example.com/terms/\", contact = list(name = \"API Support\", url = \"http://www.example.com/support\", email = \"support@example.com\"), license = list(name = \"Apache 2.0\", url = \"https://www.apache.org/licenses/LICENSE-2.0.html\"), version = \"1.0.1\" ) spec$tags <- list(list(name = \"pet\", description = \"Pets operations\"), list(name = \"toy\", description = \"Toys operations\"), list(name = \"toy space\", description = \"Toys operations\")) spec })"},{"path":"https://www.rplumber.io/articles/annotations.html","id":"block-annotations","dir":"Articles","previous_headings":"","what":"Block annotations","title":"Annotations reference","text":"block annotations combination annotations create either endpoint, filter, static file handler Plumber object modifier. Block annotations always followed expression.","code":""},{"path":[]},{"path":"https://www.rplumber.io/articles/annotations.html","id":"more-details-on-param","dir":"Articles","previous_headings":"Block annotations > Endpoint","what":"More details on @param","title":"Annotations reference","text":"Types used define API inputs. can use dynamic routes. Note Plumber first look block expression set endpoint parameters names, types default value. @param annotations dynamic route/path defined parameters override Plumber guesses block expression. Query parameters currently need explicitly converted pushed (character) block expression. dynamic route parameters converted specified @param type pushed block expression. Plumber parameter type OpenAPI type reference. programmatic use, pick one asterisk.","code":""},{"path":"https://www.rplumber.io/articles/annotations.html","id":"annotations-example-1","dir":"Articles","previous_headings":"Block annotations > Endpoint > More details on @param","what":"Annotations example","title":"Annotations reference","text":"","code":"#* @get /query/parameters #* @serializer text #* @param name:str #* @param age:[int] function(name, age) { # Explicit conversion is required for query parameters age <- as.integer(age) sprintf(\"%s is %i years old\", name, max(age)) } #* @get /dyn///route #* @serializer text #* @parser none #* @response 200 A sentence function(name, age) { sprintf(\"%s is %i years old\", name, age) } #* @post /upload_file #* @serializer rds #* @parser multi #* @parser rds #* @param f:file A file #* Upload an rds file and return the object function(f) { as_attachment(f[[1]], names(f)[1]) }"},{"path":"https://www.rplumber.io/articles/annotations.html","id":"equivalent-programmatic-usage-1","dir":"Articles","previous_headings":"Block annotations > Endpoint > More details on @param","what":"Equivalent programmatic usage","title":"Annotations reference","text":"","code":"text_handler <- function(name, age) { sprintf(\"%s is %i years old\", name, max(age)) } qp_handler <- function(name, age) { age <- as.integer(age); text_handler(name, age) } file_handler <- function(file) { as_attachment(file[[1]], names(file)[1]) } pr() %>% pr_get(path = \"/query/parameters\", handler = qp_handler, serializer = serializer_text(), params = list(\"name\" = list(type = \"string\", required = FALSE, isArray = FALSE), \"age\" = list(type = \"integer\", required = FALSE, isArray = TRUE))) %>% pr_get(path = \"/dyn///route\", handler = text_handler, serializer = serializer_text(), parsers = \"none\", responses = list(\"200\" = list(description = \"A sentence\"))) %>% pr_post(path = \"/upload_file\", handler = file_handler, serializer = serializer_rds(), parsers = c(\"multi\", \"rds\"), params = list(\"file\" = list(type = \"file\", desc = \"A file\", required = FALSE, isArray = FALSE)), comments = \"Upload an rds file and return the object\")"},{"path":[]},{"path":"https://www.rplumber.io/articles/annotations.html","id":"annotations-example-2","dir":"Articles","previous_headings":"Block annotations > Filter","what":"Annotations example","title":"Annotations reference","text":"","code":"#* @filter logger function(req){ cat(as.character(Sys.time()), \"-\", req$REQUEST_METHOD, req$PATH_INFO, \"-\", req$HTTP_USER_AGENT, \"@\", req$REMOTE_ADDR, \"\\n\") plumber::forward() }"},{"path":"https://www.rplumber.io/articles/annotations.html","id":"equivalent-programmatic-usage-2","dir":"Articles","previous_headings":"Block annotations > Filter","what":"Equivalent programmatic usage","title":"Annotations reference","text":"","code":"pr() %>% pr_filter(\"logger\", function(req){ cat(as.character(Sys.time()), \"-\", req$REQUEST_METHOD, req$PATH_INFO, \"-\", req$HTTP_USER_AGENT, \"@\", req$REMOTE_ADDR, \"\\n\") plumber::forward() })"},{"path":[]},{"path":"https://www.rplumber.io/articles/annotations.html","id":"annotations-example-3","dir":"Articles","previous_headings":"Block annotations > Static File Handler","what":"Annotations example","title":"Annotations reference","text":"","code":"#* @assets ./files/static list() #* @assets ./files/static /static list() #* @assets ./files/static / list()"},{"path":"https://www.rplumber.io/articles/annotations.html","id":"equivalent-programmatic-usage-note-that-argument-order-is-reversed","dir":"Articles","previous_headings":"Block annotations > Static File Handler","what":"Equivalent programmatic usage (note that argument order is reversed)","title":"Annotations reference","text":"","code":"pr() %>% pr_static(direc = \"./files/static\") pr() %>% pr_static(\"/static\", \"./files/static\") pr() %>% pr_static(\"/\", \"./files/static\")"},{"path":[]},{"path":"https://www.rplumber.io/articles/annotations.html","id":"annotations-example-4","dir":"Articles","previous_headings":"Block annotations > Plumber Router Modifier","what":"Annotations example","title":"Annotations reference","text":"","code":"#* @plumber function(pr) { pr %>% pr_set_debug(TRUE) %>% pr_set_docs(\"swagger\") } # Named function debug_swagger <- function(pr) { pr %>% pr_set_debug(TRUE) %>% pr_set_docs(\"swagger\") } #* @plumber debug_swagger"},{"path":"https://www.rplumber.io/articles/annotations.html","id":"equivalent-programmatic-usage-3","dir":"Articles","previous_headings":"Block annotations > Plumber Router Modifier","what":"Equivalent programmatic usage","title":"Annotations reference","text":"","code":"pr() %>% pr_set_debug(TRUE) %>% pr_set_docs(\"swagger\")"},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"execution-model","dir":"Articles","previous_headings":"","what":"Execution Model","title":"Runtime","text":"plumb() file, Plumber calls source() file evaluate top-level code defined. call plumb() file, counter variable created live environment created API. However, endpoint defined evaluated invoked response incoming request. endpoint uses <<-, “double-assignment” operator, mutates counter variable previously defined file plumb()d. technique allows endpoints filters share data defined top-level API.","code":"# Global code; gets executed at plumb() time. counter <- 0 #* @get / function(){ # Only gets evaluated when this endpoint is requested. counter <<- counter + 1 }"},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"environments","dir":"Articles","previous_headings":"","what":"Environments","title":"Runtime","text":"default, create new Plumber router (happens implicitly call plumb() file), new environment created especially router. environment expressions evaluated endpoints invoked. can become important consider mounting routers onto one another. case, may expect able share state via environment, work default. ’re creating routers programmatically, can specify environment initializing Plumber router using envir parameter. environment : decorated R script, provided, source()d. expressions evaluated. endpoint filter functions executed. important aware subrouters, default, environment. want multiple Plumber routers share environment, need provide single, shared environment create routers.","code":""},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"performance-request-processing","dir":"Articles","previous_headings":"","what":"Performance & Request Processing","title":"Runtime","text":"R single-threaded programming language, meaning can one task time. still true serving APIs using Plumber, single endpoint takes two seconds generate response, every time endpoint requested, R process unable respond additional incoming requests two seconds. Incoming HTTP requests serviced order appeared, requests coming quickly can processed API, backlog requests accrue. common solutions problem either : Keep API performant. filters endpoints complete quickly long-running complicated tasks done outside API process. Run multiple R processes redundantly host single Plumber API load-balance incoming requests available processes. See hosting section details hosting environments support feature.","code":""},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"managing-state","dir":"Articles","previous_headings":"","what":"Managing State","title":"Runtime","text":"Often, Plumber APIs require coordination state. state may need shared multiple endpoints API (e.g. counter increments every time endpoint invoked). Alternatively, information needs persisted across requests single client (e.g. storing preference setting user). Lastly, might require coordinating multiple Plumber processes running independently behind load-balancer. scenarios unique properties determine solution might appropriate. previously discussed, R single-threaded. Therefore ’s important consider fact may eventually need multiple R processes running parallel handle incoming traffic API. may seem important initially, may thank later designing “horizontally scalable” API (one can scaled adding R processes parallel). key building horizontally scalable API ensure Plumber process “stateless,” meaning persistent state lives outside Plumber process. hosting environments exist today, guaranteed two subsequent requests single client served process. Thus ’s never safe assume information stored -memory available requests horizontally scaled app. options consider coordinate state Plumber API.","code":""},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"in-memory","dir":"Articles","previous_headings":"Managing State","what":"In-Memory","title":"Runtime","text":"shown previous Execution Model section, possible share state using environment associated Plumber router. one approach presented allow Plumber process stateless. approach sufficient coordinating state within single process, scale API adding processes, state longer coordinated . Therefore approach can effective “read-” data – load single, large dataset memory API starts, allow filters endpoints reference dataset moving forward – allow share state across multiple processes scale. want build scalable, stateless application, avoid relying -memory R environment coordinate state pieces API.","code":"# Global code; gets executed at plumb() time. counter <- 0 #* @get / function(){ # Only gets evaluated when this endpoint is requested. counter <<- counter + 1 }"},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"file-system","dir":"Articles","previous_headings":"Managing State","what":"File System","title":"Runtime","text":"Writing files disk often next obvious choice storing state. Plumber APIs modify data frame use write.csv() save data disk, use writeLines() append new data existing file. approaches enable R process stateless, always resilient concurrency issues. instance, ’ve horizontally scaled API five R processes two go write.csv() simultaneously, either see one process’s data get immediately overwritten ’s, – even worse – may end corrupted CSV file can’t read. Unless otherwise stated, ’s safe assume R function writes data disk resilient concurrency contention, rely filesystem coordinate shared state single R process running concurrently. ’s also important ask whether hosting platform ’ll using supports persistent storage disk. instance, Docker may insulate R process hardware allow write outside container. RStudio Connect, , provision new directory every time deploy updated version API discard data written disk point. ’re considering writing state disk long-term, sure hosting environment supports persistent -disk storage ’ve considered concurrency implications code.","code":""},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"state-cookies","dir":"Articles","previous_headings":"Managing State","what":"Cookies","title":"Runtime","text":"HTTP cookies convention allow web servers send state client expectation client include state future requests. See Setting Cookies section details leverage cookies Plumber. modern web browsers support cookies (unless configured ) many clients , well, though clients require additional configuration order . ’re confident intended clients API support cookies consider storing state cookies. approach mitigates concerns horizontal scalability, state written client independently included subsequent requests client. also minimizes infrastructure requirements hosting Plumber APIs since don’t need setup system capable storing state; instead, ’ve commissioned clients store state. One issue maintaining state cookies size kept minimum. Clients impose restrictions differently, plan store 4kB information cookie. realize whatever information gets placed cookie must retransmitted client every request. can significantly increase size HTTP request clients make. notable concern considering using cookies store state since clients responsible storing sending state, expect state tampered . Thus, may acceptable store user preferences like preferredColor=\"blue\", store authentication information like userID=1493, since user trivially change cookie another user’s ID impersonate . ’d like use cookies store information guarantees user either read modify state, see Encrypted Cookies section).","code":""},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"external-data-store","dir":"Articles","previous_headings":"Managing State","what":"External Data Store","title":"Runtime","text":"final option consider coordinating state API leveraging external data store. relational database (like MySQL Amazon RedShift), non-relational database (like MongoDB), transactional data store like Redis. One important consideration options ensure “transactional,” meaning two Plumber processes trying write time won’t overwrite one another. ’re interested pursuing option see solutions.rstudio.com/db/ look resources put together Shiny pertains dealing databases web-accessible R platform.","code":""},{"path":"https://www.rplumber.io/articles/execution-model.html","id":"exit-handlers","dir":"Articles","previous_headings":"","what":"Exit Handlers","title":"Runtime","text":"may useful define function want run API closing – instance, pool database connections need cleaned Plumber process terminated. can use exit hook define handler. interrupt API (instance pressing Escape key Ctrl+C) ’ll see Bye bye! printed console. can even register multiple exit hooks ’ll run order registered.","code":"pr(\"plumber.R\") %>% pr_hook(\"exit\", function(){ print(\"Bye bye!\") }) %>% pr_run()"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"rstudio-connect","dir":"Articles","previous_headings":"","what":"RStudio Connect","title":"Hosting","text":"RStudio Connect enterprise publishing platform RStudio. supports push-button publishing RStudio IDE variety R content types including Plumber APIs. Unlike options listed , RStudio Connect automatically manages dependent packages files API recreates environment closely mimicking local development environment server. RStudio Connect automatically manages number R processes necessary handle current load balances incoming traffic across available processes. can also shut idle processes ’re use. allows run appropriate number R processes scale capacity accommodate current load.","code":""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"digitalocean","dir":"Articles","previous_headings":"","what":"DigitalOcean","title":"Hosting","text":"DigitalOcean easy--use Cloud Computing provider. offer simple way spin Linux virtual machine access remotely. can choose size machine want run – options ranging small machines 512MB RAM dollars month large machines dozens GB RAM – pay ’s online. deploy Plumber API DigitalOcean, please check plumber companion package plumberDeploy.","code":""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"docker","dir":"Articles","previous_headings":"","what":"Docker (Basic)","title":"Hosting","text":"Docker platform built top Linux Containers allow run processes isolated environment; environment might certain resources/software pre-configured may emulate particular Linux environment like Ubuntu 14.04 CentOS 7.3. won’t delve details Docker setup install everything system. Docker provides great resources looking get started. ’ll assume Docker installed ’re familiar basic commands required spin container. article, ’ll take advantage rstudio/plumber Docker image bundles recent version R recent version plumber pre-installed (underlying R image courtesy rocker project). can get image Remember get current snapshot Plumber continue use image run pull .","code":"docker pull rstudio/plumber"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"default-dockerfile","dir":"Articles","previous_headings":"Docker (Basic)","what":"Default Dockerfile","title":"Hosting","text":"’ll start just running single Plumber application Docker just see things work. default, rstudio/plumber image take first argument image name name file want plumb() serve port 8000. right away can run one examples ’s included plumber already installed image. : docker run tells Docker run new container --rm tells Docker clean-container ’s done -p 8000:8000 says map port 8000 plumber container (’ll run server) port 8000 local machine rstudio/plumber name image want run /usr/local/lib/R/site-library/plumber/plumber/03-mean-sum/plumber.R path inside Docker container Plumber file want host. ’ll note need plumber installed host machine work, path /usr/local/... need exist host machine. references path inside docker container R file want plumb() can found. mean-sum path default path image uses don’t specify one . ask Plumber plumb run file specified port 8000 new container. used -p argument, port 8000 local machine forwarded container. can test running machine Docker running: curl localhost:8000/mean, know IP address machine Docker running, visit web browser. /mean path one ’s defined plumber file just specified – get single number array back ([-0.1993]). works, can try using one plumber files arrangement. Keep mind file want run must available inside container must specify path file exists inside container. Keep simple now – use plumber file doesn’t require additional R packages depend files outside plumber definition. instance plumber file saved current directory called api.R, use following command ’ll notice used -v argument specify “volume” mapped host machine Docker container. defined location file /plumber.R, ’s argument give last tell container look plumber definition. can use technique share whole directory instead just passing single R file; approach useful Plumber API depends files. can also use rstudio/plumber image just like use . example, want start container based image poke around bash shell: can handy way debug problems. Prepare command think work add --entrypoint /bin/bash rstudio/plumber explore bit. Alternatively, can try run R process spawn plumber application see things go wrong (often missing package missing file).","code":"docker run --rm -p 8000:8000 rstudio/plumber docker run --rm -p 8000:8000 rstudio/plumber \\ /usr/local/lib/R/site-library/plumber/plumber/04-mean-sum/plumber.R docker run --rm -p 8000:8000 -v `pwd`/api.R:/plumber.R rstudio/plumber /plumber.R docker run -it --rm --entrypoint /bin/bash rstudio/plumber"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"custom-dockerfiles","dir":"Articles","previous_headings":"Docker (Basic)","what":"Custom Dockerfiles","title":"Hosting","text":"can build upon rstudio/plumber image build Docker image writing Dockerfile. Dockerfiles vast array options possible configurations, see official docs want learn options. couple commands relevant : RUN runs command persists side-effects Docker image ’re building. want build new image broom package, add line Dockerfile says RUN R -e \"install.packages('broom')\" make broom package available new Docker image. ENTRYPOINT command run starting image. rstudio/plumber specifies entrypoint starts R, plumb()s file, run()s router. want change plumber starts, run extra commands (like add global processor) run router, ’ll need provide custom ENTRYPOINT. CMD default arguments provide ENTRYPOINT. rstudio/plumber uses first argument name file want plumb(). custom Dockerfile simple : Dockerfile just extend rstudio/plumber image two ways. First, RUNs one additional command install broom package. Second, customizes default CMD argument used running image. case, expected mount Plumber application container /app/plumber.R build custom Docker image Dockerfile using command docker build -t mycustomdocker . (. – current directory – directory Dockerfile stored). ’d able use docker run --rm -vpwd:/app mycustomdocker run custom image, passing application’s directory volume mounted /app.","code":"FROM rstudio/plumber LABEL org.opencontainers.image.authors=\"Docker User \" RUN R -e \"install.packages('broom')\" CMD [\"/app/plumber.R\"]"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"automatically-run-on-restart","dir":"Articles","previous_headings":"Docker (Basic)","what":"Automatically Run on Restart","title":"Hosting","text":"want container start automatically machine booted, can use --restart parameter docker run. docker run -p 1234:8000 -dit --restart=unless-stopped myCustomDocker run custom image created automatically every time machine boots expose plumber service port 1234 host machine, unless container explicitly stopped. Like hosting options, ’ll need make sure firewall allows connections port 1234 want others able access service.","code":""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"docker-advanced","dir":"Articles","previous_headings":"","what":"Docker (Advanced)","title":"Hosting","text":"already basic Docker instance running, may interested advanced configurations capable hosting multiple plumber applications single server even load-balancing across multiple plumber processes. order coordinate run multiple Plumber processes one server, install docker-compose system. included installations Docker, need follow instructions currently able run docker-compose command-line. Docker Compose helps orchestrate multiple Docker containers. ’re planning run one Plumber process, ’ll want use Docker Compose keep alive route traffic .","code":""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"multiple-plumber-applications","dir":"Articles","previous_headings":"Docker (Advanced)","what":"Multiple Plumber Applications","title":"Hosting","text":"’ll use Docker Compose help us organize multiple Plumber processes. won’t go detail use Docker Compose, ’re new familiarize using official docs. define Docker Compose configuration defines behavior every Plumber application want run. ’ll first want setup Dockerfile defines desired behavior applications (outlined previously. use docker-compose.yml configuration like following: detail options options exist can found . configuration defines two Docker containers run app1 app2. associated files case laid disk follows: can see app2 simpler two apps; just plumber definition run plumb(). merely use default plumber Docker image image, customize command specify Plumber API definition can found container. Since ’re mapping host’s ./app2 /app inside container, definition found /app/plumber.R. specify always restart anything ever happens container, export port 8000 container port 7001 host. app1 complicated app. extra data another directory needs loaded, custom Dockerfile. additional R packages system dependencies requires. now run docker-compose , Docker Compose build referenced images config file run . ’ll find app1 available port 7000 machine running Docker Compose, app2 available port 7001. want APIs run background survive restarts server, can use -d switch just like docker run.","code":"services: app1: build: ./app1/ volumes: - ./data:/data - ./app1:/app restart: always ports: - \"7000:8000\" app2: image: rstudio/plumber command: /app/plumber.R volumes: - ../app2:/app restart: always ports: - \"7001:8000\" docker-compose.yml app1 ├── Dockerfile ├── api.R app2 ├── plumber.R data ├── data.csv"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"multiple-applications-on-one-port","dir":"Articles","previous_headings":"Docker (Advanced)","what":"Multiple Applications on One Port","title":"Hosting","text":"may desirable run Plumber services standard port like 80 (HTTP) 443 (HTTPS). case, ’d prefer router running port 80 can send traffic appropriate Plumber API distinguishing based path prefix. Requests myserver.com/app1/ sent app1 container, myserver.org/app2/ target app2 container, paths available port 80 server. order , can use another Docker container running nginx configured route traffic two Plumber containers. ’d add following entry docker-compose.yml app containers already defined. uses nginx Docker image downloaded . order run nginx meaningful way, provide configuration file place /etc/nginx/nginx.conf, mounting local file location container. basic nginx config file look something like following: set server_name parameter whatever public address server. can save file nginx.conf directory Compose config file. Docker Compose intelligent enough know route traffic http://app1:8000/ app1 container, port 8000, can leverage config file. Docker containers able contact non-public ports, can go directly port 8000 containers. proxy configuration trim prefix request sends applications, applications don’t need know anything hosted publicly URL includes /app1/ /app2/ prefixes. also get rid previous port mappings ports 7000 7001 applications, don’t want expose APIs ports anymore. now run docker compose , ’ll see two application servers running now new nginx server running, well. ’ll find visit server port 80, ’ll see “welcome Nginx!” page. access /app1 ’ll sent app1 just like hoped.","code":"nginx: image: nginx:1.9 ports: - \"80:80\" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro restart: always depends_on: - app1 - app2 events { worker_connections 4096; ## Default: 1024 } http { default_type application/octet-stream; sendfile on; tcp_nopush on; server_names_hash_bucket_size 128; # this seems to be required for some vhosts server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.html index.htm; server_name MYSERVER.ORG; location /app1/ { proxy_pass http://app1:8000/; proxy_set_header Host $host; } location /app2/ { proxy_pass http://app2:8000/; proxy_set_header Host $host; } location ~ /\\.ht { deny all; } } }"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"load-balancing","dir":"Articles","previous_headings":"Docker (Advanced)","what":"Load Balancing","title":"Hosting","text":"’re expecting lot traffic one application API ’s particularly computationally complex, may want distribute load across multiple R processes running Plumber application. Thankfully, can use Docker Compose , well. First, ’ll want create multiple instances application. easily accomplished docker-compose scale command. simply run docker-compose scale app1=3 run three instances app1. Now just need load balance traffic across three instances. setup nginx configuration already balance traffic across pool workers, need manually re-configure update nginx instance every time need scale number , might hassle. Luckily, ’s elegant solution. can use dockercloud/haproxy Docker image automatically balance HTTP traffic across pool workers. image intelligent enough listen workers pool arriving leaving automatically remove/add containers pool. Let’s add new container configuration defines load balancer trick allows image listen scaling app1 passing docker socket shared volume. Note particular arrangement differ based host OS. configuration intended Linux, MacOS X users require slightly different config. export port 80 new load balancer port 80 host machine solely wanted load-balance single application. Alternatively, can actually use nginx (handle routing various applications) HAProxy (handle load balancing particular application). , ’d merely add new location block nginx.conf file knows send traffic HAProxy, modify existing location block send traffic load balancer instead going directly application. location /app1/ block becomes: lb name HAProxy load balancer defined Compose configuration. next time start/redeploy Docker Compose cluster, ’ll balancing incoming requests /app1/ across pool 1 R processes based whatever ’ve set scale application. keep mind using load-balancing ’s longer guaranteed subsequent requests particular application land process. means maintain state Plumber application (like global counter, user’s session state), can’t expect shared across processes user might encounter. least three possible solutions problem: Use robust means maintaining state. put state database, instance, lives outside R processes Plumber processes get save state externally. serialize state user using (encrypted) session cookies, assuming ’s small enough. scenario, workers write data back user form cookie, user include cookie subsequent requests. works best state going set rarely read often (instance, cookie set user logs , read request detect identity user). can enable “sticky sessions” HAProxy load balancer. ensure user’s traffic always gets routed worker. downside approach distribute traffic less evenly. end situation 2 R processes application 90% traffic hitting one happens users triggering majority requests “stuck” one particular worker.","code":"lb: image: 'dockercloud/haproxy:1.2.1' links: - app1 volumes: - /var/run/docker.sock:/var/run/docker.sock location /app1/ { proxy_pass http://lb/; proxy_set_header Host $host; }"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"pm2","dir":"Articles","previous_headings":"","what":"pm2","title":"Hosting","text":"don’t luxury running Plumber instance designated server (discussed DigitalOcean section) ’re comfortable hosting API Docker, ’ll need find way run manage Plumber APIs server directly. variety tools built help manage web hosting single-threaded environment like R. compelling tools developed around Ruby (like Phusion Passenger) Node.js (like Node Supervisor, forever pm2). Thankfully, many tools can adapted support managing R process running Plumber API. pm2 process manager initially targeting Node.js. ’ll show commands needed Ubuntu 14.04, can use Operating System distribution supported pm2. end, ’ll server automatically starts plumber services booted, restarts ever crash, even centralizes logs plumber services.","code":""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"server-deployment-and-preparation","dir":"Articles","previous_headings":"pm2","what":"Server Deployment and Preparation","title":"Hosting","text":"first thing ’ll need , regardless process manager choose, deploy R files containing plumber applications server ’ll hosted. Keep mind ’ll also need include supplemental R files source()d plumber file, datasets dependencies files . ’ll also need make sure R packages need (appropriate versions) available remote server. can either manually installing packages can consider using tool like Packrat help . myriad features pm2 won’t cover . good idea spend time reading documentation see features might interest ensure understand implications pm2 hosts services (user want run processes , etc.). quick-start guide may especially relevant. sake simplicity, basic installation without customizing many options.","code":""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"install-pm2","dir":"Articles","previous_headings":"pm2","what":"Install pm2","title":"Hosting","text":"Now ’re ready install pm2. pm2 package ’s maintained npm (Node.js’s package management system); also requires Node.js order run. start ’ll want install Node.js. Ubuntu 14.04, necessary commands : npm Node.js installed, ’re ready install pm2. find errors like SSL Error: CERT_UNTRUSTED using npm command, can bypass ssl error using: set registry URL https:// http://: install pm2 globally (-g) server, meaning now able run pm2 --version get version number pm2 ’ve installed. order get pm2 startup services boot, run sudo pm2 startup create necessary files system run pm2 boot machine.","code":"sudo apt-get update sudo apt-get install nodejs npm sudo npm install -g pm2 npm config set strict-ssl false npm config set registry=\"http://registry.npmjs.org/\""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"wrap-your-plumber-file","dir":"Articles","previous_headings":"pm2","what":"Wrap Your Plumber File","title":"Hosting","text":"’ve deployed Plumber files onto server, ’ll still need tell server run server. ’re probably used running commands like Unfortunately, pm2 doesn’t understand R scripts natively; however, possible specify custom interpreter. can use feature launch R-based wrapper plumber file using Rscript scripting front-end comes R. following script run two commands listed . Save R script server something like run-myfile.R. also make executable changing permissions file using command like chmod 755 run-myfile.R. now execute file make sure runs service like expect. able make requests server appropriate port plumber service respond. can kill process using Ctrl-c ’re convinced ’s working. Make sure shell script permanent location won’t erased modified accidentally. can consider creating designated directory plumber services directory like /usr/local/plumber, put services associated Rscript-runners subdirectory like /usr/local/plumber/myfile/.","code":"pr(\"myfile.R\") %>% pr_run(port=4500) #!/usr/bin/env Rscript library(plumber) pr(\"myfile.R\") %>% pr_run(port=4000, host=\"0.0.0.0\") # Setting the host option on a VM instance ensures the application can be accessed externally. # (This may be only true for Linux users.)"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"introduce-our-service-to-pm2","dir":"Articles","previous_headings":"pm2","what":"Introduce Our Service to pm2","title":"Hosting","text":"’ll now need teach pm2 Plumber API can put work. can register configure number services pm2; let’s start myfile Plumber service. can use pm2 list command see services pm2 already running. run command now, ’ll see pm2 doesn’t services ’s charge . scripts code stored directory want , use following command tell pm2 service. see output pm2 starting instance service, followed status information pm2. everything worked properly, ’ll see new service registered running. can see output executing pm2 list . ’re happy pm2 services defined, can use pm2 save tell pm2 retain set services running next time boot machine. services defined automatically restarted . point, persistent pm2 service created Plumber application. means can reboot server, find kill underlying R process plumber application using pm2 automatically bring new process replace . help guarantee always Plumber process running port number specified shell script. good idea reboot server ensure everything comes back way expected. can repeat process plumber applications want deploy, long give unique port run . Remember can’t one service running single port. sure pm2 save every time add services want survive restart. Run netstat -tulpn see application ran. see application host 127.0.0.0 127.0.0.1, application accessed externally. change host parameter 0.0.0.0, example: `pr_run(host = “0.0.0.0”).","code":"pm2 start --interpreter=\"Rscript\" /usr/local/plumber/myfile/run-myfile.R"},{"path":"https://www.rplumber.io/articles/hosting.html","id":"logs-and-management","dir":"Articles","previous_headings":"pm2","what":"Logs and Management","title":"Hosting","text":"Now applications defined pm2, may want drill manage debug . want see information, use pm2 show command specify name application pm2 list. usually name shell script specified, may something like pm2 show run-myfile. can peruse information keep eye restarts count applications. application restart many times, implies process crashing often, sign ’s problem code. Thankfully, pm2 automatically manages log files underlying processes. ever need check log files service, can just run pm2 logs run-myfile, myfile name service obtained pm2 list. command show last lines logged process, begin streaming incoming log lines exit (Ctrl-c). want big-picture view health server pm2 services, can run pm2 monit show dashboard RAM CPU usage services.","code":""},{"path":"https://www.rplumber.io/articles/hosting.html","id":"systemd","dir":"Articles","previous_headings":"","what":"systemd","title":"Hosting","text":"systemd service manager used certain Linux distributions including RedHat/CentOS 7, SUSE 12, Ubuntu versions 16.04 later. use Linux server can use systemd run Plumber service can accessed local network even outside network depending firewall rules. option similar using Docker method. One main advantages using systemd using Docker systemd won’t bypass firewall rules (Docker !) avoids overhead running container. Compared plumber::do_provision() option won’t create new droplet use DigitalOcean; run existing droplet instead. implement option ’ll complete following three steps terminal: Verify plumber package available globally server: Run sudo nano /etc/systemd/system/plumber-api.service, paste adapt content: Activate service (auto-start power/reboot) start : check API running, type systemctl | grep running terminal display plumber-api.service \\ loaded active running Plumber API.","code":"R -e 'install.packages(\"plumber\", repos = \"https://cran.rstudio.com/\")' [Unit] Description=Plumber API # After=postgresql # (or mariadb, mysql, etc if you use a DB with Plumber, otherwise leave this commented) [Service] ExecStart=/usr/bin/Rscript -e \"library(plumber); pr('/your-dir/your-api-script.R') %>% pr_run(port=8080, host='0.0.0.0')\" Restart=on-abnormal WorkingDirectory=/your-dir/ [Install] WantedBy=multi-user.target sudo systemctl enable plumber-api # automatically start the service when the server boots sudo systemctl start plumber-api # start the service right now"},{"path":"https://www.rplumber.io/articles/introduction.html","id":"web-apis","dir":"Articles","previous_headings":"","what":"Web APIs","title":"Introduction","text":"Hypertext Transfer Protocol (HTTP) dominant medium information exchanged Internet. Application Programming Interface (API) broad term defines rules guide interaction software. case HTTP APIs, defined set endpoints accept particular inputs. Plumber translates annotations place functions HTTP API can called machines network. execute Plumber API public server, can even make API available public Internet. HTTP APIs become predominant language software communicates. creating HTTP API, ’ll empower R code leveraged services – whether ’re housed inside organization hosted side world. just ideas doors opened wrap R code Plumber API: Software written languages organization can run R code. company’s Java application now pull custom ggplot2 graph generate -demand, Python client query predictive model defined R. can third-party receive emails behalf notify Plumber service new messages arrive. register “Slash Command” Slack, enabling execute R function response command entered Slack. can write JavaScript code queries Plumber API visitor’s web browser. Even , use Plumber exclusively back-end interactive web application.","code":""},{"path":"https://www.rplumber.io/articles/migration.html","id":"plumber-0-4-0-migration-guide","dir":"Articles","previous_headings":"","what":"Plumber 0.4.0 Migration Guide","title":"Migration Guide","text":"Plumber underwent series breaking changes part 0.4.0 release. changes made attempt rectify earlier mistakes attempt take care foreseeable breaking changes Plumber package. number changes users consider preparing upgrade plumber 0.4.0. Plumber longer accepts external connections default. host parameter run() method now defaults 127.0.0.1, meaning Plumber listen incoming requests local machine ’s running – machine network. done security reasons don’t accidentally expose Plumber API ’re developing entire network. restore old behavior Plumber listened connections machine network, use $run(host=\"0.0.0.0\"). Note ’re deploying environment includes HTTP proxy (DigitalOcean servers use nginx), Plumber listen 127.0.0.1 likely right default, proxy – Plumber – one receiving external connections. Plumber longer sets Access-Control-Allow-Origin HTTP header *. previously done convenience given security implications ’re reversing decision. previous behavior allowed web browsers make requests API domains using JavaScript request used standard HTTP headers GET, HEAD, POST request. requests longer work default. wish allow endpoint accessible origins web browser, can use res$setHeader(\"Access-Control-Allow-Origin\", \"*\") endpoint filter. Rather setting default port 8000, port now randomly selected. ensures shared server (like RStudio Server) able support multiple people developing Plumber APIs concurrently without manually identify available port. can controlled specifying port parameter run() method setting plumber.port option. object-oriented model Plumber routers changed. ’re calling following methods Plumber router, need modify code use newer alternatives: addFilter, addEndpoint, addGlobalProcessor, addAssets. code around functions undergone major rewrite breaking changes introduced. four functions still supported deprecation warning 0.4.0, support best-effort. Certain parameters methods longer supported, thoroughly test Plumber API leverages methods deploying version 0.4.0. Updated documentation using Plumber programmatically now available.","code":""},{"path":"https://www.rplumber.io/articles/programmatic-usage.html","id":"creating-and-controlling-a-router","dir":"Articles","previous_headings":"","what":"Creating and Controlling a Router","title":"Programmatic Usage","text":"centerpiece Plumber router. Plumber routers responsible coordinating incoming requests httpuv, dispatching requests appropriate filters/endpoints, serializing response, handling errors might pop along way. ’ve using annotations define Plumber APIs, ’ve already worked Plumber routers ’s plumb() command produces. instantiate new Plumber router programmatically, can call pr(). return blank Plumber router endpoints. call pr_run() returned object start API, doesn’t know respond requests incoming traffic get 404 response. ’ll see momentarily add endpoints filters onto empty router. Alternatively, can pass file contains annotation-based Plumber API first parameter create router much like plumb(). aware Plumber routers come handful filters pre-configured. built-filters used things like process properties incoming request like cookies, POST body, query string. can specify filters want new router overriding filters parameter creating new router.","code":""},{"path":"https://www.rplumber.io/articles/programmatic-usage.html","id":"defining-endpoints","dir":"Articles","previous_headings":"","what":"Defining Endpoints","title":"Programmatic Usage","text":"can define endpoints router using pr_handle(), pr_get(), pr_post(). instance, define Plumber API response GET requests / POST requests /submit, use following code: “handler” functions define calls identical code defined plumber.R file using annotations define API. route methods take additional arguments allow control nuanced behavior endpoint like filter might preempt serializer use. instance, following endpoint use Plumber’s HTML serializer.","code":"pr() %>% pr_get(\"/\", function(req, res){ # ... }) %>% pr_post(\"/submit\", function(req, res){ # ... }) pr() %>% pr_get(\"/\", function(){ \"

    Programmatic Plumber!<\/h1><\/html>\" }, serializer = plumber::serializer_html())"},{"path":"https://www.rplumber.io/articles/programmatic-usage.html","id":"defining-filters","dir":"Articles","previous_headings":"","what":"Defining Filters","title":"Programmatic Usage","text":"Use filter() method Plumber router define new filter: can specify options serializer use filter returns value pr_filter() method, well.","code":"pr() %>% pr_filter(\"myFilter\", function(req){ req$filtered <- TRUE forward() }) %>% pr_get(\"/\", function(req){ paste(\"Am I filtered?\", req$filtered) })"},{"path":"https://www.rplumber.io/articles/programmatic-usage.html","id":"router-hooks","dir":"Articles","previous_headings":"","what":"Registering Hooks on a Router","title":"Programmatic Usage","text":"Plumber routers support notion “hooks” can registered execute code particular point lifecycle request. Plumber routers currently support four hooks: preroute(data, req, res) postroute(data, req, res, value) preserialize(data, req, res, value) postserialize(data, req, res, value) access disposable environment data parameter created temporary data store request. Hooks can store temporary data hooks can reused hooks processing request. One feature defining hooks Plumber routers ability modify returned value. convention hooks : function accepts parameter named value expected return new value. unmodified version value passed , mutated value. either case, hook accepts parameter named value, whatever hook returns used new value response. can add hooks using pr_hook method, can add multiple hooks using pr_hooks method takes name list names names hooks, values handlers . Making GET request / print various information three events registered hooks.","code":"pr() %>% pr_hook(\"preroute\", function(req) { cat(\"Routing a request for\", req$PATH_INFO, \"...\\n\") }) %>% pr_hooks(list( preserialize = function(req, value) { print(\"About to serialize this value:\") print(value) # Must return the value since we took one in. Here we're not choosing # to mutate it, but we could. value }, postserialize = function(res) { print(\"We serialized the value as:\") print(res$body) } )) %>% pr_get(\"/\", function(){ 123 })"},{"path":"https://www.rplumber.io/articles/programmatic-usage.html","id":"mount-static","dir":"Articles","previous_headings":"","what":"Mounting & Static File Routers","title":"Programmatic Usage","text":"Plumber routers can “nested” mounting one another using mount() method. allows compartmentalize API paths great technique decomposing large APIs smaller files. approach used defining routers serve directory static files. Static file routers just special case Plumber routers created using pr_static(). example make files directories stored ./myfiles directory available API /assets/ path.","code":"root <- pr() users <- pr(\"users.R\") products <- pr(\"products.R\") root %>% pr_mount(\"/users\", users) %>% pr_mount(\"/products\", products) root pr() %>% pr_static(\"/assets\", \"./myfiles\") %>% pr_run()"},{"path":"https://www.rplumber.io/articles/programmatic-usage.html","id":"customize-router","dir":"Articles","previous_headings":"","what":"Customizing a Router","title":"Programmatic Usage","text":"handful useful methods aware modify behavior router. Using hooks alter request processing already discussed, additionally can modify router’s behavior using following: pr_set_serializer() - Sets default serializer router. pr_set_error() - Sets error handler gets invoked filter endpoint generates error. pr_set_404() - Sets handler gets called incoming request can’t served filter, endpoint, sub-router.","code":""},{"path":"https://www.rplumber.io/articles/quickstart.html","id":"specifying-the-inputs","dir":"Articles","previous_headings":"","what":"Specifying the Inputs","title":"Quickstart","text":"may noticed functions define endpoints accept parameters. parameters allow us customize behavior endpoints. One ways using “query strings” way passing parameters HTTP API. visit http://localhost:8000/plot?spec=setosa, see similar graph one saw , now dataset filtered include “setosa” species iris dataset. might guessed, spec=setosa portion URL sets spec parameter setosa. details Plumber processes inputs available Routing & Input article.","code":""},{"path":"https://www.rplumber.io/articles/quickstart.html","id":"customizing-the-output","dir":"Articles","previous_headings":"","what":"Customizing The Output","title":"Quickstart","text":"previous example, saw one endpoint rendered JSON one produced image. Unless instructed otherwise, Plumber attempt render whatever endpoint function returns JSON. However, can specify alternative “serializers” instruct Plumber render output format HTML (@serializer html), PNG (@serializer png), JPEG (@serializer jpeg). endpoint produce something like following, visited. also sets appropriate Content-Type header browser visits page know render result HTML. can even provide custom serializers define translate R object produced endpoint bits produce Plumber’s HTTP response. can find details Rendering & Output article.","code":"#* @get /hello #* @serializer html function(){ \"

    hello world<\/h1><\/html>\" }

    hello world<\/h1><\/html>"},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"response-object","dir":"Articles","previous_headings":"","what":"The Response Object","title":"Rendering Output","text":"plumber response object stored environment, much like request object. response object, accessible res within plumber functions, contains following objects: response object also contains following methods can invoked: methods (clone, initialize, serializer) directly invoked response object.","code":""},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"serializers","dir":"Articles","previous_headings":"","what":"Serializers","title":"Rendering Output","text":"order send response R API client, object must “serialized” format client can understand. JavaScript Object Notation (JSON) one standard commonly used web APIs. JSON serialization translates R objects like list(=123, b=\"hi!\") JSON text resembling {: 123, b: \"hi!\"}. JSON appropriate every situation, however. want API render HTML page might viewed browser, instance, need different serializer. Likewise, want return image rendered R, likely want use standard image format like PNG JPEG rather JSON. default, Plumber serializes objects JSON via jsonlite R package. However, variety serializers built package. can also pass arguments certain serializers modify behavior like example . See Serialization article details.","code":"#* @serializer json list(na=\"string\")"},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"boxed-vs-unboxed-json","dir":"Articles","previous_headings":"Serializers","what":"Boxed vs Unboxed JSON","title":"Rendering Output","text":"may noticed API responses generated Plumber render singular values (“scalars”) arrays. instance: value element, though ’s singular, still rendered array. may surprise initially, done keep output consistent. JSON differentiates scalar vector objects, R . creates ambiguity serializing R object JSON since unclear whether particular element rendered atomic value JSON array. Consider following API returns letters lexicographically “higher” given letter. example API , instance, produces scalar, instances produces vector. Visiting http://localhost:8000/boxed?letter=U http://localhost:8000/unboxed?letter=U return identical responses: However, http://localhost:8000/boxed?letter=Y produce: http://localhost:8000/unboxed?letter=Y produce: /boxed endpoint, name implies, produces “boxed” JSON output length-1 vectors still rendered array. Conversely, /unboxed endpoint sets auto_unbox=TRUE call jsonlite::toJSON, causing length-1 R vectors rendered JSON scalars. R doesn’t distinguish scalars vectors, API clients may respond differently encountering JSON array versus atomic value. may find API clients respond gracefully object expected vector becomes scalar one call. reason, Plumber inherits jsonlite::toJSON default setting auto_unbox=FALSE result length-1 vectors still rendered JSON arrays. can configure endpoint use unboxedJSON serializer (shown ) want alter behavior particular endpoint. couple functions aware around feature set. using boxed JSON serialization, jsonlite::unbox() can used force length-1 object R presented JSON scalar. using unboxed JSON serialization, () cause length-1 R object present JSON array.","code":"jsonlite::toJSON(list(a=5)) #> {\"a\":[5]} #* Get letters after a given letter #* @get /boxed function(letter=\"A\"){ LETTERS[LETTERS > letter] } #* Get letters after a given letter #* @serializer unboxedJSON #* @get /unboxed function(letter=\"A\"){ LETTERS[LETTERS > letter] } [\"V\", \"W\", \"X\", \"Y\", \"Z\"] [\"Z\"] \"Z\""},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"customizing-image-serializers","dir":"Articles","previous_headings":"Serializers","what":"Customizing Image Serializers","title":"Rendering Output","text":"@serializer jpeg @serializer png annotations cause graphical output endpoint written file returned client using jpeg() png() functions, respectively. functions accept variety additional options customize output including width, height, bg among others. version 0.4.3 plumber, annotations now accept additional arguments passed functions. enables creation endpoints like: lower level, arguments inside parentheses used arguments list() call. Meaning R code can prefixed list form valid R expression can used. example, #' @serializer png (width=2^10 + 1) valid annotation. code evaluated API plumb()d. approach can used statically define size image, work dynamic sizing image. wish dynamically size images, need render capture graphical output return contents appropriate Content-Type header. See existing image renderers model .","code":"#* Example of customizing graphical output #* @serializer png list(width = 400, height = 500) #* @get / function(){ plot(1:10) }"},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"bypassing-serialization","dir":"Articles","previous_headings":"Serializers","what":"Bypassing Serialization","title":"Rendering Output","text":"instances may desirable return value directly R without serialization. can bypass serialization returning response object endpoint. example, consider following API. response returned endpoint contain body Literal text ! Content-Type header without additional serialization. Similarly, can leverage @serializer contentType annotation serialization response specifies contentType header. can use annotation want control response send. Running API visiting http://localhost:8000/pdf download PDF generated R (display PDF natively, client supports ).","code":"#* Endpoint that bypasses serialization #* @get / function(res){ res$body <- \"Literal text here!\" res } #* @serializer contentType list(type=\"application/pdf\") #* @get /pdf function(){ tmp <- tempfile() pdf(tmp) plot(1:10, type=\"b\") text(4, 8, \"PDF from plumber!\") text(6, 2, paste(\"The time is\", Sys.time())) dev.off() readBin(tmp, \"raw\", n=file.info(tmp)$size) }"},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"error-handling","dir":"Articles","previous_headings":"","what":"Error Handling","title":"Rendering Output","text":"Plumber wraps endpoint invocation can gracefully capture errors. run API visit http://localhost:8000/simple, ’ll notice two things: HTTP response status code 500 (“internal server error”) sent client. see error message resembling: {\"error\":[\"500 - Internal server error\"],\"message\":[\"Error (function () : error!\\n\"]} similar error printed terminal ’re running Plumber API. means possible intentionally stop() endpoint filter way communicate problem user. However, may preferable render errors API consistent format helpful error messages. custom error handler can set using setErrorHandler() method: run API visit http://localhost:8000/simple, ’ll notice custom error message provided error handler included browser. Since didn’t anything actual error message, nothing printed console. wanted include error console, following: function passed setErrorHandler invoked anytime R execution fails error.","code":"#* Example of throwing an error #* @get /simple function(){ stop(\"I'm an error!\") } #* Generate a friendly error #* @get /friendly function(res){ msg <- \"Your request did not include a required parameter.\" res$status <- 400 # Bad request list(error=jsonlite::unbox(msg)) } { \"error\": \"Your request did not include a required parameter.\" } pr() %>% pr_get(\"/simple\", function() stop(\"I'm an error!\")) %>% pr_set_error(function(req, res, err){ res$status <- 500 list(error = \"An error occurred. Please contact your administrator.\") }) %>% pr_run() pr() %>% pr_get(\"/simple\", function() stop(\"I'm an error!\")) %>% pr_set_error(function(req, res, err){ print(err) res$status <- 500 list(error = \"An error occurred. Please contact your administrator.\") }) %>% pr_run()"},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"setting-cookies","dir":"Articles","previous_headings":"","what":"Setting Cookies","title":"Rendering Output","text":"part fulfilling request, Plumber API can choose set HTTP cookies client. HTTP APIs don’t implicitly contain notion “session.” Without additional information, Plumber way ascertaining whether two HTTP requests come associated user. Cookies offer way commission client store state behalf selected data can outlive single HTTP request; full implications using cookies track state API discussed . two forms Plumber cookies – plain-text encrypted – discussed following sections. make cookies important part API’s security model, sure understand section security considerations working cookies.","code":""},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"setting-unencrypted-cookies","dir":"Articles","previous_headings":"Setting Cookies","what":"Setting Unencrypted Cookies","title":"Rendering Output","text":"Plumber can set receive plaint-text cookies. API endpoint return random letter, remembers preferences whether like capitalized lower-case letters. Since API using PUT request test API, ’ll use curl command line test . (’s nothing cookies necessitates PUT requests; just easily modify API use GET request.) can start visiting /letter endpoint ’ll see API defaults lower-case alphabet. curl http://localhost:8000/letter send PUT request specify capital parameter, cookie set client allow server accommodate preference future requests. curl, need specify file want save cookies using -c option. good reminder clients handle cookies differently – won’t support – sure clients intend support API play nicely cookies want use . send PUT request, setting parameter capital 1, invoke: curl -c cookies.txt -X PUT --data 'capital=1' \"http://localhost:8000/preferences\". print cookies.txt file, now see contains single cookie called capitalize value 1. can make another GET request /letter see accommodates preferences. ’ll need tell curl use cookies file just created sending request using -b switch: curl -b cookies.txt http://localhost:8000/letter. now see API returning random capitalized letter. setCookie method accepts variety additional options customize cookie handled client. default, cookies set session lifetime, meaning cookie persist user’s browser client closes tab point cookie deleted. can customize setting expiration parameter setCookie using either number seconds future cookie expire. Alternatively, can provide object class POSIXt, case interpreted time cookie expire. options can set cookie include path (path domain cookie installed client); http (controls whether cookie accessible JavaScript running domain – TRUE means cookie HTTP-, accessible JavaScript); secure (TRUE, instructs browser send cookie HTTPS, insecure HTTP. ’re using cookies infer security-sensitive properties (identify user, determine resources client access ), sure see Security article – particular section security implications cookies.","code":"#* @put /preferences function(res, capital){ if (missing(capital)){ stop(\"You must specify a value for the 'capital' preference.\") } res$setCookie(\"capitalize\", capital) } #* @get /letter function(req) { capitalize <- req$cookies$capitalize # Default to lower-case unless user preference is capitalized alphabet <- letters # The capitalize cookie will initially be empty (NULL) if (!is.null(capitalize) && capitalize == \"1\"){ alphabet <- LETTERS } list( letter = sample(alphabet, 1) ) } { \"letter\": [\"o\"] }"},{"path":"https://www.rplumber.io/articles/rendering-output.html","id":"encrypted-cookies","dir":"Articles","previous_headings":"Setting Cookies","what":"Setting Encrypted Cookies","title":"Rendering Output","text":"addition storing plain-text cookies, Plumber also supports handling cookies encrypted. Encrypted cookies prevent users seeing stored inside also sign contents users can’t modify stored. use feature, must explicitly add router constructing . example, run following sequence commands create router supports encrypted session cookies. ’ll notice example using session_cookie hooks come Plumber. adding registering hooks router, ’ll ensure req$session object made available incoming requests persisted cookie named cookieName response ready sent user. example, key used encrypt data \"mySecretHere\", obviously weak secret key. Unlike res$setHeader(), values attached req$session serialized via jsonlite; ’re free use complex data structures like lists session. Also unlike res$setHeaders(), req$session encrypts data using secret key provide first argument session_cookie() function. example, ’ll store encrypted cookie counts many times client visited particular endpoint: , need register session_cookie() hooks router code work. inspect cookie set browser, ’ll find value encrypted time gets client. time arrives Plumber, cookie available regular R list can read modified.","code":"pr(\"myfile.R\") %>% pr_cookie(\"mySecretHere\", \"cookieName\") %>% pr_run() #* @get /sessionCounter function(req){ count <- 0 if (!is.null(req$session$counter)){ count <- as.numeric(req$session$counter) } req$session$counter <- count + 1 return(paste0(\"This is visit #\", count)) }"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"endpoints","dir":"Articles","previous_headings":"","what":"Endpoints","title":"Routing & Input","text":"Endpoints terminal step process serving request. endpoint can simply viewed logic ultimately responsible generating response particular request. request checked available endpoint finds endpoint willing serve point stops looking; .e. request ever processed one endpoint. create endpoint annotating function like : annotation specifies function responsible generating response GET request /hello. value returned function used response request (run serializer e.g. convert response JSON). case, GET response /hello return content [\"hello world\"] JSON Content-Type. annotations generate endpoint include: @get @post @put @delete @head map HTTP methods API client might send along request. default open page web browser, sends GET request API. can use API clients (even JavaScript inside web browser) form HTTP requests using methods listed . conventions around methods used can read . Note conventions carry security implications, ’s good idea follow recommended uses method fully understand might deviate . Note single endpoint can support multiple verbs. following function used service incoming GET, POST, PUT request /cars.","code":"#* Return \"hello world\" #* @get /hello function(){ \"hello world\" } #* @get /cars #* @post /cars #* @put /cars function(){ ... }"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"filters","dir":"Articles","previous_headings":"","what":"Filters","title":"Routing & Input","text":"Plumber filters can used define “pipeline” handling incoming requests. allows API authors break complex logic sequence independent, understandable steps. Unlike endpoints, request may go multiple Plumber filters response generated. Typically, Plumber router pass request defined filters attempts find endpoint satisfy request. However, endpoints can “preempt” particular filters want considered execution filter(s) registered router. Filters can one three things handling request: Forward control onto next handler, potentially mutating request. Return response forward subsequent handlers Throw error three options, might desired, discussed .","code":""},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"forward-to-another-handler","dir":"Articles","previous_headings":"Filters","what":"Forward to Another Handler","title":"Routing & Input","text":"common behavior filter pass request next handler mutating incoming request invoking external side-effect. One common use case use filter request logger: filter straightforward: invokes external action (logging) calls forward() pass control next handler pipeline (another filter endpoint). req res parameters Plumber based R environments, exhibit “pass--reference” behavior. means changes made one filter req res object visible filters endpoints also touching request response. similar filter may mutate state request response object ’s given. case, req object going extended additional property named username represents value looked cookie. req$username property available subsequent filters endpoints processing request. (Note example secure system authentication; see section using cookies store state longer discussion .) modified request object, passes control next handler using forward().","code":"#* Log some information about the incoming request #* @filter logger function(req){ cat(as.character(Sys.time()), \"-\", req$REQUEST_METHOD, req$PATH_INFO, \"-\", req$HTTP_USER_AGENT, \"@\", req$REMOTE_ADDR, \"\\n\") plumber::forward() } #* @filter setuser function(req){ un <- req$cookies$user # Make req$username available to endpoints req$username <- un plumber::forward() }"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"return-a-response","dir":"Articles","previous_headings":"Filters","what":"Return a Response","title":"Routing & Input","text":"also possible filters return response. may want check request satisfies constraint (like authentication) – certain cases – return response without invoking additional handlers. example, filter used check user authenticated. common cause errors Plumber APIs forgetting invoke forward() filters. filter, result last line silently returned response incoming request. can cause API exhibit odd behavior depending ’s returned. ’re using filters, sure carefully audit code paths ensure ’re either calling forward(), causing error, intentionally returning value.","code":"#* @filter checkAuth function(req, res){ if (is.null(req$username)){ res$status <- 401 # Unauthorized return(list(error=\"Authentication required\")) } else { plumber::forward() } }"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"throw-an-error","dir":"Articles","previous_headings":"Filters","what":"Throw an Error","title":"Routing & Input","text":"Finally, filter can throw error. can occur mistake made code defining filter filter intentionally invokes stop() trigger error. case, request processed subsequent handlers immediately sent router’s error handler. See router customization details customize error handler.","code":""},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"dynamic-routes","dir":"Articles","previous_headings":"","what":"Dynamic Routes","title":"Routing & Input","text":"addition hard-coded routes like /hello, Plumber endpoints can dynamic routes. Dynamic routes allow endpoints define flexible set paths match. common REST convention include identifier object API paths associated . lookup information user #13, might make GET request path /users/13. Rather register routes every user API might possibly encounter, can use dynamic route associate endpoint variety paths. API uses dynamic path /users/ match request form /users/ followed path element like number letters. case, return information user user associated ID found, empty object . can name dynamic path elements however ’d like, note name used dynamic path must match name parameter function (case, id). can even complex dynamic routes like: hard-coded dynamic examples given , parameters provided function character string.","code":"users <- data.frame( uid=c(12,13), username=c(\"kim\", \"john\") ) #* Lookup a user #* @get /users/ function(id){ subset(users, uid %in% id) } #* @get /user//connect/ function(from, to){ # Do something with the `from` and `to` variables... }"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"typed-dynamic-routes","dir":"Articles","previous_headings":"","what":"Typed Dynamic Routes","title":"Routing & Input","text":"Unless otherwise instructed, parameters passed plumber endpoints query strings dynamic paths character strings. example, consider following API. Visiting http://localhost:8000/types/14 return: intend support particular data type particular parameter dynamic route, can specify desired type route . Specifying type dynamic path element also narrow paths match endpoint. instance, path /users/123 match first endpoint, /users/8e3k , since 8e3k integer. following details mapping type names can use dynamic types map R data types.","code":"#* @get /type/ function(id){ list( id = id, type = typeof(id) ) } { \"id\": [\"14\"], \"type\": [\"character\"] } #* @get /user/ function(id){ next <- id + 1 # ... } #* @post /user/activated/ function(active){ if (!active){ # ... } }"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"static-file-handler","dir":"Articles","previous_headings":"","what":"Static File Handler","title":"Routing & Input","text":"Plumber includes static file server can used host directories static assets JavaScript, CSS, HTML files. servers fairly simple configure integrate plumber application. example expose local directory ./files/static default /public path server. file ./files/static/branding.html, available Plumber server /public/branding.html. can optionally provide additional argument configure public path used server. instance expose local directory files/static /public, /static. Likewise, serve main index.html, can also map /files/static / using enables serve /files/static/index.html http://localhost:8000/ root URL. “implementation” server examples just empty list(). can also specify function() like plumber annotations. point, implementation doesn’t alter behavior static server. Eventually, list function may provide opportunity configure server changing things like cache control settings. ’re configuring Plumber router programmatically, can instantiate special static file router mount onto another router discussed static file router section.","code":"#* @assets ./files/static list() #* @assets ./files/static /static list() #* @assets ./files/static / list()"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"input-handling","dir":"Articles","previous_headings":"","what":"Input Handling","title":"Routing & Input","text":"Plumber routes requests based exclusively path method incoming HTTP request, requests can contain much information just . might include additional HTTP headers, query string, request body. fields may viewed “inputs” Plumber API.","code":""},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"the-request-object","dir":"Articles","previous_headings":"Input Handling","what":"The Request Object","title":"Routing & Input","text":"HTTP requests Plumber stored environments satisfy Rook interface. expected objects HTTP requests following.","code":""},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"query-strings","dir":"Articles","previous_headings":"Input Handling","what":"Query Strings","title":"Routing & Input","text":"query string may appended URL order convey additional information beyond just request route. Query strings allow encoding character string keys values. example, URL https://duckduckgo.com/?q=bread&pretty=1, everything following ? constitutes query string. case, two variables (q pretty) set (bread 1, respectively). Plumber automatically forward information query string function executed aligning name query string name function parameter. following example defines search API mimics example DuckDuckGo merely prints receives. Visiting http://localhost:8000/?q=bread&pretty=1 print: equivalent calling search(q=\"bread\", pretty=\"1\"). parameter specified query string, just omitted invocation endpoint. example http://localhost:8000/?q=cereal equivalent search(q=\"cereal\"). function fall back default value pretty parameter (0), since defined function signature. Including additional query string arguments map parameter function effect. instance http://localhost:8000/?test=123 return results calling search(). (Note raw query string available req$QUERY_STRING.) web browsers impose limitations length URL. Internet Explorer, particular, caps query string 2,048 characters. need send large amounts data client API, likely better idea send request body.","code":"#* @get / search <- function(q=\"\", pretty=0){ paste0(\"The q parameter is '\", q, \"'. \", \"The pretty parameter is '\", pretty, \"'.\") } [\"The q parameter is 'bread'. The pretty parameter is '1'.\"] [\"The q parameter is 'cereal'. The pretty parameter is '0'.\"] [\"The q parameter is ''. The pretty parameter is '0'.\"]"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"request-body","dir":"Articles","previous_headings":"Input Handling","what":"Request Body","title":"Routing & Input","text":"Another way provide additional information inside HTTP request using message body. Effectively, client specifies metadata request (path ’s trying reach, HTTP headers, etc.) can provide message body. maximum size request body depends largely technologies involved (client, proxies, etc.) typically least 2MB – much larger query string. approach commonly seen PUT POST requests, though encounter HTTP methods. Plumber attempt parse request body one using allowed parsers. fields provided message body passed parameters function.","code":""},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"files-handling-note","dir":"Articles","previous_headings":"Input Handling > Request Body","what":"Files handling note","title":"Routing & Input","text":"dealing binary parameters, Plumber return named list raw vector. names list original uploaded filenames. Raw values binary content file. Unfortunately, crafting request message body requires bit work making GET request query string web browser, can use tools like curl command line httr R package. ’ll use curl examples . Running curl --data \"id=123&name=Jennifer\" \"http://localhost:8000/user\" return: Alternatively, echo {\"id\":123, \"name\": \"Jennifer\"} > call.json & curl --data @call.json \"http://localhost:8000/user\" -H \"content-type: application/json\" (formatting body JSON) effect. demonstrated , raw request body made available req$bodyRaw parsed request body available req$body. multiple parameters matched endpoint formals, error thrown. Due nature multiple values can matched argument, recommended POST endpoints function definition accepts formals req, res, .... endpoint arguments processed like list, available req$argsBody, arguments req$args. req$args combination list(req = req, res = res), req$argsPath, req$argsBody, req$argsQuery.","code":"#* @post /user function(req, id, name) { list( id = id, name = name, body = req$body, raw = req$bodyRaw ) } { \"id\": [123], \"name\": [\"Jennifer\"], \"body\": { \"id\": [123], \"name\": [\"Jennifer\"] }, \"raw\": [\"aWQ9MTIzJm5hbWU9SmVubmlmZXI=\"] }"},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"read-cookies","dir":"Articles","previous_headings":"Input Handling","what":"Cookies","title":"Routing & Input","text":"cookies attached incoming request, ’ll made available via req$cookies. contain list cookies included request. names list correspond names cookies value element character string. See Setting Cookies section details set cookies Plumber. ’ve set encrypted cookies (discussed Encrypted Cookies section), session decrypted made available req$session.","code":""},{"path":"https://www.rplumber.io/articles/routing-and-input.html","id":"headers","dir":"Articles","previous_headings":"Input Handling","what":"Headers","title":"Routing & Input","text":"HTTP headers attached incoming request attached request object. prefixed HTTP_, name header capitalized, hyphens substituted underscores. e.g. Content-Type HTTP header can found req$HTTP_CONTENT_TYPE. Running curl --header \"customheader: abc123\" http://localhost:8000 return: can print names properties attached request running print(ls(req)) inside endpoint.","code":"#* Return the value of a custom header #* @get / function(req){ list( val = req$HTTP_CUSTOMHEADER ) } { \"val\": [\"abc123\"] }"},{"path":"https://www.rplumber.io/articles/security.html","id":"networking","dir":"Articles","previous_headings":"","what":"Networking & Firewalls","title":"Security","text":"networking standpoint, two fundamentally different approaches developing R code. can develop locally using tool like RStudio Desktop. case, R session (Plumber APIs run()) housed local machine. can develop remote machine using tool like RStudio Server. , R session running remote server accessed across network. first case, ’s typically little consider networking perspective. APIs accessible http://127.0.0.1:8000 default (localhost synonymous local IP address 127.0.0.1) likely won’t need concern firewalls network proxies. second case, however, may need consider network environment server running API. considerations ’ll need make hosting API server production use. particular, investigate whether firewalls server hosting Plumber API clients want able connect. Firewalls way block undesired network traffic. desktop computers many servers come firewalls enabled ---box. means want expose API running port 8000, need configure firewall accept incoming connections port. Firewalls can also configured network intermediaries, may need configure multiple firewalls allow traffic order expose desired port API clients.","code":""},{"path":"https://www.rplumber.io/articles/security.html","id":"https","dir":"Articles","previous_headings":"","what":"HTTPS","title":"Security","text":"HTTPS secure form HTTP. Many people now aware check browser displays padlock associated HTTPS enter sensitive information like credit card number. HTTPS important consider developing Plumber APIs, well. HTTPS primarily offers two protections: encrypts information sent API client Plumber process using TLS (often still referred predecessor, “SSL”). prevents others network able read information sent back forth. gives API client confidence ’s communicating Plumber process, opposed imposter. two assurances critical API handling sensitive data performing actions intended authenticated users. hosting options support serving HTTP HTTPS simultaneously. secure configuration redirect incoming HTTP traffic HTTPS hosting option. added layer protection, can consider enabling HTTP Strict Transport Security (HSTS). HSTS way instruct clients – future – refuse connect server unsecure HTTP. ensures imposter server able trick client connecting insecure HTTP future. Unfortunately, Plumber implement HTTPS support natively, documented hosting options offer ways deploy HTTPS HSTS front Plumber API.","code":""},{"path":"https://www.rplumber.io/articles/security.html","id":"dos","dir":"Articles","previous_headings":"","what":"Denial Of Service (DoS)","title":"Security","text":"Denial service (DoS) attacks employed order temporarily shut server service overwhelming traffic. DoS scenario caused single ignorant user unintentionally making request ask server impossible task, intentionally introduced malicious actor leveraging vast number machines repeatedly make requests expensive server respond . later form often called distributed denial service attack (DDoS) typically requires special infrastructure network capacity beyond scope ’ll discuss . However, practices employ designing Plumber API put safety guards around work API request might instigate. expected output harmless plot. plot takes negligible amount time create. However, plots points take time create. plot, 10,000 points added, took 0.155 seconds generate. doesn’t sound like much, exposed API publicly Internet, attacker easily generate enough traffic endpoint overwhelm Plumber process. Even worse, attacker make request endpoint millions billions points might cause server run memory consume much CPU deprives important system resources. Either case result Plumber process crashing altogether. solution, case, ensure reasonable safety guards place user input. can see allow user request graph 1,000 points. requests exceeding limit immediately terminated without computation. attentive resources consumed filters endpoints. Consider various values user provide API endpoint’s parameters ensure behavior system reasonable cases. API endpoints require extensive computation, consider protect endpoints (perhaps exposing authenticated users) prevent malicious user abusing system.","code":"#* This is an example of an UNSAFE endpoint which #* is vulnerable to a DOS attack. #* @get / #* @serializer png function(pts=10) { # An example of an UNSAFE endpoint. plot(1:pts) } #* This is an example of an safe endpoint which #* checks user input to avoid a DOS attack #* @get / #* @serializer png function(pts=10) { if (pts > 1000 & pts > 0){ stop(\"pts must be between 1 and 1,000\") } plot(1:pts) }"},{"path":"https://www.rplumber.io/articles/security.html","id":"sanitization","dir":"Articles","previous_headings":"","what":"Sanitization & Injection","title":"Security","text":"time accept input user code, plan worst-case scenario. , example, API endpoint allows user specify name file read particular directory returns contents, might naively implement like . Unfortunately, API endpoint properly sanitize user input. user set file parameter ../plumber.R now endpoint return source code Plumber API. course just easy attempt read files might contain API keys sensitive data. One solution case strip special characters user input prevent users able escape different directory. File paths opportunity malicious input damage system, however. Another way user input can dangerous attack known “cross site scripting,” “XSS.” attack can leveraged whenever user input may rendered user’s browser. instance endpoint allows users comment page later displays comments users, attacked craft comment : can see, comment JavaScript embedded within , case used popup message user. course JavaScript used harmful way redirecting users malicious site, instance, uploading data special access server destination. user input might included HTML page properly escaped (see htmltools::html_escape help). Lastly, user input can used “injection attack,” user injects malicious commands might sent another system. best known family SQL injection attacks, user input meant included SQL query executed database might contain additional SQL commands leak data damage database. details SQL injection attacks mitigation strategies R available . summary, sure separate “trusted” “untrusted” objects API implementation. Anything user provides considered “untrusted” escaped sanitized. point can consider object “trusted” proceed take actions .","code":"#* This is an example of an UNSAFE endpoint which #* does not sanitize user input #* @get / function(file) { # An example of an UNSAFE endpoint. path <- file.path(\"./datasets\", file) readLines(path) } #* This is an example of an endpoint which #* checks user input. #* @get / function(file) { # Strip all \"non-word\" characters from user input sanitizedFile <- gsub(\"\\\\W\", \"\", file) path <- file.path(\"./datasets\", sanitizedFile) readLines(path) } \"This is a comment with JavaScript!