diff --git a/dev/articles/how-to.html b/dev/articles/how-to.html index 1bcbbe7..584a829 100644 --- a/dev/articles/how-to.html +++ b/dev/articles/how-to.html @@ -131,7 +131,7 @@

How http <- webfakes::local_app_process(webfakes::httpbin_app(), start = TRUE) http$local_env(list(GITHUB_API = "{url}")) Sys.getenv("GITHUB_API") -#> [1] "http://127.0.0.1:40897/" +#> [1] "http://127.0.0.1:46449/" http$stop() Sys.getenv("GITHUB_API") #> [1] "" @@ -154,14 +154,14 @@

How can I write my own app?
 web <- webfakes::new_app_process(time)
 web$url()
-#> [1] "http://127.0.0.1:41519/"
+#> [1] "http://127.0.0.1:42131/"

Use web$url() to query the URL of the app. For example:

 url <- web$url("/time")
 httr::content(httr::GET(url))
 #> $time
-#> [1] "2025-01-13 21:26:05"
+#> [1] "2025-01-14 09:13:41"

web$stop() stops the app and the subprocess as well:

 web$stop()
@@ -345,8 +345,8 @@ 

How do I test a sequence of reques #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 3.7 seconds... -#> Response [http://127.0.0.1:44631/unstable] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:46793/unstable] +#> Date: 2025-01-14 09:13 #> Status: 200 #> Content-Type: application/json #> Size: 17 B

@@ -397,8 +397,8 @@

How do I test a sequence of reques ) } post_package("vcr") -#> Response [http://127.0.0.1:41859/packages?name=vcr] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:40119/packages?name=vcr] +#> Date: 2025-01-14 09:13 #> Status: 200 #> Content-Type: application/json #> Size: 18 B @@ -411,8 +411,8 @@

How do I test a sequence of reques #> [1] "vcr" post_package("httptest") -#> Response [http://127.0.0.1:41859/packages?name=httptest] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:40119/packages?name=httptest] +#> Date: 2025-01-14 09:13 #> Status: 200 #> Content-Type: application/json #> Size: 29 B @@ -654,34 +654,29 @@

Can I test asynchrono
 web <- webfakes::local_app_process(
   webfakes::httpbin_app(),
-  opts = webfakes::server_opts(num_threads = 3)
+  opts = webfakes::server_opts(num_threads = 6, enable_keep_alive = TRUE)
 )
-test_that("", {
-  url <- web$url("/delay/1")
+testthat::test_that("parallel requests", {
+  url <- web$url("/delay/0.5")
   p <- curl::new_pool()
-  handles <- replicate(3, curl::new_handle(url = url, http_version = 2))
+  handles <- replicate(3, curl::new_handle(url = url))
   resps <- list()
   for (handle in handles) {
     curl::multi_add(
       handle,
-      done = function(x) message("one is done"),
+      done = function(x) resps <<- c(resps, list(x)),
       fail = stop,
       pool = p
     )
   }
-  st <- system.time(curl::multi_run(timeout = 5, pool = p))
-  print(st)
-  expect_true(st[["elapsed"]] < 3.0)
+  st <- system.time(curl::multi_run(timeout = 3, pool = p))
+  testthat::expect_true(st[["elapsed"]] < 1.5)
 })
-#> one is done
-#> one is done
-#> one is done
-#>    user  system elapsed 
-#>   0.002   0.001   1.039 
 #> Test passed 🎊
-

(The http_version = 2 option is currently needed because -of a bug (https://github.com/r-lib/webfakes/issues/108).)

+

(If this should fail for you and webfakes appears to process the +requests sequentially, see issue #108 for +possible workarounds.)

How to make sure that my code works with the real API? @@ -724,9 +719,9 @@

How do I simulate a slow i resp <- curl::curl_fetch_memory(slow$url("/bytes/200")) resp$times #> redirect namelookup connect pretransfer starttransfer -#> 0.000000 0.000044 0.000186 0.000230 0.007567 +#> 0.000000 0.000033 0.000173 0.000221 0.008264 #> total -#> 2.008067

+#> 2.008760

throttle gives the number of bytes per second, so downloading 200 random bytes from the fake app will take about 2 seconds.

diff --git a/dev/articles/introduction.html b/dev/articles/introduction.html index 6918ba8..bb9b9bb 100644 --- a/dev/articles/introduction.html +++ b/dev/articles/introduction.html @@ -217,14 +217,14 @@

Writing apps
 web <- webfakes::new_app_process(time)
 web$url()
-#> [1] "http://127.0.0.1:38937/"
+#> [1] "http://127.0.0.1:43371/"

Use web$url() to query the URL of the app. For example:

 url <- web$url("/time")
 httr::content(httr::GET(url))
 #> $time
-#> [1] "2025-01-13 21:26:36"
+#> [1] "2025-01-14 09:14:13"

web$stop() stops the app and the subprocess as well:

 web$stop()
diff --git a/dev/articles/oauth.html b/dev/articles/oauth.html
index 25a2502..5e2a7af 100644
--- a/dev/articles/oauth.html
+++ b/dev/articles/oauth.html
@@ -118,9 +118,9 @@ 

The OAuth2.0 resource an #> auto_start: #> TRUE #> process id: -#> 9317 +#> 9266 #> http url: -#> http://127.0.0.1:46005/ +#> http://127.0.0.1:36859/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app @@ -156,9 +156,9 @@

OAuth2.0 app creation & registra #> auto_start: #> TRUE #> process id: -#> 9330 +#> 9279 #> http url: -#> http://127.0.0.1:37423/ +#> http://127.0.0.1:33549/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app @@ -183,8 +183,8 @@

OAuth2.0 app creation & registra ) reg_resp <- httr::GET(url) reg_resp -#> Response [http://127.0.0.1:46005/register?name=3P%20app&redirect_uri=http://127.0.0.1:37423/login/redirect] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:36859/register?name=3P%20app&redirect_uri=http://127.0.0.1:33549/login/redirect] +#> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 184 B @@ -197,17 +197,17 @@

OAuth2.0 app creation & registra #> #> $client_id #> $client_id[[1]] -#> [1] "id-95d943f6c6dd352a1cb06ad05f3ac5" +#> [1] "id-71bc087ed76a8feb1c761d5743da97" #> #> #> $client_secret #> $client_secret[[1]] -#> [1] "secret-6c96fbab12fc439eea5a0dd05206ef" +#> [1] "secret-0c69b132f31e1e2aec6af2a7df5c07" #> #> #> $redirect_uri #> $redirect_uri[[1]] -#> [1] "http://127.0.0.1:37423/login/redirect"

+#> [1] "http://127.0.0.1:33549/login/redirect"

The resource app replies with the client id and the client secret. We’ll use them to authenticate the third party app. In real life they are included in the config of the third party app by its admin. Our @@ -226,8 +226,8 @@

OAuth2.0 app creation & registra body = auth_data, encode = "json" ) -#> Response [http://127.0.0.1:37423/login/config] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:33549/login/config] +#> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 41 B @@ -278,9 +278,9 @@

The OAuth2.0 dance#> auto_start: #> TRUE #> process id: -#> 9344 +#> 9293 #> http url: -#> http://127.0.0.1:34853/ +#> http://127.0.0.1:43333/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app @@ -299,8 +299,8 @@

The OAuth2.0 dance) reg_resp2 <- httr::GET(url2) reg_resp2 -#> Response [http://127.0.0.1:46005/register?name=3P%20app2&redirect_uri=http://127.0.0.1:34853/login/redirect] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:36859/register?name=3P%20app2&redirect_uri=http://127.0.0.1:43333/login/redirect] +#> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 185 B @@ -313,17 +313,17 @@

The OAuth2.0 dance#> #> $client_id #> $client_id[[1]] -#> [1] "id-0718e622548334e24b394eb649ffe7" +#> [1] "id-e881e8c22afbe2b2c3c26af6d94e20" #> #> #> $client_secret #> $client_secret[[1]] -#> [1] "secret-7fb7b914231f5398b7007277812415" +#> [1] "secret-6748f149aaa0d02c1a91cede4ccb04" #> #> #> $redirect_uri #> $redirect_uri[[1]] -#> [1] "http://127.0.0.1:34853/login/redirect" +#> [1] "http://127.0.0.1:43333/login/redirect" auth_data2 <- list( auth_url = auth_url, token_url = toke_url, @@ -336,8 +336,8 @@

The OAuth2.0 dance= auth_data2, encode = "json" ) -#> Response [http://127.0.0.1:34853/login/config] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:43333/login/config] +#> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 41 B @@ -367,8 +367,8 @@

The OAuth2.0 dance
 resp_data <- httr::GET(tpapp2$url("/data"))
 resp_data
-#> Response [http://127.0.0.1:34853/data]
-#>   Date: 2025-01-13 21:26
+#> Response [http://127.0.0.1:43333/data]
+#>   Date: 2025-01-14 09:14
 #>   Status: 200
 #>   Content-Type: application/json
 #>   Size: 24 B
@@ -399,8 +399,8 @@ 

OAuth2.0 app creation & regist ) reg_resp3 <- httr::GET(url3) reg_resp3 -#> Response [http://127.0.0.1:46005/register?name=3P%20app2&redirect_uri=http://localhost:1410/] -#> Date: 2025-01-13 21:26 +#> Response [http://127.0.0.1:36859/register?name=3P%20app2&redirect_uri=http://localhost:1410/] +#> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 170 B @@ -413,12 +413,12 @@

OAuth2.0 app creation & regist #> #> $client_id #> $client_id[[1]] -#> [1] "id-cad660ce51543ffee2d7e4d3639af8" +#> [1] "id-eefc6f17f2775997497365921db914" #> #> #> $client_secret #> $client_secret[[1]] -#> [1] "secret-927663c2f2cdec693f52b8caaa192c" +#> [1] "secret-8632991fdd4c1adc9430558682ca2a" #> #> #> $redirect_uri @@ -449,18 +449,18 @@

OAuth2.0 app creation & regist token #> <Token> #> <oauth_endpoint> -#> authorize: http://127.0.0.1:46005/authorize -#> access: http://127.0.0.1:46005/token +#> authorize: http://127.0.0.1:36859/authorize +#> access: http://127.0.0.1:36859/token #> <oauth_app> 3P app2 -#> key: id-cad660ce51543ffee2d7e4d3639af8 +#> key: id-eefc6f17f2775997497365921db914 #> secret: <hidden> #> <credentials> access_token, expiry, refresh_token #> ---

Without the token, the query to the resource server fails:

 httr::GET(rsapp$url("/data"))
-#> Response [http://127.0.0.1:46005/data]
-#>   Date: 2025-01-13 21:26
+#> Response [http://127.0.0.1:36859/data]
+#>   Date: 2025-01-14 09:14
 #>   Status: 401
 #>   Content-Type: text/plain
 #>   Size: 20 B
@@ -527,13 +527,13 @@

Debugging#> [1] "3P app" #> #> $apps[[1]]$client_id -#> [1] "id-95d943f6c6dd352a1cb06ad05f3ac5" +#> [1] "id-71bc087ed76a8feb1c761d5743da97" #> #> $apps[[1]]$client_secret -#> [1] "secret-6c96fbab12fc439eea5a0dd05206ef" +#> [1] "secret-0c69b132f31e1e2aec6af2a7df5c07" #> #> $apps[[1]]$redirect_uri -#> [1] "http://127.0.0.1:37423/login/redirect" +#> [1] "http://127.0.0.1:33549/login/redirect" #> #> #> $apps[[2]] @@ -541,13 +541,13 @@

Debugging#> [1] "3P app2" #> #> $apps[[2]]$client_id -#> [1] "id-0718e622548334e24b394eb649ffe7" +#> [1] "id-e881e8c22afbe2b2c3c26af6d94e20" #> #> $apps[[2]]$client_secret -#> [1] "secret-7fb7b914231f5398b7007277812415" +#> [1] "secret-6748f149aaa0d02c1a91cede4ccb04" #> #> $apps[[2]]$redirect_uri -#> [1] "http://127.0.0.1:34853/login/redirect" +#> [1] "http://127.0.0.1:43333/login/redirect" #> #> #> $apps[[3]] @@ -555,10 +555,10 @@

Debugging#> [1] "3P app2" #> #> $apps[[3]]$client_id -#> [1] "id-cad660ce51543ffee2d7e4d3639af8" +#> [1] "id-eefc6f17f2775997497365921db914" #> #> $apps[[3]]$client_secret -#> [1] "secret-927663c2f2cdec693f52b8caaa192c" +#> [1] "secret-8632991fdd4c1adc9430558682ca2a" #> #> $apps[[3]]$redirect_uri #> [1] "http://localhost:1410/" @@ -568,70 +568,70 @@

Debugging#> $access #> $access[[1]] #> $access[[1]]$client_id -#> [1] "id-95d943f6c6dd352a1cb06ad05f3ac5" +#> [1] "id-71bc087ed76a8feb1c761d5743da97" #> #> $access[[1]]$token #> [1] "token-c6be45eee35844e7ec1d6ada44bc15" #> #> $access[[1]]$expiry -#> [1] "2025-01-13 21:26:51" +#> [1] "2025-01-14 09:14:28" #> #> #> $access[[2]] #> $access[[2]]$client_id -#> [1] "id-0718e622548334e24b394eb649ffe7" +#> [1] "id-e881e8c22afbe2b2c3c26af6d94e20" #> #> $access[[2]]$token #> [1] "token-08e1470fb2bbfa9216925390655281" #> #> $access[[2]]$expiry -#> [1] "2025-01-13 21:26:51" +#> [1] "2025-01-14 09:14:29" #> #> #> $access[[3]] #> $access[[3]]$client_id -#> [1] "id-cad660ce51543ffee2d7e4d3639af8" +#> [1] "id-eefc6f17f2775997497365921db914" #> #> $access[[3]]$token #> [1] "token-1f46a0366717828ac5cc842c163a31" #> #> $access[[3]]$expiry -#> [1] "2025-01-13 21:26:52" +#> [1] "2025-01-14 09:14:29" #> #> #> #> $refresh #> $refresh[[1]] #> $refresh[[1]]$client_id -#> [1] "id-95d943f6c6dd352a1cb06ad05f3ac5" +#> [1] "id-71bc087ed76a8feb1c761d5743da97" #> #> $refresh[[1]]$token #> [1] "refresh-token-ee3f1285a6f4585e9f410375e0512d" #> #> $refresh[[1]]$expiry -#> [1] "2093-02-01 00:40:48" +#> [1] "2093-02-01 12:28:25" #> #> #> $refresh[[2]] #> $refresh[[2]]$client_id -#> [1] "id-0718e622548334e24b394eb649ffe7" +#> [1] "id-e881e8c22afbe2b2c3c26af6d94e20" #> #> $refresh[[2]]$token #> [1] "refresh-token-f70b06b589156b9b5d462b040c500c" #> #> $refresh[[2]]$expiry -#> [1] "2093-02-01 00:40:48" +#> [1] "2093-02-01 12:28:26" #> #> #> $refresh[[3]] #> $refresh[[3]]$client_id -#> [1] "id-cad660ce51543ffee2d7e4d3639af8" +#> [1] "id-eefc6f17f2775997497365921db914" #> #> $refresh[[3]]$token #> [1] "refresh-token-81d2b2f09bcf64302605ab6a9750b3" #> #> $refresh[[3]]$expiry -#> [1] "2093-02-01 00:40:49" +#> [1] "2093-02-01 12:28:26"

Use web$url() to query the URL of the app. For example:

url <- web$url("/time")
 httr::content(httr::GET(url))
 #> $time
-#> [1] "2025-01-12 02:08:03"

+#> [1] "2025-01-14 10:07:38"

web$stop() stops the app and the subprocess as well:

web$stop()
 web$get_state()
@@ -258,11 +258,11 @@ 

How do I test a sequence of reque

pr <- webfakes::new_app_process(flaky)
 url <- pr$url("/unstable")
 httr::RETRY("GET", url, times = 4)
-#> Request failed [401]. Retrying in 1.6 seconds...
-#> Request failed [401]. Retrying in 1.8 seconds...
-#> Request failed [401]. Retrying in 3.4 seconds...
-#> Response [http://127.0.0.1:59492/unstable]
-#>   Date: 2025-01-12 02:08
+#> Request failed [401]. Retrying in 1 seconds...
+#> Request failed [401]. Retrying in 1 seconds...
+#> Request failed [401]. Retrying in 2.1 seconds...
+#> Response [http://127.0.0.1:64374/unstable]
+#>   Date: 2025-01-14 10:07
 #>   Status: 200
 #>   Content-Type: application/json
 #>   Size: 17 B

@@ -307,8 +307,8 @@

How do I test a sequence of reque ) } post_package("vcr") -#> Response [http://127.0.0.1:59498/packages?name=vcr] -#> Date: 2025-01-12 02:08 +#> Response [http://127.0.0.1:64380/packages?name=vcr] +#> Date: 2025-01-14 10:07 #> Status: 200 #> Content-Type: application/json #> Size: 18 B @@ -321,8 +321,8 @@

How do I test a sequence of reque #> [1] "vcr" post_package("httptest") -#> Response [http://127.0.0.1:59498/packages?name=httptest] -#> Date: 2025-01-12 02:08 +#> Response [http://127.0.0.1:64380/packages?name=httptest] +#> Date: 2025-01-14 10:07 #> Status: 200 #> Content-Type: application/json #> Size: 29 B @@ -536,33 +536,27 @@

Can I test asynchron Each request takes 1 second to answer, but if the web server has more than three threads, together they'll still take about 1 second.

web <- webfakes::local_app_process(
   webfakes::httpbin_app(),
-  opts = webfakes::server_opts(num_threads = 3)
+  opts = webfakes::server_opts(num_threads = 6, enable_keep_alive = TRUE)
 )

-

test_that("", {
-  url <- web$url("/delay/1")
+

testthat::test_that("parallel requests", {
+  url <- web$url("/delay/0.5")
   p <- curl::new_pool()
-  handles <- replicate(3, curl::new_handle(url = url, http_version = 2))
+  handles <- replicate(3, curl::new_handle(url = url))
   resps <- list()
   for (handle in handles) {
     curl::multi_add(
       handle,
-      done = function(x) message("one is done"),
+      done = function(x) resps <<- c(resps, list(x)),
       fail = stop,
       pool = p
     )
   }
-  st <- system.time(curl::multi_run(timeout = 5, pool = p))
-  print(st)
-  expect_true(st[["elapsed"]] < 3.0)
+  st <- system.time(curl::multi_run(timeout = 3, pool = p))
+  testthat::expect_true(st[["elapsed"]] < 1.5)
 })
-#> one is done
-#> one is done
-#> one is done
-#>    user  system elapsed
-#>   0.002   0.001   1.094
 #> Test passed

-

(The http_version = 2 option is currently needed because of a bug -(https://github.com/r-lib/webfakes/issues/108).)

+

(If this should fail for you and webfakes appears to process the +requests sequentially, see issue #108 for possible workarounds.)

@@ -598,9 +592,9 @@

How do I simulate a slow resp <- curl::curl_fetch_memory(slow$url("/bytes/200")) resp$times #> redirect namelookup connect pretransfer starttransfer -#> 0.000000 0.000097 0.000315 0.000339 0.004731 +#> 0.000000 0.000087 0.000266 0.000284 0.004878 #> total -#> 2.013523

+#> 2.013756

throttle gives the number of bytes per second, so downloading 200 random bytes from the fake app will take about 2 seconds.

diff --git a/dev/reference/httpbin_app.html b/dev/reference/httpbin_app.html index 96a3372..dc0e5ac 100644 --- a/dev/reference/httpbin_app.html +++ b/dev/reference/httpbin_app.html @@ -89,7 +89,7 @@

Examples#> [1] "close" #> #> $date -#> [1] "Mon, 13 Jan 2025 21:25:48 GMT" +#> [1] "Tue, 14 Jan 2025 09:13:25 GMT" #> #> $`content-type` #> [1] "application/json" @@ -98,20 +98,20 @@

Examples#> [1] "313" #> #> $etag -#> [1] "\"0651e283\"" +#> [1] "\"793689be\"" #> cat(rawToChar(resp$content)) #> { #> "args": {}, #> "headers": { -#> "Host": "127.0.0.1:41045", +#> "Host": "127.0.0.1:32961", #> "User-Agent": "R/4.4.2 R (4.4.2 x86_64-pc-linux-gnu x86_64 linux-gnu) on GitHub Actions", #> "Accept": "*/*", #> "Accept-Encoding": "deflate, gzip, br, zstd" #> }, #> "origin": "127.0.0.1", #> "path": "/get", -#> "url": "http://127.0.0.1:41045/get" +#> "url": "http://127.0.0.1:32961/get" #> } proc$stop() diff --git a/dev/reference/introduction.html b/dev/reference/introduction.html index 1a47299..42c0ed6 100644 --- a/dev/reference/introduction.html +++ b/dev/reference/introduction.html @@ -148,12 +148,12 @@

Writing appsnew_app_process().

web <- webfakes::new_app_process(time)
 web$url()
-#> [1] "http://127.0.0.1:59478/"

+#> [1] "http://127.0.0.1:64358/"

Use web$url() to query the URL of the app. For example:

url <- web$url("/time")
 httr::content(httr::GET(url))
 #> $time
-#> [1] "2025-01-12 02:07:58"

+#> [1] "2025-01-14 10:07:33"

web$stop() stops the app and the subprocess as well:

web$stop()
 web$get_state()
diff --git a/dev/reference/server_opts.html b/dev/reference/server_opts.html
index e1ee7cb..3260f85 100644
--- a/dev/reference/server_opts.html
+++ b/dev/reference/server_opts.html
@@ -170,7 +170,7 @@ 

Examples#> [1] NA #> #> $error_log_file -#> [1] "/tmp/Rtmpd5MOPJ/webfakes/error.log" +#> [1] "/tmp/RtmpCq81KX/webfakes/error.log" #> #> $tcp_nodelay #> [1] FALSE diff --git a/dev/search.json b/dev/search.json index 02d4f46..f4bc849 100644 --- a/dev/search.json +++ b/dev/search.json @@ -1 +1 @@ -[{"path":[]},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"our-pledge","dir":"","previous_headings":"","what":"Our Pledge","title":"Contributor Covenant Code of Conduct","text":"members, contributors, leaders pledge make participation community harassment-free experience everyone, regardless age, body size, visible invisible disability, ethnicity, sex characteristics, gender identity expression, level experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, sexual identity orientation. pledge act interact ways contribute open, welcoming, diverse, inclusive, healthy community.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"our-standards","dir":"","previous_headings":"","what":"Our Standards","title":"Contributor Covenant Code of Conduct","text":"Examples behavior contributes positive environment community include: Demonstrating empathy kindness toward people respectful differing opinions, viewpoints, experiences Giving gracefully accepting constructive feedback Accepting responsibility apologizing affected mistakes, learning experience Focusing best just us individuals, overall community Examples unacceptable behavior include: use sexualized language imagery, sexual attention advances kind Trolling, insulting derogatory comments, personal political attacks Public private harassment Publishing others’ private information, physical email address, without explicit permission conduct reasonably considered inappropriate professional setting","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"enforcement-responsibilities","dir":"","previous_headings":"","what":"Enforcement Responsibilities","title":"Contributor Covenant Code of Conduct","text":"Community leaders responsible clarifying enforcing standards acceptable behavior take appropriate fair corrective action response behavior deem inappropriate, threatening, offensive, harmful. Community leaders right responsibility remove, edit, reject comments, commits, code, wiki edits, issues, contributions aligned Code Conduct, communicate reasons moderation decisions appropriate.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"scope","dir":"","previous_headings":"","what":"Scope","title":"Contributor Covenant Code of Conduct","text":"Code Conduct applies within community spaces, also applies individual officially representing community public spaces. Examples representing community include using official e-mail address, posting via official social media account, acting appointed representative online offline event.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"enforcement","dir":"","previous_headings":"","what":"Enforcement","title":"Contributor Covenant Code of Conduct","text":"Instances abusive, harassing, otherwise unacceptable behavior may reported community leaders responsible enforcement codeofconduct@posit.co. complaints reviewed investigated promptly fairly. community leaders obligated respect privacy security reporter incident.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"enforcement-guidelines","dir":"","previous_headings":"","what":"Enforcement Guidelines","title":"Contributor Covenant Code of Conduct","text":"Community leaders follow Community Impact Guidelines determining consequences action deem violation Code Conduct:","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_1-correction","dir":"","previous_headings":"Enforcement Guidelines","what":"1. Correction","title":"Contributor Covenant Code of Conduct","text":"Community Impact: Use inappropriate language behavior deemed unprofessional unwelcome community. Consequence: private, written warning community leaders, providing clarity around nature violation explanation behavior inappropriate. public apology may requested.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_2-warning","dir":"","previous_headings":"Enforcement Guidelines","what":"2. Warning","title":"Contributor Covenant Code of Conduct","text":"Community Impact: violation single incident series actions. Consequence: warning consequences continued behavior. interaction people involved, including unsolicited interaction enforcing Code Conduct, specified period time. includes avoiding interactions community spaces well external channels like social media. Violating terms may lead temporary permanent ban.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_3-temporary-ban","dir":"","previous_headings":"Enforcement Guidelines","what":"3. Temporary Ban","title":"Contributor Covenant Code of Conduct","text":"Community Impact: serious violation community standards, including sustained inappropriate behavior. Consequence: temporary ban sort interaction public communication community specified period time. public private interaction people involved, including unsolicited interaction enforcing Code Conduct, allowed period. Violating terms may lead permanent ban.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_4-permanent-ban","dir":"","previous_headings":"Enforcement Guidelines","what":"4. Permanent Ban","title":"Contributor Covenant Code of Conduct","text":"Community Impact: Demonstrating pattern violation community standards, including sustained inappropriate behavior, harassment individual, aggression toward disparagement classes individuals. Consequence: permanent ban sort public interaction within community.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"attribution","dir":"","previous_headings":"","what":"Attribution","title":"Contributor Covenant Code of Conduct","text":"Code Conduct adapted Contributor Covenant, version 2.1, available https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. Community Impact Guidelines inspired [Mozilla’s code conduct enforcement ladder][https://github.com/mozilla/inclusion]. answers common questions code conduct, see FAQ https://www.contributor-covenant.org/faq. Translations available https://www.contributor-covenant.org/translations.","code":""},{"path":"https://webfakes.r-lib.org/dev/LICENSE.html","id":null,"dir":"","previous_headings":"","what":"MIT License","title":"MIT License","text":"Copyright (c) 2023 webfakes authors Permission hereby granted, free charge, person obtaining copy software associated documentation files (β€œSoftware”), deal Software without restriction, including without limitation rights use, copy, modify, merge, publish, distribute, sublicense, /sell copies Software, permit persons Software furnished , subject following conditions: copyright notice permission notice shall included copies substantial portions Software. SOFTWARE PROVIDED β€œβ€, WITHOUT WARRANTY KIND, EXPRESS IMPLIED, INCLUDING LIMITED WARRANTIES MERCHANTABILITY, FITNESS PARTICULAR PURPOSE NONINFRINGEMENT. EVENT SHALL AUTHORS COPYRIGHT HOLDERS LIABLE CLAIM, DAMAGES LIABILITY, WHETHER ACTION CONTRACT, TORT OTHERWISE, ARISING , CONNECTION SOFTWARE USE DEALINGS SOFTWARE.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"app","dir":"Articles","previous_headings":"","what":"app","title":"webfakes glossary","text":"(Also: fake web app, webfakes app.) web application can served webfakes’s web server, typically another process, app process. Sometimes call fake web app, emphasize use testing real web apps APIs. can create webfakes app new_app() function. webfakes app R object can save disk saveRDS() , can also include package. can start $listen() method. Since main R process runs test suite code, usually run subprocess, see new_app_process() local_app_process().","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"app-process","dir":"Articles","previous_headings":"","what":"app process","title":"webfakes glossary","text":"(Also: web server process, webfakes subprocess.) app process R subprocess, started main R process, serve webfakes app. can create app process object new_app_process() local_app_process(). default actual process start yet, create . can start explicitly $start method app process object, querying URL $url() port $get_port(). test cases, typically start app processes places: setup*.R file, start app whole test suite can use. Alternatively, helper*.R file, start app whole test suite can use, works better interactive development. beginning test file, create app single test file. Inside test_that(), create app single test block. See -details .","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"handler","dir":"Articles","previous_headings":"","what":"handler","title":"webfakes glossary","text":"(handler function.) handler route middleware.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"handler-stack","dir":"Articles","previous_headings":"","what":"handler stack","title":"webfakes glossary","text":"stack handler functions, called app one , passing request response objects . Handlers typically manipulate request /response objects. terminal handler instructs app return response HTTP client. non-terminal handler tells app keep calling handlers, returning string \"next\".","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"httpbin-app","dir":"Articles","previous_headings":"","what":"httpbin app","title":"webfakes glossary","text":"example app, implements excellent https://httpbin.org/ web service. can use simulate certain HTTP responses. handy HTTP clients, potentially useful tools well. Use httpbin_app() create instance app.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"middleware","dir":"Articles","previous_headings":"","what":"middleware","title":"webfakes glossary","text":"middleware handler function bound path. called router, like handler functions. may manipulate request response, can side effect. example built-middleware functions webfakes: mw_json() parses request’s JSON body R object. mw_log() logs requests responses screen file. mw_static() serves static files directory. can also write middleware functions.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"path-matching","dir":"Articles","previous_headings":"","what":"path matching","title":"webfakes glossary","text":"router performs path matching goes handler stack. HTTP method path route match HTTP method URL request, handler called, otherwise . Paths can parameters regular expressions. See ?new_regexp() regular expressions β€œPath parameters” ?new_app() parameters.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"route","dir":"Articles","previous_headings":"","what":"route","title":"webfakes glossary","text":"route handler function bound certain paths web app. request URL matches path route, handler function called, give chance send appropriate response. Route paths may parameters can regular expressions webfakes.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"routing","dir":"Articles","previous_headings":"","what":"routing","title":"webfakes glossary","text":"Routing process going handlers stack, calling handler functions, one , one handles request. handler function route, router calls path matches request URL.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-use-webfakes-in-my-package","dir":"Articles","previous_headings":"","what":"How do I use webfakes in my package?","title":"How to use webfakes in your tests","text":"First, need add webfakes DESCRIPTION file package. Use Suggests field, webfakes needed testing: , unless URL web service argument package functions, might need tweak package code slightly make sure every call real web service can targeted another URL instead (fake app). See next subsection. Last least, need decide want single web app test cases. alternative use different apps test files. Occasionally may want use special app single test case. app runs new subprocess, takes typically 100-400ms start. See sections later writing tests single app multiple apps.","code":"... Suggests: webfakes, testthat ..."},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-make-my-app-connect-to-webfakes-when-the-tests-are-running","dir":"Articles","previous_headings":"","what":"How do I make my app connect to webfakes when the tests are running?","title":"How to use webfakes in your tests","text":"typical scenario, want package connect test app running tests. URL web service argument functions, one way achieve allow specifying web server URL(s) via environment variables. E.g. writing GitHub API client, package can check use GITHUB_URL environment variable. E.g. set, package connects proper GitHub API. testing, can point test app. new_app_process() helps setting temporary environment variables. active process running, removed reset $stop(). example: $local_env() environment variables, webfakes replaces {url} actual app URL. needed default, web server process starts later, URL known yet.","code":"service_url <- function() { Sys.getenv(\"GITHUB_URL\", \"https://api.github.com\") } # rest of the package code foobar <- function() { httr::GET(service_url()) } http <- webfakes::local_app_process(webfakes::httpbin_app(), start = TRUE) http$local_env(list(GITHUB_API = \"{url}\")) Sys.getenv(\"GITHUB_API\") #> [1] \"http://127.0.0.1:40897/\" http$stop() Sys.getenv(\"GITHUB_API\") #> [1] \"\""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-write-my-own-app","dir":"Articles","previous_headings":"","what":"How can I write my own app?","title":"How to use webfakes in your tests","text":"create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:41519/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-13 21:26:05\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-use-httpbin_app-or-another-app-with-testthat","dir":"Articles","previous_headings":"","what":"How do I use httpbin_app() (or another app) with testthat?","title":"How to use webfakes in your tests","text":"can use testthat’s setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See β€œstart app writing tests?” just .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed πŸ₯‡"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-start-the-app-when-writing-the-tests","dir":"Articles","previous_headings":"","what":"How do I start the app when writing the tests?","title":"How to use webfakes in your tests","text":"convenient start webfakes server process(es) working tests interactively, e.g.Β using devtools::load_all(). local_app_process() testthat setup*.R file automatic, devtools::load_all() run files. need source setup*.R files manually, error prone. One solution create server processes testthat helper*.R files. load_all() executes helper files default. instead using setup file, can simply helper-http.R file: app process created helper file, ready use load_all(), (default) actual process started first $url() $get_port() call. can also start manually $start(). Processes created helper files cleaned automatically end test suite, unless clean registering $stop() call setup file, like : practice necessary, R CMD check runs tests separate process, finishes, webfakes processes cleaned well. running devtools::test(), testthat::test_local() another testthat function run (part ) test suite current session, helper*.R files (re)loaded first. terminate currently running app processes, , create new app process objects. test suite auto-start test processes helper*.R, cleaned end test suite, next load_all() test() call, end R session. lets run test code interactively, either via test() manually, without thinking much webfakes processes.","code":"httpbin <- local_app_process(httpbin_app()) withr::defer(httpbin$stop(), testthat::teardown_env())"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"can-i-have-an-app-for-a-single-testthat-test-file","dir":"Articles","previous_headings":"","what":"Can I have an app for a single testthat test file?","title":"How to use webfakes in your tests","text":"run web app single test file, start new_app_process() beginning file, register cleanup using withr::defer(). Even simpler, use local_app_process() new_app_process() automatically stops web server process, end test file: test cases, use web$url() get URL connect .","code":"app <- webfakes::new_app() app$get(\"/hello/:user\", function(req, res) { res$send(paste0(\"Hello \", req$params$user, \"!\")) }) web <- webfakes::local_app_process(app) test_that(\"can use hello API\", { url <- web$url(\"/hello/Gabor\") expect_equal(httr::content(httr::GET(url)), \"Hello Gabor!\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed 🎊"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"can-i-use-an-app-for-a-single-testthat-test","dir":"Articles","previous_headings":"","what":"Can I use an app for a single testthat test?","title":"How to use webfakes in your tests","text":"Sure. need create app process within testthat::test_that() test case. local_app_process() automatically cleans end block. goes like :","code":"test_that(\"query works\", { app <- webfakes::new_app() app$get(\"/hello\", function(req, res) res$send(\"hello there\")) web <- webfakes::local_app_process(app) echo <- httr::content(httr::GET(web$url(\"/hello\"))) expect_equal(echo, \"hello there\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed 🌈"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-test-a-sequence-of-requests","dir":"Articles","previous_headings":"","what":"How do I test a sequence of requests?","title":"How to use webfakes in your tests","text":"test sequence requests, app needs state information kept requests. app$locals environment belongs app, can used record information retrieve future requests. store anything app$locals, something simple like counter variable, something fancier like sqlite database. can add something app$locals via methods directly creating app. E.g. end point fails three times, succeeds , fails three times, etc. Note counter created code starts 0, 1. Let’s run app another process connect : Another example send information app retrieve . POST request store name query parameter app$locals$packages, can queried GET request. Now start app subprocess, run GET query . Let’s POST new information. Stop app process:","code":"store <- webfakes::new_app() store$locals$packages <- list(\"webfakes\") ls(store$locals) #> [1] \"packages\" store$locals$packages #> [[1]] #> [1] \"webfakes\" flaky <- webfakes::new_app() flaky$get(\"/unstable\", function(req, res) { if (identical(res$app$locals$counter, 3L)) { res$app$locals$counter <- NULL res$send_json(object = list(result = \"ok\")) } else { res$app$locals$counter <- c(res$app$locals$counter, 0L)[[1]] + 1L res$send_status(401) } }) pr <- webfakes::new_app_process(flaky) url <- pr$url(\"/unstable\") httr::RETRY(\"GET\", url, times = 4) #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 3.7 seconds... #> Response [http://127.0.0.1:44631/unstable] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 17 B store <- webfakes::new_app() # Initial \"data\" for the app store$locals$packages <- list(\"webfakes\") # Get method store$get(\"/packages\", function(req, res) { res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) # Post method, store information from the query store$post(\"/packages\", function(req, res) { res$app$locals$packages <- c(res$app$locals$packages, req$query$name) res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) web <- webfakes::local_app_process(store, start = TRUE) # Get current information get_packages <- function() { httr::content( httr::GET( httr::modify_url( web$url(), path = \"packages\" ) ) ) } get_packages() #> [[1]] #> [1] \"webfakes\" post_package <- function(name) { httr::POST( httr::modify_url( web$url(), path = \"packages\", query = list(name = name) ) ) } post_package(\"vcr\") #> Response [http://127.0.0.1:41859/packages?name=vcr] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 18 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" post_package(\"httptest\") #> Response [http://127.0.0.1:41859/packages?name=httptest] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 29 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" #> #> [[3]] #> [1] \"httptest\" web$stop()"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-debug-an-app","dir":"Articles","previous_headings":"","what":"How can I debug an app?","title":"How to use webfakes in your tests","text":"debug app, best run main R process, .e.Β via new_app_process(). can add breakpoints, browser() calls handler functions, invoke app another process. might find curl command line tool send HTTP requests app, can just use another R process. example. simply print incoming request object screen now. real debugging session probably want place browser() command . Now start app port 3000: Connect app another R curl process: main R session print incoming request: Press CTRL+C ESC interrupt app main session.","code":"app <- webfakes::new_app() app$get(\"/debug\", function(req, res) { print(req) res$send(\"Got your back\") }) app$listen(port = 3000) #> Running webfakes web app on port 3000 curl -v http://127.0.0.1:3000/debug #> * Trying 127.0.0.1... #> * TCP_NODELAY set #> * Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0) #> > GET /debug HTTP/1.1 #> > Host: 127.0.0.1:3000 #> > User-Agent: curl/7.54.0 #> > Accept: */* #> > #> < HTTP/1.1 200 OK #> < Content-Type: text/plain #> < Content-Length: 13 #> < #> * Connection #0 to host 127.0.0.1 left intact #> Got your back #> #> method: #> get #> url: #> http://127.0.0.1:3000/debug #> client: #> 127.0.0.1 #> query: #> headers: #> Host: 127.0.0.1:3000 #> User-Agent: curl/7.54.0 #> Accept: */* #> fields and methods: #> app # the webfakes_app the request belongs to #> headers # HTTP request headers #> hostname # server hostname, the Host header #> method # HTTP method of request (lowercase) #> path # server path #> protocol # http or https #> query_string # raw query string without '?' #> query # named list of query parameters #> remote_addr # IP address of the client #> url # full URL of the request #> get_header(field) # get a request header #> # see ?webfakes_request for details"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-test-https-requests","dir":"Articles","previous_headings":"","what":"How can I test HTTPS requests?","title":"How to use webfakes in your tests","text":"Serving HTTPS localhost 127.0.0.1 instead HTTP easy, need 1. Set port HTTPS port adding \"s\" suffix port number. Use \"0s\" OS assigned free port: r new_app_process(app, port = \"0s\") default webfakes uses server key + certificate file r system.file(\"cert/localhost/server.pem\", package = \"webfakes\") certificate includes localhost, 127.0.0.1 localhost.localdomain. need another domain IP address, ’ll need create certificate. generate.sh file directory helps . 2. Specify certificate bundle HTTP client using. default server key use ca.crt file webfakes package: r system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") See examples HTTP clients . using curl package, use ca_info option curl::new_handle() curl::handle_setopt(): httr package, use httr::config(cainfo = ...): httr2 package: utils::download.file point CURL_CA_BUNDLE environment variable ca.crt file. Don’t forget undo , HTTP request done.","code":"cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") curl::curl_fetch_memory( http$url(\"/path/to/endpoint\"), handle = curl::new_handle(cainfo = cainfo) ) httr::GET( http$url(\"/headers\", https = TRUE), httr::config(cainfo = cainfo) ) httr2::request(\"https://example.com\") |> httr2::req_options(cainfo = cainfo) |> httr2::req_perform() Sys.setenv( CURL_CA_BUNDLE = system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") ) download.file(http$url(\"/path/to/endpoint\"), res <- tempfile())"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"special-considerations-for-tests-on-windows","dir":"Articles","previous_headings":"How can I test HTTPS requests?","what":"Special considerations for tests on Windows","title":"How to use webfakes in your tests","text":"Unfortunately things simple Windows, HTTP clients. far can tell, easily possible make HTTP clients accept new self-signed certificate. possible libcurl, though, need set CURL_SSL_BACKEND=openssl environment variable. (libcurl must built openssl support course.) need set env var loading libcurl, best set starting R. One way tests run tests subprocess, callr package. Look test-https.R file webfakes complete, current example. tests use helper function, defined helper.R: Example test case: seems like good idea skip_on_cran() HTTPS tests, least Windows, setup yet tested enough consider robust. webfakes uses Mbed TLS serving HTTPS.","code":"callr_curl <- function(url, options = list()) { callr::r( function(url, options) { h <- curl::new_handle() curl::handle_setopt(h, .list = options) curl::curl_fetch_memory(url, handle = h) }, list(url = url, options = options), env = c( callr::rcmd_safe_env(), CURL_SSL_BACKEND = \"openssl\", CURL_CA_BUNDLE = if (\"cainfo\" %in% names(options)) options$cainfo ) ) } # ... cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") resp <- if (.Platform$OS.type == \"windows\") { callr_curl(http$url(\"/hello\"), list(cainfo = cainfo)) } else { curl::curl_fetch_memory( http$url(\"/hello\"), handle = curl::new_handle(cainfo = cainfo) ) } # ..."},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-run-a-server-on-multiple-ports","dir":"Articles","previous_headings":"","what":"How can I run a server on multiple ports?","title":"How to use webfakes in your tests","text":"can specify multiple port numbers, vector. webfakes listen ports. can also mix HTTP HTTP ports. redirect HTTP port HTTPS port, append \"r\" suffix HTTP port number. port redirected next HTTPS port. E.g. redirect HTTP port 3000 HTTPS port 3001. redirect OS assigned HTTP port OS assigned HTTPS port, use zeros port numbers: can use http$get_ports() query port numbers. can also use get HTTPS URL instead default one (one first port).","code":"new_app_process(app, port = c(\"3000r\", \"3001s\")) http <- new_app_process(app, port = c(\"0r\", \"0s\")) http$url(..., https = TRUE)"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"can-i-test-asynchronous-or-parallel-http-requests","dir":"Articles","previous_headings":"","what":"Can I test asynchronous or parallel HTTP requests?","title":"How to use webfakes in your tests","text":"R single threaded webfakes app runs R interpreter, process multiple requests time. web server runs separate thread, can also process request separate thread, time one request can use R interpreter. important, sometimes test requests may take longer process. example /delay/:secs end point httpbin_app() wait specified number seconds responding, simulate slow web server. wait implemented via standard Sys.sleep() R function, requests can processed sleep . avoid , webfakes can put waiting request hold, return R interpreter, respond incoming requests. Indeed, /delay/ end point implemented using feature. However, request thread web server still busy hold, take advantage , need allow multiple threads. num_threads argument $listen() method webfakes_app lets specify number request threads web server use. Similarly, num_threads argument local_app_process() lets modify number threads. testing asynchronous parallel code, might invoke multiple, possibly delayed requests, best increase number threads. code calls API request concurrently, three times. request takes 1 second answer, web server three threads, together ’ll still take 1 second. (http_version = 2 option currently needed bug (https://github.com/r-lib/webfakes/issues/108).)","code":"web <- webfakes::local_app_process( webfakes::httpbin_app(), opts = webfakes::server_opts(num_threads = 3) ) test_that(\"\", { url <- web$url(\"/delay/1\") p <- curl::new_pool() handles <- replicate(3, curl::new_handle(url = url, http_version = 2)) resps <- list() for (handle in handles) { curl::multi_add( handle, done = function(x) message(\"one is done\"), fail = stop, pool = p ) } st <- system.time(curl::multi_run(timeout = 5, pool = p)) print(st) expect_true(st[[\"elapsed\"]] < 3.0) }) #> one is done #> one is done #> one is done #> user system elapsed #> 0.002 0.001 1.039 #> Test passed 🎊"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-to-make-sure-that-my-code-works-with-the-real-api","dir":"Articles","previous_headings":"","what":"How to make sure that my code works with the real API?","title":"How to use webfakes in your tests","text":"Indeed, use webfakes test cases, never touch real web server. might suspect, ideal, especially control server. web service might change API, test cases fail warn . One practical solution write (least ) flexible tests, can run local fake webserver, real one, quick switch change behavior. found environment variables work great . E.g. FAKE_HTTP_TESTS environment variable set, tests run real web server, otherwise use fake one. Another solution, works best HTTP requests downstream package code, introduce one environment variable API need connect . might set real API servers, fake ones. tests can use kinds servers, can set continuous integration (CI) framework, run tests agains real server (say) day. special CI run makes sure code works well real API. can run tests, locally CI, fake local web server. See question webfakes helps setting environment variables point local server.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-simulate-a-slow-internet-connection","dir":"Articles","previous_headings":"","what":"How do I simulate a slow internet connection?","title":"How to use webfakes in your tests","text":"need use throttle server option start web app. means can run app different connection speed. goes: throttle gives number bytes per second, downloading 200 random bytes fake app take 2 seconds.","code":"library(webfakes) slow <- new_app_process( httpbin_app(), opts = server_opts(throttle = 100) ) resp <- curl::curl_fetch_memory(slow$url(\"/bytes/200\")) resp$times #> redirect namelookup connect pretransfer starttransfer #> 0.000000 0.000044 0.000186 0.000230 0.007567 #> total #> 2.008067"},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"why-civetwet","dir":"Articles","previous_headings":"","what":"Why civetwet?","title":"Webfakes internals","text":"Civetweb small simple. C code . Embedding trivial. main developer nice responsive. project active. code portable works OOB OSes tried. nice features built , e.g.Β limiting download speed.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"why-not-x","dir":"Articles","previous_headings":"","what":"Why not x?","title":"Webfakes internals","text":"httpuv alternative. heavier, contains libuv, also needs 7 non-core packages. AFAICT easy way delay response. also use libuv directly. difficult, probably need deal internals. .e. IOCPs, polls, etc. Libuv also HTTP, need implement use another library. also use R’s internal web server. means redefining default handlers help, fine, temporarily. internal web server limited, handles GET POST requests, give enough information requests. also support delaying response. Mongoose embedded web server, civetweb forked originally. license GPL-2, restrictive.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"multithreading","dir":"Articles","previous_headings":"","what":"Multithreading","title":"Webfakes internals","text":"Threads: main R thread. main web server thread. Request threads, created new connections. Web server worker threads. main rule main R thread can call function R API. civetweb callbacks run civetweb threads, call R API. Currently use begin_request() callback, called request threads. need synchronize request threads main R thread. essentially producer-consumer problem, single consumer, main R thread, multiple producers, request threads. single consumer means queue store jobs length one. good guide solve problem: https://docs.oracle.com/cd/E36784_01/html/E36868/sync-31.html need two conditions, signal 1) something work , 2) new work may come . also need mutex able wait conditions. stored user_data civetweb server instance: nextconn queue, used pass request request thread main R thread. request thread comes , make sure nextconn NULL, waits process_less. given green light, sets nextconn civetweb connection object, wait finish_cond condition, stored connection specific user data: main R thread can use user_data next_conn access information connection request. main R thread done processing request, sets connection’s req_todo field non-zero, signals connection’s finish_cond condition allow request thread continue. also signals process_less condition server, let request threads . Currently main R thread can set req_todo two different values. WEBFAKES_DONE means request processed, request thread can quit. requests like . WEBFAKES_WAIT means request thread still needs stay around sleep specified number secs. sleeping specified amount time, request thread signal process_more , notifying main R thread, also sets main_todo WEBFAKES_WAIT, main R thread knows new request. main R thread can just take stored request req field connection user data case.","code":"struct server_user_data { ... pthread_cond_t process_more; /* there is something to process */ pthread_cond_t process_less; /* we can process something */ pthread_mutex_t process_lock; struct mg_connection *nextconn; ... }; struct connection_user_data { pthread_cond_t finish_cond; /* can finish callback? */ pthread_mutex_t finish_lock; int main_todo; /* what should the main thread do? */ int req_todo; /* what shoudl the request thread do? */ double secs; /* how much should we wait? */ SEXP req; ... };"},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"error-handling","dir":"Articles","previous_headings":"","what":"Error handling","title":"Webfakes internals","text":"server running, errors must handled server must keep running.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"errors-while-starting-up","dir":"Articles","previous_headings":"Error handling","what":"Errors while starting up","title":"Webfakes internals","text":"caught re-thrown, civetweb error log added. error log typically contains information. E.g. common failure specified port free error log meaningful error message case.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"errors-in-request-handlers","dir":"Articles","previous_headings":"Error handling","what":"Errors in request handlers","title":"Webfakes internals","text":"Errors happen R request handler functions caught server send HTTP 500 response, R error message: response sent multiple pieces, possible status code headers sent already. case just send R error message.","code":"while (TRUE) { req <- server_poll(srv) tryCatch( self$.process_request(req), error = function(err) { cat(as.character(err), file = stderr()) response_send_error(req, as.character(err), 500L) } ) }"},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"errors-in-the-c-code-while-processing-the-request-or-response-","dir":"Articles","previous_headings":"Error handling","what":"Errors in the C code while processing the request or response.","title":"Webfakes internals","text":"Errors happen C code processing request response different, probably send anything meaningful client. E.g. frequent error happens connection breaks client closes connection. errors caught server_poll() response_*() R functions, printed screen (see server.R). originate civetweb, also logged civetweb error log. errors invalidate request, finish processing callback. implemented server.R functions (re)throwing webfakes_error, caught silently ignored processing loop. See β€˜Resource cleanup’ resources cleaned error.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"delayed-responses","dir":"Articles","previous_headings":"","what":"Delayed responses","title":"Webfakes internals","text":"See β€˜Multithreading’ section well. create req object incoming request, passing R C. object environment kept response request completely sent . (connection closed reason.) req object also added connection user data civetweb. Additionally, server keeps list (environment) request objects. latter makes sure request object garbage collected, don’t need worry . response delayed, app makes note position handler function handler stack (.stackptr), handler function can called , delay. calls response_write() sends WEBFAKES_WAIT message request thread. main R thread can continue processing potentially serving requests, assuming server started least two threads. wait, request thread sends message main R thread , app’s poll call get request object second (etc.) time. app starts calling handler functions recorded .stackptr position.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"interruption","dir":"Articles","previous_headings":"","what":"Interruption","title":"Webfakes internals","text":"server runs interrupted. (console remotely via processx::process$interrupt().) need make sure server can interrupted waiting new requests (.e.Β main R thread waiting process_more condition, see β€˜Multithreading’ ). pthread_cond_wait() interrupted SIGINT Unix, seemingly, Windows, need use pthread_cond_timedwait(). currently check interrupts every 50ms. server interrupted point, cleanup needed needed, hold resources. fact functions server.R keep server intact, delayed responses, possible call server_poll() . app$listen() method clean server case. Maybe change future. theory C code interrupted points. hand R API functions might error time, need proper cleanup everywhere, see β€˜Resource cleanup’ section . R code interrupted, server.R functions need cleanup. (theory error messages might get lost timing extremely unfortunate server.R function handling error interrupt happens.) app$listen() method cleans server .exit().","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"resource-cleanup","dir":"Articles","previous_headings":"","what":"Resource cleanup","title":"Webfakes internals","text":"See also β€˜Interruption’ just . points C code R errors happen code holding resources. use (copy ) cleancall package take care resource cleanup . first place server_poll() C function, request request thread. Creating R object request involves lot R API calls, one fails, need clean resources associated connection. (error logged R server_poll() function continue polling.) cleanup case involves: Sending WEBFAKES_DONE message request thread, quit. Removing request server’s list current requests. Signaling process_less condition, let threads know ready process requests. functions need cleanup C functions work response: response_delay(), response_send_headers(), response_send() response_write(). cleanups similar one server_poll(). finalizer server object takes care cleaning resources associated server, including request objects request threads. also called server_stop() R function, turn called .exit() listen() method. finalizer uses list requests tag xptr object walk requests, finish request threads. Thread sleeping delayed response frequently check server-wide shutdown flag, finalizer also sets, threads quit well. finalizer calls civetweb function mg_stop(). mg_stop() shutdown flag waits request worker threads quit. Given just cleaned , shouldn’t many, just coming , ’ll also observe shutdown flag quit quickly.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"what-is-webfakes","dir":"Articles","previous_headings":"","what":"What is webfakes?","title":"Happy HTTP testing with webfakes","text":"Webfakes R package can spin web servers machine facilitate testing R code. R code needs HTTP connection trivial test: Connectivity problems might prevent tests accessing web server. web server might need authentication, easy convey login information test suite secure way. web server might rate limits, .e, limits number queries per hour day, causing spurious test failures. might want test non-normal conditions, e.g.Β low bandwidth, client rate limited. conditions don’t normally happen web server hard trigger. webfakes can easily start custom web app, running local machine. Webfakes need network connection. Webfakes need authentication. Well, unless want . Webfakes rate limits. Webfakes can simulate low bandwidth, broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"webfakes-vs-mocking","dir":"Articles","previous_headings":"","what":"Webfakes vs mocking","title":"Happy HTTP testing with webfakes","text":"Mocking general technique mimic behavior function object needed test case. case HTTP requests, typically means request response recorded tests run first time, saved disk. Subsequent test runs intercept HTTP requests, match recorded requests replay corresponding recorded response. See example vcr httptest R packages. advantages using webfakes server, mocking: Simpler infrastructure. separate recording replaying phases, recorded files. request matching. can use web client want. E.g. curl base R’s HTTP functions explicitly support mocking currently. need worry sensitive information recorded requests responses. Often easier use testing non-normal conditions, e.g.Β errors hard trigger, low bandwidth, rate limits. Works stream data HTTP connection, instead reading whole response . can reuse app multiple tests, multiple packages. Easier use tests require multiple rounds requests. Comes built-https://httpbin.org compatible app, chances , don’t even need write testing app, just start writing tests right away. Better test writing experience. subjective, mileage may vary.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"webfakes-vs-the-real-api","dir":"Articles","previous_headings":"","what":"Webfakes vs the real API","title":"Happy HTTP testing with webfakes","text":"network needed. skip_if_offline(). Much faster. rate limits. can simulate one want . can write custom app. Simulate low bandwidth broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"webfakes-vs-httpbin-org","dir":"Articles","previous_headings":"","what":"Webfakes vs httpbin.org","title":"Happy HTTP testing with webfakes","text":"network needed. skip_if_offline(). Much faster. can use built-webfakes::httpbin_app() app, easy switch httpbin.org. can write custom app, httpbin.org might need.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"using-webfakeshttpbin_app-with-testthat","dir":"Articles","previous_headings":"","what":"Using webfakes::httpbin_app() with testthat","title":"Happy HTTP testing with webfakes","text":"can use testthat’s setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See β€œstart app writing tests?” just . can also create web server test file, even single test case. See vignette(\"-\") details .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed πŸ₯‡"},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"writing-apps","dir":"Articles","previous_headings":"","what":"Writing apps","title":"Happy HTTP testing with webfakes","text":"builtin httpbin_app() appropriate tests, can write app. can also extend httpbin_app() app, don’t want start scratch. create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:38937/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-13 21:26:36\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"the-oauth2-0-resource-and-authorization-server","dir":"Articles","previous_headings":"","what":"The OAuth2.0 resource and authorization server","title":"OAuth2.0 webfakes apps","text":"First need create resource server, also performs authorization, create variables holding different URLs.","code":"templog <- tempfile() rsapp <- new_app_process( oauth2_resource_app( refresh_duration = .Machine$integer.max, access_duration = 10L, ), opts = server_opts(num_threads = 3) ) regi_url <- rsapp$url(\"/register\") auth_url <- rsapp$url(\"/authorize\") toke_url <- rsapp$url(\"/token\") rsapp #> #> state: #> live #> auto_start: #> TRUE #> process id: #> 9317 #> http url: #> http://127.0.0.1:46005/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app #> get_ports() # query all ports of the app #> get_state() # query web server process state #> local_env(envvars) # set temporary environment variables #> start() # start the app #> url(path, query) # query url for an api path #> stop() # stop web server process #> # see ?webfakes_app_process for details"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"oauth2-0-app-creation-registration","dir":"Articles","previous_headings":"Fake third party application","what":"OAuth2.0 app creation & registration","title":"OAuth2.0 webfakes apps","text":"create third-party app, create variables holding different URLs. need register third-party app resource server. real life done admin third party app. fake resource server provides endpoint /register (regi_url) automatically, without user interaction. need send name third party app, redirect URL, query parameters. resource app replies client id client secret. ’ll use authenticate third party app. real life included config third party app admin. third party app API endpoint, /login/config (already conf_url) configure .","code":"tpapp <- new_app_process( oauth2_third_party_app(\"3P app\"), opts = server_opts(num_threads = 3) ) redi_url <- tpapp$url(\"/login/redirect\") conf_url <- tpapp$url(\"/login/config\") tpapp #> #> state: #> live #> auto_start: #> TRUE #> process id: #> 9330 #> http url: #> http://127.0.0.1:37423/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app #> get_ports() # query all ports of the app #> get_state() # query web server process state #> local_env(envvars) # set temporary environment variables #> start() # start the app #> url(path, query) # query url for an api path #> stop() # stop web server process #> # see ?webfakes_app_process for details url <- paste0( regi_url, \"?name=3P%20app\", \"&redirect_uri=\", redi_url ) reg_resp <- httr::GET(url) reg_resp #> Response [http://127.0.0.1:46005/register?name=3P%20app&redirect_uri=http://127.0.0.1:37423/login/redirect] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 184 B regdata <- httr::content(reg_resp) regdata #> $name #> $name[[1]] #> [1] \"3P app\" #> #> #> $client_id #> $client_id[[1]] #> [1] \"id-95d943f6c6dd352a1cb06ad05f3ac5\" #> #> #> $client_secret #> $client_secret[[1]] #> [1] \"secret-6c96fbab12fc439eea5a0dd05206ef\" #> #> #> $redirect_uri #> $redirect_uri[[1]] #> [1] \"http://127.0.0.1:37423/login/redirect\" auth_data <- list( auth_url = auth_url, token_url = toke_url, client_id = regdata$client_id[[1]], client_secret = regdata$client_secret[[1]] ) httr::POST( conf_url, body = auth_data, encode = \"json\" ) #> Response [http://127.0.0.1:37423/login/config] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 41 B"},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"the-oauth2-0-dance","dir":"Articles","previous_headings":"Fake third party application","what":"The OAuth2.0 dance","title":"OAuth2.0 webfakes apps","text":"Now user can go login URL third party app, /login fake app, authenticate. start web page R, can run third party app now token, can use authenticate resource app. See test-oauth.R file within webfakes see part programmatically, without browser. default fake third party app saves token(s) local variable, also returns JSON, can see browser: want change behavior, can define redirect_hook function third party app. example: can authenticate new app fake third party app also endpoint return saved tokens: Now third-party app can get data behalf (whole goal OAuth!) β€” also post data behalf resource app endpoints . example /data endpoint third party app queries resource app needs authentication. run following without OAuth dance, access denied. now works fine: real life, access third-party app might limited scopes, fake apps shipped webfakes handle .","code":"browseURL(tpapp$url(\"/login\")) #> { #> \"access_token\": \"token-c6be45eee35844e7ec1d6ada44bc15\", #> \"expiry\": 10, #> \"refresh_token\": \"refresh-token-ee3f1285a6f4585e9f410375e0512d\" #> } thirdp <- oauth2_third_party_app(\"3P app\") thirdp$redirect_hook <- function(res, tokens) { res$ set_status(200L)$ send(\"Authentication complete, return to R!\") } tpapp2 <- new_app_process( thirdp, opts = server_opts(num_threads = 3) ) redi_url2 <- tpapp2$url(\"/login/redirect\") conf_url2 <- tpapp2$url(\"/login/config\") tpapp2 #> #> state: #> live #> auto_start: #> TRUE #> process id: #> 9344 #> http url: #> http://127.0.0.1:34853/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app #> get_ports() # query all ports of the app #> get_state() # query web server process state #> local_env(envvars) # set temporary environment variables #> start() # start the app #> url(path, query) # query url for an api path #> stop() # stop web server process #> # see ?webfakes_app_process for details url2 <- paste0( regi_url, \"?name=3P%20app2\", \"&redirect_uri=\", redi_url2 ) reg_resp2 <- httr::GET(url2) reg_resp2 #> Response [http://127.0.0.1:46005/register?name=3P%20app2&redirect_uri=http://127.0.0.1:34853/login/redirect] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 185 B regdata2 <- httr::content(reg_resp2) regdata2 #> $name #> $name[[1]] #> [1] \"3P app2\" #> #> #> $client_id #> $client_id[[1]] #> [1] \"id-0718e622548334e24b394eb649ffe7\" #> #> #> $client_secret #> $client_secret[[1]] #> [1] \"secret-7fb7b914231f5398b7007277812415\" #> #> #> $redirect_uri #> $redirect_uri[[1]] #> [1] \"http://127.0.0.1:34853/login/redirect\" auth_data2 <- list( auth_url = auth_url, token_url = toke_url, client_id = regdata2$client_id[[1]], client_secret = regdata2$client_secret[[1]] ) httr::POST( conf_url2, body = auth_data2, encode = \"json\" ) #> Response [http://127.0.0.1:34853/login/config] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 41 B browseURL(tpapp2$url(\"/login\")) #> Authentication complete, return to R! httr::content(httr::GET(tpapp2$url(\"/locals\"))) #> $access_token #> [1] \"token-08e1470fb2bbfa9216925390655281\" #> #> $expiry #> [1] 10 #> #> $refresh_token #> [1] \"refresh-token-f70b06b589156b9b5d462b040c500c\" resp_data <- httr::GET(tpapp2$url(\"/data\")) resp_data #> Response [http://127.0.0.1:34853/data] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 24 B httr::content(resp_data, as = \"text\") #> No encoding supplied: defaulting to UTF-8. #> [1] \"{\\\"data\\\":[\\\"top secret!\\\"]}\""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"the-fake-resource-server-and-httr","dir":"Articles","previous_headings":"","what":"The fake resource server and httr","title":"OAuth2.0 webfakes apps","text":"use httr’s OAuth tool, ’s gymnastics happening R playing role third-party app via httr httpuv (listen redirect URI).","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"oauth2-0-app-creation-registration-1","dir":"Articles","previous_headings":"The fake resource server and httr","what":"OAuth2.0 app creation & registration","title":"OAuth2.0 webfakes apps","text":"’s crucial setting httr::oauth_callback() redirect URI, creating app R package uses OAuth2.0 authenticate resource server. Now set registration data third-party app. Now can launch token creation. Without token, query resource server fails: token, successful. httr also automatically refreshes token needed.","code":"url3 <- paste0( regi_url, \"?name=3P%20app2\", \"&redirect_uri=\", httr::oauth_callback() ) reg_resp3 <- httr::GET(url3) reg_resp3 #> Response [http://127.0.0.1:46005/register?name=3P%20app2&redirect_uri=http://localhost:1410/] #> Date: 2025-01-13 21:26 #> Status: 200 #> Content-Type: application/json #> Size: 170 B regdata3 <- httr::content(reg_resp3) regdata3 #> $name #> $name[[1]] #> [1] \"3P app2\" #> #> #> $client_id #> $client_id[[1]] #> [1] \"id-cad660ce51543ffee2d7e4d3639af8\" #> #> #> $client_secret #> $client_secret[[1]] #> [1] \"secret-927663c2f2cdec693f52b8caaa192c\" #> #> #> $redirect_uri #> $redirect_uri[[1]] #> [1] \"http://localhost:1410/\" app <- httr::oauth_app( \"3P app2\", key = regdata3$client_id[[1]], secret = regdata3$client_secret[[1]], redirect_uri = httr::oauth_callback() ) endpoint <- httr::oauth_endpoint( authorize = auth_url, access = toke_url ) token <- oauth2_httr_login( httr::oauth2.0_token(endpoint, app, cache = FALSE) ) #> Waiting for authentication in browser... #> Press Esc/Ctrl + C to abort #> Authentication complete. token #> #> #> authorize: http://127.0.0.1:46005/authorize #> access: http://127.0.0.1:46005/token #> 3P app2 #> key: id-cad660ce51543ffee2d7e4d3639af8 #> secret: #> access_token, expiry, refresh_token #> --- httr::GET(rsapp$url(\"/data\")) #> Response [http://127.0.0.1:46005/data] #> Date: 2025-01-13 21:26 #> Status: 401 #> Content-Type: text/plain #> Size: 20 B httr::content( httr::GET(rsapp$url(\"/data\"), config = token), as = \"text\" ) #> No encoding supplied: defaulting to UTF-8. #> [1] \"{\\\"data\\\":[\\\"top secret!\\\"]}\""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"applications","dir":"Articles","previous_headings":"Advanced topics","what":"Applications","title":"OAuth2.0 webfakes apps","text":"apps, resource server app, can now test code helps users create store OAuth2.0 token. Like webfakes apps, OAuth2.0 apps extensible: can add endpoints middleware . E.g. add logging via mw_log() new endpoint. want customize one apps apps lot, might make sense use source code starting point inspiration.","code":"rsapp2 <- oauth2_resource_app( refresh_duration = .Machine$integer.max, access_duration = 10L ) logfile <- tempfile(\"oauth-rs-\", fileext = \".log\") rsapp2$use(mw_log(stream = logfile), .first = TRUE) rsapp2$get(\"/docs\", function(req, res) { res$ set_status(200L)$ send(\"See vignette('oauth', package = 'webfakes')\") }) rsapp2_process <- new_app_process( rsapp2, opts = server_opts(num_threads = 3) )"},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"debugging","dir":"Articles","previous_headings":"Advanced topics","what":"Debugging","title":"OAuth2.0 webfakes apps","text":"See usual debugging advice webfakes apps. particular, can add mw_log() middleware write log app file, like . resource app /locals endpoint, returns data stored app, includes tokens refresh tokens:","code":"httr::content( httr::GET(rsapp$url(\"/locals\")) ) #> $apps #> $apps[[1]] #> $apps[[1]]$name #> [1] \"3P app\" #> #> $apps[[1]]$client_id #> [1] \"id-95d943f6c6dd352a1cb06ad05f3ac5\" #> #> $apps[[1]]$client_secret #> [1] \"secret-6c96fbab12fc439eea5a0dd05206ef\" #> #> $apps[[1]]$redirect_uri #> [1] \"http://127.0.0.1:37423/login/redirect\" #> #> #> $apps[[2]] #> $apps[[2]]$name #> [1] \"3P app2\" #> #> $apps[[2]]$client_id #> [1] \"id-0718e622548334e24b394eb649ffe7\" #> #> $apps[[2]]$client_secret #> [1] \"secret-7fb7b914231f5398b7007277812415\" #> #> $apps[[2]]$redirect_uri #> [1] \"http://127.0.0.1:34853/login/redirect\" #> #> #> $apps[[3]] #> $apps[[3]]$name #> [1] \"3P app2\" #> #> $apps[[3]]$client_id #> [1] \"id-cad660ce51543ffee2d7e4d3639af8\" #> #> $apps[[3]]$client_secret #> [1] \"secret-927663c2f2cdec693f52b8caaa192c\" #> #> $apps[[3]]$redirect_uri #> [1] \"http://localhost:1410/\" #> #> #> #> $access #> $access[[1]] #> $access[[1]]$client_id #> [1] \"id-95d943f6c6dd352a1cb06ad05f3ac5\" #> #> $access[[1]]$token #> [1] \"token-c6be45eee35844e7ec1d6ada44bc15\" #> #> $access[[1]]$expiry #> [1] \"2025-01-13 21:26:51\" #> #> #> $access[[2]] #> $access[[2]]$client_id #> [1] \"id-0718e622548334e24b394eb649ffe7\" #> #> $access[[2]]$token #> [1] \"token-08e1470fb2bbfa9216925390655281\" #> #> $access[[2]]$expiry #> [1] \"2025-01-13 21:26:51\" #> #> #> $access[[3]] #> $access[[3]]$client_id #> [1] \"id-cad660ce51543ffee2d7e4d3639af8\" #> #> $access[[3]]$token #> [1] \"token-1f46a0366717828ac5cc842c163a31\" #> #> $access[[3]]$expiry #> [1] \"2025-01-13 21:26:52\" #> #> #> #> $refresh #> $refresh[[1]] #> $refresh[[1]]$client_id #> [1] \"id-95d943f6c6dd352a1cb06ad05f3ac5\" #> #> $refresh[[1]]$token #> [1] \"refresh-token-ee3f1285a6f4585e9f410375e0512d\" #> #> $refresh[[1]]$expiry #> [1] \"2093-02-01 00:40:48\" #> #> #> $refresh[[2]] #> $refresh[[2]]$client_id #> [1] \"id-0718e622548334e24b394eb649ffe7\" #> #> $refresh[[2]]$token #> [1] \"refresh-token-f70b06b589156b9b5d462b040c500c\" #> #> $refresh[[2]]$expiry #> [1] \"2093-02-01 00:40:48\" #> #> #> $refresh[[3]] #> $refresh[[3]]$client_id #> [1] \"id-cad660ce51543ffee2d7e4d3639af8\" #> #> $refresh[[3]]$token #> [1] \"refresh-token-81d2b2f09bcf64302605ab6a9750b3\" #> #> $refresh[[3]]$expiry #> [1] \"2093-02-01 00:40:49\""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"case-study-for-oauth2-0-testing","dir":"Articles","previous_headings":"","what":"Case study for OAuth2.0 testing","title":"OAuth2.0 webfakes apps","text":"Consider package function uses OAuth2.0 access GitHub. section ’ll show can use webfakes test function. gh_repos() uses OAuth2.0 app get token GitHub, uses token authenticate list public repositories current user. real package probably get token separate function, cache , re-use multiple queries. (Also, don’t actually need authorization listing public repositories, somewhat artificial example.) run function, need register app https://github.com/settings/developers. Make sure set http://localhost:1410 authorization callback URL. can set options please. R set GH_CLIENT_ID GH_CLIENT_SECRET environment variables client id client secret app: run function opens Window browser, can authorize app access public information GitHub. subsequent runs browser window still opens, authorization automatic. real package cache OAuth2.0 tokens machine, e.g.Β using keyring package. Let’s write test case now gh_repos(). use oauth2_repource_app() fake GitHub. important points test case: Since test case fail port 1410 taken, safer skip CRAN. fake_app webfakes app, almost oauth2_resource_app(), two changes. first change end point getting token /access_token GitHub uses. second also add /user/repos endpoint. endpoint needs authorization, calls app$is_authorized(), fails without , app returns 401 Access denied. fake_proc app process runs fake GH app. Always run fake OAuth2.0 apps multiple threads. fake GitHub app endpoint register app testing. need send app’s name correct redirect URI, fake_app reply fake client id client secret. set GH_CLIENT_ID GH_CLIENT_SECRET fake ones just got fake_app. also redirect gh_token() fake app, setting FAKE_GH_API_BASE FAKE_GH_AUTH_BASE. Now call gh_repos(), connect fake app. also need make sure httr can log fake app, without browser interaction. webfakes::oauth2_httr_login() wrapper takes care . runs HTTP client background process, perform log . background process webfakes::oauth2_httr_login() fails log reason, test code might freeze. another good reason skip test CRAN. suppressMessages() suppresses (harmless) httr messages authorization. test case lot boilerplate set manage fake apps. Note can refactor code helper function, starts fake app sub-process demand helper file. way can reuse multiple test files test cases. test case ensures OAuth2.0 setup gh_repos() stays correct.","code":"gh_base <- function() { Sys.getenv(\"FAKE_GH_API_BASE\", \"https://api.github.com\") } gh_oauth_base <- function() { Sys.getenv( \"FAKE_GH_AUTH_BASE\", \"https://github.com/login/oauth\" ) } gh_repos <- function() { ghapp <- httr::oauth_app( \"gh_repos\", key = Sys.getenv(\"GH_CLIENT_ID\"), secret = Sys.getenv(\"GH_CLIENT_SECRET\") ) endpoints <- httr::oauth_endpoint( base_url = gh_oauth_base(), request = NULL, authorize = \"authorize\", access = \"access_token\" ) gh_token <- httr::oauth2.0_token( endpoints, ghapp, cache = FALSE ) httr_token <- httr::config(token = gh_token) response <- httr::GET( paste0(gh_base(), \"/user/repos?visibility=public\"), httr::add_headers(Accept = \"application/vnd.github.v3+json\"), config = httr_token ) httr::stop_for_status(response) json <- httr::content(response, as = \"text\") repos <- jsonlite::fromJSON(json, simplifyVector = FALSE) vapply(repos, function(r) r$full_name, character(1)) } Sys.setenv(GH_CLIENT_ID = \"\") Sys.setenv(GH_CLIENT_SECRET = \"\") gh_repos() #> Waiting for authentication in browser... #> Press Esc/Ctrl + C to abort #> Authentication complete. #> [1] \"gaborcsardi/altlist\" \"gaborcsardi/argufy\" #> [3] \"gaborcsardi/async\" \"gaborcsardi/disposables\" #> [5] \"gaborcsardi/dotenv\" \"gaborcsardi/falsy\" #> [7] \"gaborcsardi/franc\" \"gaborcsardi/ISA\" #> [9] \"gaborcsardi/keypress\" \"gaborcsardi/lpSolve\" #> [11] \"gaborcsardi/macBriain\" \"gaborcsardi/maxygen\" #> [13] \"gaborcsardi/MISO\" \"gaborcsardi/msgtools\" #> [15] \"gaborcsardi/notifier\" \"gaborcsardi/odbc\" #> [17] \"gaborcsardi/parr\" \"gaborcsardi/parsedate\" #> [19] \"gaborcsardi/prompt\" \"gaborcsardi/r-font\" #> [21] \"gaborcsardi/r-source\" \"gaborcsardi/rcorpora\" #> [23] \"gaborcsardi/roxygenlabs\" \"gaborcsardi/sankey\" #> [25] \"gaborcsardi/secret\" \"gaborcsardi/spark\" #> [27] \"gaborcsardi/standalones\" testthat::test_that(\"gh_repos\", { testthat::skip_on_cran() fake_app <- oauth2_resource_app(token_endpoint = \"/access_token\") fake_app$get(\"/user/repos\", function(req, res) { if (!app$is_authorized(req, res)) return() res$send_json(list( list(full_name = \"user/repo1\"), list(full_name = \"user/repo2\") ), auto_unbox = TRUE) }) fake_proc <- local_app_process( fake_app, opts = server_opts(num_threads = 3) ) # register the app to our fake GH server reg_url <- paste0( fake_proc$url(\"/register\"), \"?name=gh_repos&redirect_uri=http://localhost:1410/\" ) regdata <- httr::content(httr::GET(reg_url)) withr::local_envvar( GH_CLIENT_ID = regdata$client_id[[1]], GH_CLIENT_SECRET = regdata$client_secret[[1]], FAKE_GH_API_BASE = fake_proc$url(), FAKE_GH_AUTH_BASE = fake_proc$url() ) ret <- suppressMessages(webfakes::oauth2_httr_login( gh_repos() )) testthat::expect_equal(ret, c(\"user/repo1\", \"user/repo2\")) }) #> Test passed πŸŽ‰"},{"path":"https://webfakes.r-lib.org/dev/authors.html","id":null,"dir":"","previous_headings":"","what":"Authors","title":"Authors and Citation","text":"GΓ‘bor CsΓ‘rdi. Author, maintainer. . Copyright holder, funder. Civetweb contributors. Contributor. see inst/credits/ciwetweb.md Redoc contributors. Contributor. see inst/credits/redoc.md L. Peter Deutsch. Contributor. src/md5.h Martin Purschke. Contributor. src/md5.h Aladdin Enterprises. Copyright holder. src/md5.h MaΓ«lle Salmon. Contributor.","code":""},{"path":"https://webfakes.r-lib.org/dev/authors.html","id":"citation","dir":"","previous_headings":"","what":"Citation","title":"Authors and Citation","text":"CsΓ‘rdi G (2025). webfakes: Fake Web Apps HTTP Testing. R package version 1.3.2.9000, https://github.com/r-lib/webfakes, https://webfakes.r-lib.org/.","code":"@Manual{, title = {webfakes: Fake Web Apps for HTTP Testing}, author = {GΓ‘bor CsΓ‘rdi}, year = {2025}, note = {R package version 1.3.2.9000, https://github.com/r-lib/webfakes}, url = {https://webfakes.r-lib.org/}, }"},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"webfakes","dir":"","previous_headings":"","what":"Fake Web Apps for HTTP Testing","title":"Fake Web Apps for HTTP Testing","text":"web server happy HTTP testing Lightweight fake web apps testing. Built using civetweb embedded web server.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"features","dir":"","previous_headings":"","what":"Features","title":"Fake Web Apps for HTTP Testing","text":"Complete web app framework, define handlers HTTP requests R. Write app custom test cases; use app similar https://httpbin.org API, often don’t need write web app (e.g.Β writing HTTP client (httr, curl, crul). Run one web app per test suite, per test file per test case. Flexible path matching, parameters regular expressions. Built templating system using glue bring template engine. Middleware parse JSON, multipart URL encoded request bodies. web app just R object. can saved disk, copied another R process, etc. web app extensible, adding new routes middleware . Helper functions sending JSON, files disk, etc. App-specific environment store data including data requests fake app. web app launched R, can interact R also command line, browser, etc. Nice debugging. web server runs R process, problems local firewalls. Multi-threaded web server supports concurrent HTTP requests. Limit download speed simulate low bandwidth.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"optional-dependencies","dir":"","previous_headings":"","what":"Optional dependencies","title":"Fake Web Apps for HTTP Testing","text":"jsonlite package needed mw_json() middleware, response$send_json() method httpbin_app() app. glue package needed tmpl_glue() template engine. callr package needed new_app_process() local_app_process work. /brotli endpoint httpbin_app() needs brotli package. /deflate endpoint httpbin_app() needs zip package. /digest-auth endpoint httpbin_app() needs digest package. git_app() requires processx package.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"installation","dir":"","previous_headings":"","what":"Installation","title":"Fake Web Apps for HTTP Testing","text":"Install release version CRAN: need development version package, install GitHub:","code":"install.packages(\"webfakes\") pak::pak(\"r-lib/webfakes\")"},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"usage","dir":"","previous_headings":"","what":"Usage","title":"Fake Web Apps for HTTP Testing","text":"Start web app beginning tests test file, stop . example testthat package. Suppose want test get_hello() function can query API: local_app_process() helps clean web server process test block, test file. similar withr::local_* functions. testing HTTP clients can often use built httpbin_app():","code":"app <- webfakes::new_app() app$get(\"/hello/:user\", function(req, res) { res$send(paste0(\"Hello \", req$params$user, \"!\")) }) web <- webfakes::local_app_process(app) test_that(\"can use hello API\", { url <- web$url(\"/hello/Gabor\") expect_equal(get_hello(url), \"Hello Gabor!\") }) httpbin <- webfakes::local_app_process(webfakes::httpbin_app()) test_that(\"HTTP errors are caught\", { url <- httpbin$url(\"/status/404\") resp <- httr::GET(url) expect_error(httr::stop_for_status(resp), class = \"http_404\") }) #> Test passed 😸"},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"documentation","dir":"","previous_headings":"","what":"Documentation","title":"Fake Web Apps for HTTP Testing","text":"See https://webfakes.r-lib.org","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"other-solutions-for-http-testing-in-r","dir":"","previous_headings":"Links","what":"Other solutions for HTTP testing in R:","title":"Fake Web Apps for HTTP Testing","text":"vcr httptest","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"r-web-application-frameworks","dir":"","previous_headings":"Links","what":"R web application frameworks","title":"Fake Web Apps for HTTP Testing","text":"webfakes focuses testing, packages writing real web apps: shiny opencpu plumber fiery RestRserve","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"code-of-conduct","dir":"","previous_headings":"","what":"Code of Conduct","title":"Fake Web Apps for HTTP Testing","text":"Please note webfakes project released Contributor Code Conduct. contributing project, agree abide terms.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"license","dir":"","previous_headings":"","what":"License","title":"Fake Web Apps for HTTP Testing","text":"MIT Β© RStudio","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Web app that acts as a git http server β€” git_app","title":"Web app that acts as a git http server β€” git_app","text":"useful tests need HTTP git server.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Web app that acts as a git http server β€” git_app","text":"","code":"git_app( git_root, git_cmd = \"git\", git_timeout = as.difftime(1, units = \"mins\"), filter = TRUE, cleanup = TRUE )"},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Web app that acts as a git http server β€” git_app","text":"git_root Path root directory tree served. git_cmd Command call, default \"git\". may also full path git. git_timeout difftime object, time limit git command. filter Whether support filter capability server. cleanup Whether clean git_root app garbage collected.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Web app that acts as a git http server β€” git_app","text":"","code":"if (FALSE) { dir.create(tmp <- tempfile()) setwd(tmp) system(\"git clone --bare https://github.com/cran/crayon\") system(\"git clone --bare https://github.com/cran/glue\") app <- git_app(tmp) git <- new_app_process(app) system(paste(\"git ls-remote\", git$url(\"/crayon\"))) }"},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":null,"dir":"Reference","previous_headings":"","what":"webfakes glossary β€” glossary","title":"webfakes glossary β€” glossary","text":"webfakes glossary","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"webfakes-glossary","dir":"Reference","previous_headings":"","what":"Webfakes glossary","title":"webfakes glossary β€” glossary","text":"webfakes package uses vocabulary standard web apps, especially developed Express.js, necessarily well known R package developers.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"app","dir":"Reference","previous_headings":"","what":"app","title":"webfakes glossary β€” glossary","text":"(Also: fake web app, webfakes app.) web application can served webfakes's web server, typically another process, app process. Sometimes call fake web app, emphasize use testing real web apps APIs. can create webfakes app new_app() function. webfakes app R object can save disk saveRDS() , can also include package. can start $listen() method. Since main R process runs test suite code, usually run subprocess, see new_app_process() local_app_process().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"app-process","dir":"Reference","previous_headings":"","what":"app process","title":"webfakes glossary β€” glossary","text":"(Also: web server process, webfakes subprocess.) app process R subprocess, started main R process, serve webfakes app. can create app process object new_app_process() local_app_process(). default actual process start yet, create . can start explicitly $start method app process object, querying URL $url() port $get_port(). test cases, typically start app processes places: setup*.R file, start app whole test suite can use. Alternatively, helper*.R file, start app whole test suite can use, works better interactive development. beginning test file, create app single test file. Inside test_that(), create app single test block. See -details .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"handler","dir":"Reference","previous_headings":"","what":"handler","title":"webfakes glossary β€” glossary","text":"(handler function.) handler route middleware.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"handler-stack","dir":"Reference","previous_headings":"","what":"handler stack","title":"webfakes glossary β€” glossary","text":"stack handler functions, called app one , passing request response objects . Handlers typically manipulate request /response objects. terminal handler instructs app return response HTTP client. non-terminal handler tells app keep calling handlers, returning string \"next\".","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"httpbin-app","dir":"Reference","previous_headings":"","what":"httpbin app","title":"webfakes glossary β€” glossary","text":"example app, implements excellent https://httpbin.org/ web service. can use simulate certain HTTP responses. handy HTTP clients, potentially useful tools well. Use httpbin_app() create instance app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"middleware","dir":"Reference","previous_headings":"","what":"middleware","title":"webfakes glossary β€” glossary","text":"middleware handler function bound path. called router, like handler functions. may manipulate request response, can side effect. example built-middleware functions webfakes: mw_json() parses request's JSON body R object. mw_log() logs requests responses screen file. mw_static() serves static files directory. can also write middleware functions.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"path-matching","dir":"Reference","previous_headings":"","what":"path matching","title":"webfakes glossary β€” glossary","text":"router performs path matching goes handler stack. HTTP method path route match HTTP method URL request, handler called, otherwise . Paths can parameters regular expressions. See ?new_regexp() regular expressions \"Path parameters\" ?new_app() parameters.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"route","dir":"Reference","previous_headings":"","what":"route","title":"webfakes glossary β€” glossary","text":"route handler function bound certain paths web app. request URL matches path route, handler function called, give chance send appropriate response. Route paths may parameters can regular expressions webfakes.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"routing","dir":"Reference","previous_headings":"","what":"routing","title":"webfakes glossary β€” glossary","text":"Routing process going handlers stack, calling handler functions, one , one handles request. handler function route, router calls path matches request URL.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":null,"dir":"Reference","previous_headings":"","what":"How to use webfakes in your tests β€” how-to","title":"How to use webfakes in your tests β€” how-to","text":"use webfakes tests","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-use-webfakes-in-my-package-","dir":"Reference","previous_headings":"","what":"How do I use webfakes in my package?","title":"How to use webfakes in your tests β€” how-to","text":"First, need add webfakes DESCRIPTION file package. Use Suggests field, webfakes needed testing: , unless URL web service argument package functions, might need tweak package code slightly make sure every call real web service can targeted another URL instead (fake app). See next subsection. Last least, need decide want single web app test cases. alternative use different apps test files. Occasionally may want use special app single test case. app runs new subprocess, takes typically 100-400ms start. See sections later writing tests single app multiple apps.","code":"... Suggests: webfakes, testthat ..."},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-make-my-app-connect-to-webfakes-when-the-tests-are-running-","dir":"Reference","previous_headings":"","what":"How do I make my app connect to webfakes when the tests are running?","title":"How to use webfakes in your tests β€” how-to","text":"typical scenario, want package connect test app running tests. URL web service argument functions, one way achieve allow specifying web server URL(s) via environment variables. E.g. writing GitHub API client, package can check use GITHUB_URL environment variable. E.g. set, package connects proper GitHub API. testing, can point test app. new_app_process() helps setting temporary environment variables. active process running, removed reset $stop(). example: $local_env() environment variables, webfakes replaces {url} actual app URL. needed default, web server process starts later, URL known yet.","code":"service_url <- function() { Sys.getenv(\"GITHUB_URL\", \"https://api.github.com\") } # rest of the package code foobar <- function() { httr::GET(service_url()) } http <- webfakes::local_app_process(webfakes::httpbin_app(), start = TRUE) http$local_env(list(GITHUB_API = \"{url}\")) Sys.getenv(\"GITHUB_API\") #> [1] \"http://127.0.0.1:59481/\" http$stop() Sys.getenv(\"GITHUB_API\") #> [1] \"\""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-write-my-own-app-","dir":"Reference","previous_headings":"","what":"How can I write my own app?","title":"How to use webfakes in your tests β€” how-to","text":"create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:59482/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-12 02:08:03\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-use-httpbin-app-or-another-app-with-testthat-","dir":"Reference","previous_headings":"","what":"How do I use httpbin_app() (or another app) with testthat?","title":"How to use webfakes in your tests β€” how-to","text":"can use testthat's setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See \"start app writing tests?\" just .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-start-the-app-when-writing-the-tests-","dir":"Reference","previous_headings":"","what":"How do I start the app when writing the tests?","title":"How to use webfakes in your tests β€” how-to","text":"convenient start webfakes server process(es) working tests interactively, e.g. using devtools::load_all(). local_app_process() testthat setup*.R file automatic, devtools::load_all() run files. need source setup*.R files manually, error prone. One solution create server processes testthat helper*.R files. load_all() executes helper files default. instead using setup file, can simply helper-http.R file: app process created helper file, ready use load_all(), (default) actual process started first $url() $get_port() call. can also start manually $start(). Processes created helper files cleaned automatically end test suite, unless clean registering $stop() call setup file, like : practice necessary, R CMD check runs tests separate process, finishes, webfakes processes cleaned well. running devtools::test(), testthat::test_local() another testthat function run (part ) test suite current session, helper*.R files (re)loaded first. terminate currently running app processes, , create new app process objects. test suite auto-start test processes helper*.R, cleaned end test suite, next load_all() test() call, end R session. lets run test code interactively, either via test() manually, without thinking much webfakes processes.","code":"httpbin <- local_app_process(httpbin_app()) withr::defer(httpbin$stop(), testthat::teardown_env())"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"can-i-have-an-app-for-a-single-testthat-test-file-","dir":"Reference","previous_headings":"","what":"Can I have an app for a single testthat test file?","title":"How to use webfakes in your tests β€” how-to","text":"run web app single test file, start new_app_process() beginning file, register cleanup using withr::defer(). Even simpler, use local_app_process() new_app_process() automatically stops web server process, end test file: test cases, use web$url() get URL connect .","code":"app <- webfakes::new_app() app$get(\"/hello/:user\", function(req, res) { res$send(paste0(\"Hello \", req$params$user, \"!\")) }) web <- webfakes::local_app_process(app) test_that(\"can use hello API\", { url <- web$url(\"/hello/Gabor\") expect_equal(httr::content(httr::GET(url)), \"Hello Gabor!\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"can-i-use-an-app-for-a-single-testthat-test-","dir":"Reference","previous_headings":"","what":"Can I use an app for a single testthat test?","title":"How to use webfakes in your tests β€” how-to","text":"Sure. need create app process within testthat::test_that() test case. local_app_process() automatically cleans end block. goes like :","code":"test_that(\"query works\", { app <- webfakes::new_app() app$get(\"/hello\", function(req, res) res$send(\"hello there\")) web <- webfakes::local_app_process(app) echo <- httr::content(httr::GET(web$url(\"/hello\"))) expect_equal(echo, \"hello there\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-test-a-sequence-of-requests-","dir":"Reference","previous_headings":"","what":"How do I test a sequence of requests?","title":"How to use webfakes in your tests β€” how-to","text":"test sequence requests, app needs state information kept requests. app$locals environment belongs app, can used record information retrieve future requests. store anything app$locals, something simple like counter variable, something fancier like sqlite database. can add something app$locals via methods directly creating app. E.g. end point fails three times, succeeds , fails three times, etc. Note counter created code starts 0, 1. run app another process connect : Another example send information app retrieve . POST request store name query parameter app$locals$packages, can queried GET request. Now start app subprocess, run GET query . POST new information. Stop app process:","code":"store <- webfakes::new_app() store$locals$packages <- list(\"webfakes\") ls(store$locals) #> [1] \"packages\" store$locals$packages #> [[1]] #> [1] \"webfakes\" flaky <- webfakes::new_app() flaky$get(\"/unstable\", function(req, res) { if (identical(res$app$locals$counter, 3L)) { res$app$locals$counter <- NULL res$send_json(object = list(result = \"ok\")) } else { res$app$locals$counter <- c(res$app$locals$counter, 0L)[[1]] + 1L res$send_status(401) } }) pr <- webfakes::new_app_process(flaky) url <- pr$url(\"/unstable\") httr::RETRY(\"GET\", url, times = 4) #> Request failed [401]. Retrying in 1.6 seconds... #> Request failed [401]. Retrying in 1.8 seconds... #> Request failed [401]. Retrying in 3.4 seconds... #> Response [http://127.0.0.1:59492/unstable] #> Date: 2025-01-12 02:08 #> Status: 200 #> Content-Type: application/json #> Size: 17 B store <- webfakes::new_app() # Initial \"data\" for the app store$locals$packages <- list(\"webfakes\") # Get method store$get(\"/packages\", function(req, res) { res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) # Post method, store information from the query store$post(\"/packages\", function(req, res) { res$app$locals$packages <- c(res$app$locals$packages, req$query$name) res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) web <- webfakes::local_app_process(store, start = TRUE) # Get current information get_packages <- function() { httr::content( httr::GET( httr::modify_url( web$url(), path = \"packages\" ) ) ) } get_packages() #> [[1]] #> [1] \"webfakes\" post_package <- function(name) { httr::POST( httr::modify_url( web$url(), path = \"packages\", query = list(name = name) ) ) } post_package(\"vcr\") #> Response [http://127.0.0.1:59498/packages?name=vcr] #> Date: 2025-01-12 02:08 #> Status: 200 #> Content-Type: application/json #> Size: 18 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" post_package(\"httptest\") #> Response [http://127.0.0.1:59498/packages?name=httptest] #> Date: 2025-01-12 02:08 #> Status: 200 #> Content-Type: application/json #> Size: 29 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" #> #> [[3]] #> [1] \"httptest\" web$stop()"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-debug-an-app-","dir":"Reference","previous_headings":"","what":"How can I debug an app?","title":"How to use webfakes in your tests β€” how-to","text":"debug app, best run main R process, .e. via new_app_process(). can add breakpoints, browser() calls handler functions, invoke app another process. might find curl command line tool send HTTP requests app, can just use another R process. example. simply print incoming request object screen now. real debugging session probably want place browser() command . Now start app port 3000: Connect app another R curl process: main R session print incoming request: Press CTRL+C ESC interrupt app main session.","code":"app <- webfakes::new_app() app$get(\"/debug\", function(req, res) { print(req) res$send(\"Got your back\") }) app$listen(port = 3000) #> Running webfakes web app on port 3000 curl -v http://127.0.0.1:3000/debug #> * Trying 127.0.0.1... #> * TCP_NODELAY set #> * Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0) #> > GET /debug HTTP/1.1 #> > Host: 127.0.0.1:3000 #> > User-Agent: curl/7.54.0 #> > Accept: */* #> > #> < HTTP/1.1 200 OK #> < Content-Type: text/plain #> < Content-Length: 13 #> < #> * Connection #0 to host 127.0.0.1 left intact #> Got your back #> #> method: #> get #> url: #> http://127.0.0.1:3000/debug #> client: #> 127.0.0.1 #> query: #> headers: #> Host: 127.0.0.1:3000 #> User-Agent: curl/7.54.0 #> Accept: */* #> fields and methods: #> app # the webfakes_app the request belongs to #> headers # HTTP request headers #> hostname # server hostname, the Host header #> method # HTTP method of request (lowercase) #> path # server path #> protocol # http or https #> query_string # raw query string without '?' #> query # named list of query parameters #> remote_addr # IP address of the client #> url # full URL of the request #> get_header(field) # get a request header #> # see ?webfakes_request for details"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-test-https-requests-","dir":"Reference","previous_headings":"","what":"How can I test HTTPS requests?","title":"How to use webfakes in your tests β€” how-to","text":"Serving HTTPS localhost 127.0.0.1 instead HTTP easy, need Set port HTTPS port adding \"s\" suffix port number. Use \"0s\" OS assigned free port: default webfakes uses server key + certificate file certificate includes localhost, 127.0.0.1 localhost.localdomain. need another domain IP address, need create certificate. generate.sh file directory helps . Specify certificate bundle HTTP client using. default server key use ca.crt file webfakes package: See examples HTTP clients . using curl package, use ca_info option curl::new_handle() curl::handle_setopt(): httr package, use httr::config(cainfo = ...): httr2 package: utils::download.file point CURL_CA_BUNDLE environment variable ca.crt file. forget undo , HTTP request done.","code":"new_app_process(app, port = \"0s\") system.file(\"cert/localhost/server.pem\", package = \"webfakes\") system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") curl::curl_fetch_memory( http$url(\"/path/to/endpoint\"), handle = curl::new_handle(cainfo = cainfo) ) httr::GET( http$url(\"/headers\", https = TRUE), httr::config(cainfo = cainfo) ) httr2::request(\"https://example.com\") |> httr2::req_options(cainfo = cainfo) |> httr2::req_perform() Sys.setenv( CURL_CA_BUNDLE = system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") ) download.file(http$url(\"/path/to/endpoint\"), res <- tempfile())"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"special-considerations-for-tests-on-windows","dir":"Reference","previous_headings":"","what":"Special considerations for tests on Windows","title":"How to use webfakes in your tests β€” how-to","text":"Unfortunately things simple Windows, HTTP clients. far can tell, easily possible make HTTP clients accept new self-signed certificate. possible libcurl, though, need set CURL_SSL_BACKEND=openssl environment variable. (libcurl must built openssl support course.) need set env var loading libcurl, best set starting R. One way tests run tests subprocess, callr package. Look test-https.R file webfakes complete, current example. tests use helper function, defined helper.R: Example test case: seems like good idea skip_on_cran() HTTPS tests, least Windows, setup yet tested enough consider robust. webfakes uses Mbed TLS serving HTTPS.","code":"callr_curl <- function(url, options = list()) { callr::r( function(url, options) { h <- curl::new_handle() curl::handle_setopt(h, .list = options) curl::curl_fetch_memory(url, handle = h) }, list(url = url, options = options), env = c( callr::rcmd_safe_env(), CURL_SSL_BACKEND = \"openssl\", CURL_CA_BUNDLE = if (\"cainfo\" %in% names(options)) options$cainfo ) ) } # ... cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") resp <- if (.Platform$OS.type == \"windows\") { callr_curl(http$url(\"/hello\"), list(cainfo = cainfo)) } else { curl::curl_fetch_memory( http$url(\"/hello\"), handle = curl::new_handle(cainfo = cainfo) ) } # ..."},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-run-a-server-on-multiple-ports-","dir":"Reference","previous_headings":"","what":"How can I run a server on multiple ports?","title":"How to use webfakes in your tests β€” how-to","text":"can specify multiple port numbers, vector. webfakes listen ports. can also mix HTTP HTTP ports. redirect HTTP port HTTPS port, append \"r\" suffix HTTP port number. port redirected next HTTPS port. E.g. redirect HTTP port 3000 HTTPS port 3001. redirect OS assigned HTTP port OS assigned HTTPS port, use zeros port numbers: can use http$get_ports() query port numbers. can also use get HTTPS URL instead default one (one first port).","code":"new_app_process(app, port = c(\"3000r\", \"3001s\")) http <- new_app_process(app, port = c(\"0r\", \"0s\")) http$url(..., https = TRUE)"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"can-i-test-asynchronous-or-parallel-http-requests-","dir":"Reference","previous_headings":"","what":"Can I test asynchronous or parallel HTTP requests?","title":"How to use webfakes in your tests β€” how-to","text":"R single threaded webfakes app runs R interpreter, process multiple requests time. web server runs separate thread, can also process request separate thread, time one request can use R interpreter. important, sometimes test requests may take longer process. example /delay/:secs end point httpbin_app() wait specified number seconds responding, simulate slow web server. wait implemented via standard Sys.sleep() R function, requests can processed sleep . avoid , webfakes can put waiting request hold, return R interpreter, respond incoming requests. Indeed, /delay/ end point implemented using feature. However, request thread web server still busy hold, take advantage , need allow multiple threads. num_threads argument $listen() method webfakes_app lets specify number request threads web server use. Similarly, num_threads argument local_app_process() lets modify number threads. testing asynchronous parallel code, might invoke multiple, possibly delayed requests, best increase number threads. code calls API request concurrently, three times. request takes 1 second answer, web server three threads, together still take 1 second. (http_version = 2 option currently needed bug (https://github.com/r-lib/webfakes/issues/108).)","code":"web <- webfakes::local_app_process( webfakes::httpbin_app(), opts = webfakes::server_opts(num_threads = 3) ) test_that(\"\", { url <- web$url(\"/delay/1\") p <- curl::new_pool() handles <- replicate(3, curl::new_handle(url = url, http_version = 2)) resps <- list() for (handle in handles) { curl::multi_add( handle, done = function(x) message(\"one is done\"), fail = stop, pool = p ) } st <- system.time(curl::multi_run(timeout = 5, pool = p)) print(st) expect_true(st[[\"elapsed\"]] < 3.0) }) #> one is done #> one is done #> one is done #> user system elapsed #> 0.002 0.001 1.094 #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-to-make-sure-that-my-code-works-with-the-real-api-","dir":"Reference","previous_headings":"","what":"How to make sure that my code works with the real API?","title":"How to use webfakes in your tests β€” how-to","text":"Indeed, use webfakes test cases, never touch real web server. might suspect, ideal, especially control server. web service might change API, test cases fail warn . One practical solution write (least ) flexible tests, can run local fake webserver, real one, quick switch change behavior. found environment variables work great . E.g. FAKE_HTTP_TESTS environment variable set, tests run real web server, otherwise use fake one. Another solution, works best HTTP requests downstream package code, introduce one environment variable API need connect . might set real API servers, fake ones. tests can use kinds servers, can set continuous integration (CI) framework, run tests agains real server (say) day. special CI run makes sure code works well real API. can run tests, locally CI, fake local web server. See question webfakes helps setting environment variables point local server.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-simulate-a-slow-internet-connection-","dir":"Reference","previous_headings":"","what":"How do I simulate a slow internet connection?","title":"How to use webfakes in your tests β€” how-to","text":"need use throttle server option start web app. means can run app different connection speed. goes: throttle gives number bytes per second, downloading 200 random bytes fake app take 2 seconds.","code":"library(webfakes) slow <- new_app_process( httpbin_app(), opts = server_opts(throttle = 100) ) resp <- curl::curl_fetch_memory(slow$url(\"/bytes/200\")) resp$times #> redirect namelookup connect pretransfer starttransfer #> 0.000000 0.000097 0.000315 0.000339 0.004731 #> total #> 2.013523"},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":null,"dir":"Reference","previous_headings":"","what":"Format a time stamp for HTTP β€” http_time_stamp","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"Format time stamp HTTP","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"","code":"http_time_stamp(t = Sys.time())"},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"t Date-time value format, defaults current date time. must POSIXct object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"Character vector, formatted date-time.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Generic web app for testing HTTP clients β€” httpbin_app","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"web app similar https://httpbin.org. See specific docs. can also see docs locally, starting app:","code":"httpbin <- new_app_process(httpbin_app()) browseURL(httpbin$url())"},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"","code":"httpbin_app(log = interactive())"},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"log Whether log requests standard output.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"webfakes_app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"","code":"app <- httpbin_app() proc <- new_app_process(app) url <- proc$url(\"/get\") resp <- curl::curl_fetch_memory(url) curl::parse_headers_list(resp$headers) #> $connection #> [1] \"close\" #> #> $date #> [1] \"Mon, 13 Jan 2025 21:25:48 GMT\" #> #> $`content-type` #> [1] \"application/json\" #> #> $`content-length` #> [1] \"313\" #> #> $etag #> [1] \"\\\"0651e283\\\"\" #> cat(rawToChar(resp$content)) #> { #> \"args\": {}, #> \"headers\": { #> \"Host\": \"127.0.0.1:41045\", #> \"User-Agent\": \"R/4.4.2 R (4.4.2 x86_64-pc-linux-gnu x86_64 linux-gnu) on GitHub Actions\", #> \"Accept\": \"*/*\", #> \"Accept-Encoding\": \"deflate, gzip, br, zstd\" #> }, #> \"origin\": \"127.0.0.1\", #> \"path\": \"/get\", #> \"url\": \"http://127.0.0.1:41045/get\" #> } proc$stop()"},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":null,"dir":"Reference","previous_headings":"","what":"Happy HTTP testing with webfakes β€” introduction","title":"Happy HTTP testing with webfakes β€” introduction","text":"Happy HTTP testing webfakes","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"what-is-webfakes-","dir":"Reference","previous_headings":"","what":"What is webfakes?","title":"Happy HTTP testing with webfakes β€” introduction","text":"Webfakes R package can spin web servers machine facilitate testing R code. R code needs HTTP connection trivial test: Connectivity problems might prevent tests accessing web server. web server might need authentication, easy convey login information test suite secure way. web server might rate limits, .e, limits number queries per hour day, causing spurious test failures. might want test non-normal conditions, e.g. low bandwidth, client rate limited. conditions normally happen web server hard trigger. webfakes can easily start custom web app, running local machine. Webfakes need network connection. Webfakes need authentication. Well, unless want . Webfakes rate limits. Webfakes can simulate low bandwidth, broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"webfakes-vs-mocking","dir":"Reference","previous_headings":"","what":"Webfakes vs mocking","title":"Happy HTTP testing with webfakes β€” introduction","text":"Mocking general technique mimic behavior function object needed test case. case HTTP requests, typically means request response recorded tests run first time, saved disk. Subsequent test runs intercept HTTP requests, match recorded requests replay corresponding recorded response. See example vcr httptest R packages. advantages using webfakes server, mocking: Simpler infrastructure. separate recording replaying phases, recorded files. request matching. can use web client want. E.g. curl base R's HTTP functions explicitly support mocking currently. need worry sensitive information recorded requests responses. Often easier use testing non-normal conditions, e.g. errors hard trigger, low bandwidth, rate limits. Works stream data HTTP connection, instead reading whole response . can reuse app multiple tests, multiple packages. Easier use tests require multiple rounds requests. Comes built-https://httpbin.org compatible app, chances , even need write testing app, just start writing tests right away. Better test writing experience. subjective, mileage may vary.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"webfakes-vs-the-real-api","dir":"Reference","previous_headings":"","what":"Webfakes vs the real API","title":"Happy HTTP testing with webfakes β€” introduction","text":"network needed. skip_if_offline(). Much faster. rate limits. can simulate one want . can write custom app. Simulate low bandwidth broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"webfakes-vs-httpbin-org","dir":"Reference","previous_headings":"","what":"Webfakes vs httpbin.org","title":"Happy HTTP testing with webfakes β€” introduction","text":"network needed. skip_if_offline(). Much faster. can use built-webfakes::httpbin_app() app, easy switch httpbin.org. can write custom app, httpbin.org might need.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"using-webfakes-httpbin-app-with-testthat","dir":"Reference","previous_headings":"","what":"Using webfakes::httpbin_app() with testthat","title":"Happy HTTP testing with webfakes β€” introduction","text":"can use testthat's setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See \"start app writing tests?\" just . can also create web server test file, even single test case. See vignette(\"-\") details .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"writing-apps","dir":"Reference","previous_headings":"","what":"Writing apps","title":"Happy HTTP testing with webfakes β€” introduction","text":"builtin httpbin_app() appropriate tests, can write app. can also extend httpbin_app() app, want start scratch. create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:59478/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-12 02:07:58\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/reference/local_app_process.html","id":null,"dir":"Reference","previous_headings":"","what":"App process that is cleaned up automatically β€” local_app_process","title":"App process that is cleaned up automatically β€” local_app_process","text":"can start process explicit $start() call. Alternatively starts first $url() $get_port() call.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/local_app_process.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"App process that is cleaned up automatically β€” local_app_process","text":"","code":"local_app_process(app, ..., .local_envir = parent.frame())"},{"path":"https://webfakes.r-lib.org/dev/reference/local_app_process.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"App process that is cleaned up automatically β€” local_app_process","text":"app webfakes_app object, web app run. ... Passed new_app_process(). .local_envir environment attach process cleanup . Typically frame. frame finishes, process stopped.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware that calls a CGI script β€” mw_cgi","title":"Middleware that calls a CGI script β€” mw_cgi","text":"can use unconditional middleware app$use(), handler app$get(), app$post(), etc., can call handler. See examples .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware that calls a CGI script β€” mw_cgi","text":"","code":"mw_cgi(command, args = character(), timeout = as.difftime(Inf, units = \"secs\"))"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware that calls a CGI script β€” mw_cgi","text":"command External command run. args Arguments pass external command. timeout Timeout external command. command terminate time, web server kills returns 500 response.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware that calls a CGI script β€” mw_cgi","text":"function signature See RFC 3875 details CGI protocol. request body () passed external command standard intput. mw_cgi() sets CONTENT_LENGTH, CONTENT_TYPE, GATEWAY_INTERFACE, PATH_INFO, QUERY_STRING, REMOTE_ADDR, REMOTE_HOST, REMOTE_USER, REQUEST_METHOD, SERVER_NAME, SERVER_PORT, SERVER_PROTOCOL, SERVER_SOFTEWARE. currently set AUTH_TYPE, PATH_TRANSLATED, REMOTE_IDENT, SCRIPT_NAME environment variables. standard output external command used set response status code, response headers response body. Example output git's CGI:","code":"function(req, res, env = character()) Status: 200 OK Expires: Fri, 01 Jan 1980 00:00:00 GMT Pragma: no-cache Cache-Control: no-cache, max-age=0, must-revalidate Content-Type: application/x-git-upload-pack-advertisement 000eversion 2 0015agent=git/2.42.0 0013ls-refs=unborn 0020fetch=shallow wait-for-done 0012server-option 0017object-format=sha1 0010object-info 0000"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware that calls a CGI script β€” mw_cgi","text":"","code":"app <- new_app() app$use(mw_cgi(\"echo\", \"Status: 200\\n\\nHello\")) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods app2 <- new_app() app2$get(\"/greet\", mw_cgi(\"echo\", \"Status: 200\\n\\nHello\")) app2 #> #> routes: #> get /greet #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Using `mw_cgi()` in a handler, you can pass extra environment variables app3 <- new_app() cgi <- mw_cgi(\"echo\", \"Status: 200\\n\\nHello\") app2$get(\"/greet\", function(req, res) { cgi(req, res, env = c(\"EXTRA_VAR\" = \"EXTRA_VALUE\")) }) app3 #> #> routes: #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse Cookies β€” mw_cookie_parser","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"Adds cookies cookies element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"","code":"mw_cookie_parser()"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"Handler function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"ignores cookies invalid format. ignores duplicate cookies: two cookies name, first one included.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware that add an ETag header to the response β€” mw_etag","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"response already ETag header, kept.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"","code":"mw_etag(algorithm = \"crc32\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"algorithm Checksum algorithm use. \"crc32\" implemented currently.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"Handler function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"middleware handles -None-Match headers, sets status code response 304 -None-Match matches ETag. also removes response body case.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"","code":"app <- new_app() app$use(mw_etag()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse a JSON body β€” mw_json","title":"Middleware to parse a JSON body β€” mw_json","text":"Adds parsed object json element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse a JSON body β€” mw_json","text":"","code":"mw_json(type = \"application/json\", simplifyVector = FALSE, ...)"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to parse a JSON body β€” mw_json","text":"type Content type match parsing. match, request object modified. simplifyVector Whether simplify lists vectors, passed jsonlite::fromJSON(). ... Arguments pass jsonlite::fromJSON(), performs JSON parsing.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse a JSON body β€” mw_json","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to parse a JSON body β€” mw_json","text":"","code":"app <- new_app() app$use(mw_json()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":null,"dir":"Reference","previous_headings":"","what":"Log requests to the standard output or other connection β€” mw_log","title":"Log requests to the standard output or other connection β€” mw_log","text":"one line log entry every request. output looks like : contains HTTP method, full request URL, HTTP status code response, long took process response, ms, size response body, bytes.","code":"GET http://127.0.0.1:3000/image 200 3 ms - 4742"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Log requests to the standard output or other connection β€” mw_log","text":"","code":"mw_log(format = \"dev\", stream = \"stdout\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Log requests to the standard output or other connection β€” mw_log","text":"format Log format. implemented currently. stream R connection log . \"stdout\" means standard output, \"stderr\" standard error. can also supply connection object, need sure valid app actually running.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Log requests to the standard output or other connection β€” mw_log","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Log requests to the standard output or other connection β€” mw_log","text":"","code":"app <- new_app() app$use(mw_log()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":null,"dir":"Reference","previous_headings":"","what":"Parse a multipart HTTP request body β€” mw_multipart","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"Adds parsed form fields form element request parsed files files element.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"","code":"mw_multipart(type = \"multipart/form-data\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"type Content type match parsing. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"","code":"app <- new_app() app$use(mw_multipart()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse a Range header β€” mw_range_parser","title":"Middleware to parse a Range header β€” mw_range_parser","text":"Adds requested ranges ranges element request object. request$ranges data frame two columns, . row corresponds one requested interval.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse a Range header β€” mw_range_parser","text":"","code":"mw_range_parser()"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse a Range header β€” mw_range_parser","text":"Handler function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Middleware to parse a Range header β€” mw_range_parser","text":"last n bytes file requested, matrix row set c(0, -n). bytes p position requested, matrix row set c(p, Inf). intervals overlap, ranges set, .e. Range header ignored. syntax invalid unit bytes, Range header ignored.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to read the raw body of a request β€” mw_raw","title":"Middleware to read the raw body of a request β€” mw_raw","text":"Adds raw body, raw object raw field request.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to read the raw body of a request β€” mw_raw","text":"","code":"mw_raw(type = \"application/octet-stream\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to read the raw body of a request β€” mw_raw","text":"type Content type match. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to read the raw body of a request β€” mw_raw","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to read the raw body of a request β€” mw_raw","text":"","code":"app <- new_app() app$use(mw_raw()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware function to serve static files β€” mw_static","title":"Middleware function to serve static files β€” mw_static","text":"content type response set automatically extension file. Note terminal middleware handler function. file served, rest handler functions called. file found, however, rest handlers still called.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware function to serve static files β€” mw_static","text":"","code":"mw_static(root, set_headers = NULL)"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware function to serve static files β€” mw_static","text":"root Root path served files. Everything directory served automatically. Directory lists currently supports. set_headers Callback function call file served.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware function to serve static files β€” mw_static","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware function to serve static files β€” mw_static","text":"","code":"root <- system.file(package = \"webfakes\", \"examples\", \"static\", \"public\") app <- new_app() app$use(mw_static(root = root)) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse a plain text body β€” mw_text","title":"Middleware to parse a plain text body β€” mw_text","text":"Adds parsed object text element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse a plain text body β€” mw_text","text":"","code":"mw_text(default_charset = \"utf-8\", type = \"text/plain\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to parse a plain text body β€” mw_text","text":"default_charset Encoding set text. type Content type match parsing. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse a plain text body β€” mw_text","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to parse a plain text body β€” mw_text","text":"","code":"app <- new_app() app$use(mw_text()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse an url-encoded request body β€” mw_urlencoded","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"typically data form. parsed data added form element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"","code":"mw_urlencoded(type = \"application/x-www-form-urlencoded\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"type Content type match parsing. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"","code":"app <- new_app() app$use(mw_urlencoded()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Create a new web application β€” new_app","title":"Create a new web application β€” new_app","text":"Create new web application","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Create a new web application β€” new_app","text":"","code":"new_app()"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Create a new web application β€” new_app","text":"new webfakes_app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Create a new web application β€” new_app","text":"typical workflow creating web application : Create webfakes_app object new_app(). Add middleware /routes . Start webfakes_app$listen() method, start another process new_app_process(). Make queries web app. Stop via CTRL+C / ESC, , running another process, $stop() method new_app_process(). web application can restarted, saved disk, copied another process using callr package, similar way, embedded package, extended simply adding new routes /middleware. webfakes API much influenced express.js project.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"create-web-app-objects","dir":"Reference","previous_headings":"","what":"Create web app objects","title":"Create a new web application β€” new_app","text":"new_app() returns webfakes_app object methods listed page. app environment S3 class webfakes_app.","code":"new_app()"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"the-handler-stack","dir":"Reference","previous_headings":"","what":"The handler stack","title":"Create a new web application β€” new_app","text":"app stack handlers. handler can route middleware. differences two : route bound one paths web server. Middleware (currently) bound paths, run paths. route usually (always) end handler stack request. .e. route takes care sending response request. Middleware typically performs action request response, next handler stack invoked.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"routes","dir":"Reference","previous_headings":"","what":"Routes","title":"Create a new web application β€” new_app","text":"following methods define routes. method corresponds HTTP verb name, except app$(), creates route HTTP methods. path path specification, see 'Path specification' . ... one handler functions. placed handler stack, called match incoming HTTP request. See 'Handler functions' . webfakes also methods less frequently used HTTP verbs: CONNECT, MKCOL, OPTIONS, PROPFIND, REPORT. (method names always lowercase.) request handled routes (handler functions general), webfakes send simple HTTP 404 response.","code":"app$all(path, ...) app$delete(path, ...) app$get(path, ...) app$head(path, ...) app$patch(path, ...) app$post(path, ...) app$put(path, ...) ... (see list below)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"middleware","dir":"Reference","previous_headings":"","what":"Middleware","title":"Create a new web application β€” new_app","text":"app$use() adds middleware handler stack. middleware handler function, see 'Handler functions' . webfakes comes middleware perform common tasks: mw_cookie_parser() parses Cookie headers. mw_etag() adds ETag header response. mw_json() parses JSON request bodies. mw_log() logs requests standard output, another connection. mw_multipart() parses multipart request bodies. mw_range_parser() parses Range headers. mw_raw() parses raw request bodies. mw_static() serves static files directory. mw_text() parses plain text request bodies. mw_urlencoded() parses URL encoded request bodies. ... set (middleware) handler functions. added handler stack, called every HTTP request. (Unless HTTP response created reaching point handler stack.) .first set TRUE want add handler function bottom stack.","code":"app$use(..., .first = FALSE)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"handler-functions","dir":"Reference","previous_headings":"","what":"Handler functions","title":"Create a new web application β€” new_app","text":"handler function route middleware. handler function called webfakes incoming HTTP request outgoing HTTP response objects (built) arguments. handler function may query modify members request /response object. returns string \"next\", terminal handler, returns, webfakes move call next handler stack. typical route: handler belongs API path, wildcard path case. matches /user/alice, /user/bob, etc. handler called GET methods matching API paths. handler receives request (req) response (res). sets HTTP status, additional headers, sends data. (case webfakes_response$send_json() method automatically converts response JSON sets Content-Type Content-Length headers. terminal handler, return \"next\". handler function returns, webfakes send HTTP response. typical middleware: HTTP method API path , webfakes call handler HTTP request. terminal handler, return \"next\", returns webfakes look next handler stack.","code":"app$get(\"/user/:id\", function(req, res) { id <- req$params$id ... res$ set_status(200L)$ set_header(\"X-Custom-Header\", \"foobar\")$ send_json(response, auto_unbox = TRUE) }) app$use(function(req, res) { ... \"next\" })"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"errors","dir":"Reference","previous_headings":"","what":"Errors","title":"Create a new web application β€” new_app","text":"handler function throws error, web server return HTTP 500 text/plain response, error message response body.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"request-and-response-objects","dir":"Reference","previous_headings":"","what":"Request and response objects","title":"Create a new web application β€” new_app","text":"See webfakes_request webfakes_response methods request response objects.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"path-specification","dir":"Reference","previous_headings":"","what":"Path specification","title":"Create a new web application β€” new_app","text":"Routes associated one API paths. path specification can \"plain\" (.e. without parameters) string. (E.g. \"/list\".) parameterized string. (E.g. \"/user/:id\".) regular expression created via new_regexp() function. list character vector previous ones. (Regular expressions must list.)","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"path-parameters","dir":"Reference","previous_headings":"","what":"Path parameters","title":"Create a new web application β€” new_app","text":"Paths specified parameterized strings regular expressions can parameters. parameterized strings keys may contain letters, numbers underscores. webfakes matches API path handler parameterized string path, parameters added request, params. .e. handler function (subsequent handler functions, current one terminal), available req$params list. regular expressions, capture groups also added parameters. best use named capture groups, parameters named list. path handler list parameterized strings regular expressions, parameters set according first matching one.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"templates","dir":"Reference","previous_headings":"","what":"Templates","title":"Create a new web application β€” new_app","text":"webfakes supports templates, using template engine. comes template engine uses glue package, see tmpl_glue(). app$engine() registers template engine, certain file extension. $render() method webfakes_response can called handler function evaluate template file. ext: file extension template engine added. contain dot. E.g. \"html\"', \"brew\"`. engine: template engine, function takes file path (path) template, list local variables (locals) can used template. return result. example template engine uses glue might look like : (built-tmpl_glue() engine features.) template engine can used handler: location templates can set using views configuration parameter, see $set_config() method . template, variables passed locals, also response local variables (see locals webfakes_response), available.","code":"app$engine(ext, engine) app$engine(\"txt\", function(path, locals) { txt <- readChar(path, nchars = file.size(path)) glue::glue_data(locals, txt) }) app$get(\"/view\", function(req, res) { txt <- res$render(\"test\") res$ set_type(\"text/plain\")$ send(txt) })"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"starting-and-stopping","dir":"Reference","previous_headings":"","what":"Starting and stopping","title":"Create a new web application β€” new_app","text":"port: port listen . NULL, operating system automatically select free port. Add \"s\" suffix port use HTTPS. Use \"0s\" use OS assigned port HTTPS. See -manual page want start web server one ports. opts: options web server. See server_opts() list options default values. cleanup: stop server (error) standard input process closed. handy app runs callr::r_session subprocess, stops app (subprocess) main process terminated. method return, can interrupted CTRL+C / ESC SIGINT signal. See new_app_process() interrupting app running another process. port NULL, operating system chooses port app listen. able get port number programmatically, listen method blocks, advertises selected port webfakes_port condition, one can catch : webfakes default binds loopback interface 127.0.0.1, webfakes web app never reachable network.","code":"app$listen(port = NULL, opts = server_opts(), cleanup = TRUE) withCallingHandlers( app$listen(), \"webfakes_port\" = function(msg) print(msg$port) )"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"logging","dir":"Reference","previous_headings":"","what":"Logging","title":"Create a new web application β€” new_app","text":"webfakes can write access log contains entry incoming requests, also error log errors happen server running. default behavior local app (ones started app$listen() remote apps (ones started via new_app_process(): Local apps write access log default. Remote apps write access log /webfakes//access.log file, session temporary directory main process, process id subprocess. Local apps write error log /webfakes/error.log, session temporary directory current process. Remote app write error log /webfakes//error.log, session temporary directory main process process id subprocess`. See server_opts() changing default logging behavior.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"shared-app-data","dir":"Reference","previous_headings":"","what":"Shared app data","title":"Create a new web application β€” new_app","text":"often useful share data handlers requests app. app$locals environment supports . E.g. middleware counts number requests can implemented : webfakes_response objects also locals environment, initially populated copy app$locals.","code":"app$locals app$use(function(req, res) { locals <- req$app$locals if (is.null(locals$num)) locals$num <- 0L locals$num <- locals$num + 1L \"next\" })"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"configuration","dir":"Reference","previous_headings":"","what":"Configuration","title":"Create a new web application β€” new_app","text":"key: configuration key. value: configuration value. Currently used configuration values: views: path webfakes searches templates.","code":"app$get_config(key) app$set_config(key, value)"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Create a new web application β€” new_app","text":"","code":"# see example web apps in the `/examples` directory in system.file(package = \"webfakes\", \"examples\") #> [1] \"/home/runner/work/_temp/Library/webfakes/examples\" app <- new_app() app$use(mw_log()) app$get(\"/hello\", function(req, res) { res$send(\"Hello there!\") }) app$get(new_regexp(\"^/hi(/.*)?$\"), function(req, res) { res$send(\"Hi indeed!\") }) app$post(\"/hello\", function(req, res) { res$send(\"Got it, thanks!\") }) app #> #> routes: #> use * #> get /hello #> get \"^/hi(/.*)?$\" #> post /hello #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Start the app with: app$listen() # Or start it in another R session: new_app_process(app)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":null,"dir":"Reference","previous_headings":"","what":"Run a webfakes app in another process β€” new_app_process","title":"Run a webfakes app in another process β€” new_app_process","text":"Runs app subprocess, using callr::r_session.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Run a webfakes app in another process β€” new_app_process","text":"","code":"new_app_process( app, port = NULL, opts = server_opts(remote = TRUE), start = FALSE, auto_start = TRUE, process_timeout = NULL, callr_opts = NULL )"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Run a webfakes app in another process β€” new_app_process","text":"app webfakes_app object, web app run. port Port(s) use. default OS assigns port. Add \"s\" suffix port use HTTPS. Use \"0s\" use OS assigned port HTTPS. See -run web server multiple ports. opts Server options. See server_opts() defaults. start Whether start web server immediately. FALSE, auto_start TRUE, started neeed. auto_start Whether start web server process automatically. TRUE process running, $start(), $get_port(), $get_ports() $url() start process. process_timeout long wait subprocess start, milliseconds. callr_opts Options pass callr::r_session_options() setting subprocess.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Run a webfakes app in another process β€” new_app_process","text":"webfakes_app_process object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"methods","dir":"Reference","previous_headings":"","what":"Methods","title":"Run a webfakes app in another process β€” new_app_process","text":"webfakes_app_process class following methods: envvars: Named list environment variables. {url} substring replaced URL app. path: Path return URL . query: Additional query parameters, named list, add URL. get_app() returns app object. get_port() returns (first) port web server running . get_ports() returns ports web server running , whether uses SSL ports, data frame columns ipv4, ipv6, port ssl. stop() stops web server, also subprocess. error log file empty, dumps contents screen. get_state() returns string, state web server: \"running\" server running (stopped already). \"live\" means server running. \"dead\" means subprocess quit crashed. local_env() sets given environment variables duration app process. resets $stop(). Webfakes replaces {url} value environment variables app URL, can set environment variables point app. url() returns URL web app. can use path parameter return specific path.","code":"get_app() get_port() get_ports() stop() get_state() local_env(envvars) url(path = \"/\", query = NULL)"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Run a webfakes app in another process β€” new_app_process","text":"","code":"app <- new_app() app$get(\"/foo\", function(req, res) { res$send(\"Hello world!\") }) proc <- new_app_process(app) url <- proc$url(\"/foo\") resp <- curl::curl_fetch_memory(url) cat(rawToChar(resp$content)) #> Hello world! proc$stop()"},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":null,"dir":"Reference","previous_headings":"","what":"Create a new regular expression to use in webfakes routes β€” new_regexp","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"Note webfakes uses PERL regular expressions.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"","code":"new_regexp(x)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"x String scalar containing regular expression.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"String class webfakes_regexp.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"R data type class regular expressions, can use new_regexp() mark string regular expression, adding routes.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"","code":"new_regexp(\"^/api/match/(?.*)$\") #> \"^/api/match/(?.*)$\""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":null,"dir":"Reference","previous_headings":"","what":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"perform automatic acknowledgement log local OAuth2.0 app, run httr, wrap expression obtains OAuth2.0 token oauth2_httr_login().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"","code":"oauth2_httr_login(expr)"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"expr Expression calls httr::oauth2.0_token(), either directly, indirectly.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"return value expr.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"interactive sessions, oauth2_httr_login() overrides browser option, httr opens browser page, calls oauth2_login() subprocess. non-interactive sessions, httr open browser page, messages user manually. oauth2_httr_login() listens messages, calls oauth2_login() subprocess.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":null,"dir":"Reference","previous_headings":"","what":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"works oauth2_resource_app(), third party app, including fake oauth2_third_party_app().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"","code":"oauth2_login(login_url)"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"login_url login URL third party app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"named list login_response curl HTTP response object login page. token_response curl HTTP response object submitting login page.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"See test-oauth.R webfakes example.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"webfakes package comes two fake apps allow imitate OAuth2.0 flow test cases. (See Aaron Parecki’s tutorial good introduction OAuth2.0.) One app (oauth2_resource_app()) API server serves resource provides authorization. oauth2_third_party_app() plays role third-party app. useful testing demonstrating code handling OAuth2.0 authorization, token caching, etc. package. apps can used tests directly, adapt one better mimic particular OAuth2.0 flow.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"","code":"oauth2_resource_app( access_duration = 3600L, refresh_duration = 7200L, refresh = TRUE, seed = NULL, authorize_endpoint = \"/authorize\", token_endpoint = \"/token\" )"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"access_duration many seconds access tokens expire. refresh_duration many seconds refresh tokens expire (ignored refresh FALSE). refresh refresh token returned (logical). seed Random seed used creating tokens. NULL, rely R provide seed. app uses RNG stream, affect reproducibility tests. authorize_endpoint authorization endpoint resource server. Change default real app faking use /authorize. token_endpoint endpoint request tokens. Change real app faking use /token.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"webfakes app webfakes app","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"app following endpoints: GET /register endpoint can use register third party app. needs receive name third party app, redirect_uri query parameters, otherwise returns HTTP 400 error. success returns JSON dictionary entries name (name third party app), client_id, client_secret redirect_uri. GET /authorize endpoint user third party app sent. can change URL endpoint authorize_endpoint argument. needs receive client_id third party app, correct redirect_uri query parameters. may receive state string well, can used client identify request. Otherwise generates random state string. error fails HTTP 400 error. success returns simple HTML login page. POST /authorize/decision endpoint HTML login page generated /authorize connects back , either positive negative result. form login page send state string user's choice action variable. user authorized third party app, redirected redirect_uri app, temporary code state string supplied query parameters. Otherwise simple HTML page returned. POST /token endpoint third party app requests temporary access token. also uses refreshing access token refresh token. can change URL endpoint token_endpoint argument. request new token refresh existing one, following data must included either JSON URL encoded request body: grant_type, must authorization_code new tokens, refresh_token refreshing. code, must temporary code obtained /authorize/decision redirection, new tokens. needed refreshing. client_id must client id third party app. client_secret must client secret third party app. redirect_uri must correct redirection URI third party app. needed refreshing tokens. refresh_token must refresh token obtained previously, refreshing token. needed new tokens. success JSON dictionary returned entries: access_token, expiry refresh_token. (latter omitted refresh argument FALSE). GET /locals returns list current apps, access tokens refresh tokens. GET /data endpoint returns simple JSON response, needs authorization.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"notes","dir":"Reference","previous_headings":"","what":"Notes","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"Using app tests requires glue package, need put Suggests. can add custom endpoints app, needed. need authorization custom endpoint, call app$is_authorized() handler: app$is_authorized() returns HTTP 401 response client authorized, can simply return handler. details see vignette(\"oauth\", package = \"webfakes\").","code":"if (!app$is_authorized(req, res)) return()"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"oauth-resource-app-","dir":"Reference","previous_headings":"","what":"oauth2_resource_app()","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"App representing API server (resource/authorization)","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":null,"dir":"Reference","previous_headings":"","what":"App representing the third-party app β€” oauth2_third_party_app","title":"App representing the third-party app β€” oauth2_third_party_app","text":"webfakes package comes two fake apps allow imitate OAuth2.0 flow test cases. (See Aaron Parecki’s tutorial good introduction OAuth2.0.) One app (oauth2_resource_app()) API server serves resource provides authorization. oauth2_third_party_app() plays role third-party app. useful testing demonstrating code handling OAuth2.0 authorization, token caching, etc. package. apps can used tests directly, adapt one better mimic particular OAuth2.0 flow.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"App representing the third-party app β€” oauth2_third_party_app","text":"","code":"oauth2_third_party_app(name = \"Third-Party app\")"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"App representing the third-party app β€” oauth2_third_party_app","text":"name Name third-party app","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"App representing the third-party app β€” oauth2_third_party_app","text":"webfakes app","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"App representing the third-party app β€” oauth2_third_party_app","text":"Endpoints: POST /login/config Use endpoint configure client ID client secret app, received oauth2_resource_app() (another resource app). need send JSON URL encoded body: auth_url, authorization URL resource app. token_url, token URL resource app. client_id, client ID, received resource app. client_secret client secret, received resource app. GET /login Use endpoint start login process. redirect resource app authorization OAuth2.0 dance /login/redirect. GET /login/redirect, POST /login/redirect redirect URI third party app. (HTTP clients redirect POST GET, others , .) endpoint used resource app, received code can exchanged access token state generated /login. contacts resource app get access token, stores token app$locals local variables. fails HTTP code 500 obtain access token. success returns JSON dictionary access_token, expiry refresh_token (optionally) default. behavior can changed redefining app$redirect_hook() function. GET /locals returns tokens obtained resource app. GET /data endpoint uses obtained token(s) connect /data endpoint resource app. /data endpoint resource app needs authorization. responds response resource app. tries refresh access token app needed. details see vignette(\"oauth\", package = \"webfakes\").","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":null,"dir":"Reference","previous_headings":"","what":"Webfakes web server options β€” server_opts","title":"Webfakes web server options β€” server_opts","text":"Webfakes web server options","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Webfakes web server options β€” server_opts","text":"","code":"server_opts( remote = FALSE, port = NULL, num_threads = 1, interfaces = \"127.0.0.1\", enable_keep_alive = FALSE, access_log_file = remote, error_log_file = TRUE, tcp_nodelay = FALSE, throttle = Inf, decode_url = TRUE, ssl_certificate = NULL )"},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Webfakes web server options β€” server_opts","text":"remote Meta-option. set TRUE, webfakes uses slightly different defaults, appropriate background server process. port Port start web server . Defaults randomly chosen port. num_threads Number request handler threads use. Typically need one thread, unless run test cases parallel make concurrent HTTP requests. interfaces network interfaces listen . test web server, defaults localhost. bind public interface know . webfakes designed serve public web pages. enable_keep_alive Whether server keeps connections alive. access_log_file TRUE, FALSE, path. See 'Logging' . error_log_file TRUE, FALSE, path. See 'Logging' . tcp_nodelay TRUE packages sent soon possible, instead waiting full buffer timeout occur. throttle Limit download speed clients. Inf, maximum number bytes per second, sent connection. decode_url Whether server automatically decode URL-encodded URLs. TRUE (default), /foo%2fbar converted /foo/bar automatically. FALSE, URLs URL-decoded. ssl_certificate Path SSL certificate server, needed want server HTTPS requests.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Webfakes web server options β€” server_opts","text":"List options can passed webfakes_app$listen() (see new_app()), new_app_process().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"logging","dir":"Reference","previous_headings":"","what":"Logging","title":"Webfakes web server options β€” server_opts","text":"access_log_file, TRUE means /access.log. error_log_file, TRUE means /error.log. set contents WEBFAKES_LOG_DIR environment variable, set. Otherwise set /webfakes local apps //webfakes remote apps (started new_app_procss()). session temporary directory main process. process id subprocess.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Webfakes web server options β€” server_opts","text":"","code":"# See the defaults server_opts() #> $remote #> [1] FALSE #> #> $port #> NULL #> #> $num_threads #> [1] 1 #> #> $interfaces #> [1] \"127.0.0.1\" #> #> $enable_keep_alive #> [1] FALSE #> #> $access_log_file #> [1] NA #> #> $error_log_file #> [1] \"/tmp/Rtmpd5MOPJ/webfakes/error.log\" #> #> $tcp_nodelay #> [1] FALSE #> #> $throttle #> [1] Inf #> #> $decode_url #> [1] TRUE #> #> $ssl_certificate #> [1] \"/home/runner/work/_temp/Library/webfakes/cert/localhost/server.pem\" #>"},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":null,"dir":"Reference","previous_headings":"","what":"glue based template engine β€” tmpl_glue","title":"glue based template engine β€” tmpl_glue","text":"Use template engine create pages glue templates. See glue::glue() syntax.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"glue based template engine β€” tmpl_glue","text":"","code":"tmpl_glue( sep = \"\", open = \"{\", close = \"}\", na = \"NA\", transformer = NULL, trim = TRUE )"},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"glue based template engine β€” tmpl_glue","text":"sep Separator used separate elements. open opening delimiter. Doubling full delimiter escapes . close closing delimiter. Doubling full delimiter escapes . na Value replace NA values . NULL missing values propagated, NA result cause NA output. Otherwise value replaced value na. transformer function taking three parameters code, envir data used transform output block evaluation. trim Whether trim input template glue::trim() .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"glue based template engine β€” tmpl_glue","text":"Template function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"glue based template engine β€” tmpl_glue","text":"","code":"# See th 'hello' app at hello_root <- system.file(package = \"webfakes\", \"examples\", \"hello\") hello_root #> [1] \"/home/runner/work/_temp/Library/webfakes/examples/hello\" app <- new_app() app$engine(\"txt\", tmpl_glue()) app$use(mw_log()) app$get(\"/view\", function(req, res) { txt <- res$render(\"test\") res$ set_type(\"text/plain\")$ send(txt) }) # Switch to the app's root: setwd(hello_root) # Now start the app with: app$listen(3000L) # Or start it in another process: new_process(app)"},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes-package.html","id":null,"dir":"Reference","previous_headings":"","what":"webfakes: Fake Web Apps for HTTP Testing β€” webfakes-package","title":"webfakes: Fake Web Apps for HTTP Testing β€” webfakes-package","text":"Create web app makes easier test web clients without using internet. includes web app framework path matching, parameters templates. Can parse various 'HTTP' request bodies. Can send 'JSON' data files disk. Includes web app implements 'httpbin.org' web service.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes-package.html","id":"author","dir":"Reference","previous_headings":"","what":"Author","title":"webfakes: Fake Web Apps for HTTP Testing β€” webfakes-package","text":"Maintainer: GΓ‘bor CsΓ‘rdi csardi.gabor@gmail.com contributors: Posit Software, PBC [copyright holder, funder] Civetweb contributors (see inst/credits/ciwetweb.md) [contributor] Redoc contributors (see inst/credits/redoc.md) [contributor] L. Peter Deutsch (src/md5.h) [contributor] Martin Purschke (src/md5.h) [contributor] Aladdin Enterprises (src/md5.h) [copyright holder] MaΓ«lle Salmon maelle.salmon@yahoo.se (ORCID) [contributor]","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_request.html","id":null,"dir":"Reference","previous_headings":"","what":"A webfakes request object β€” webfakes_request","title":"A webfakes request object β€” webfakes_request","text":"webfakes creates webfakes_request object every incoming HTTP request. object passed every matched route middleware, response sent. reference semantics, handlers can modify .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_request.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"A webfakes request object β€” webfakes_request","text":"Fields methods: app: webfakes_app object . headers: Named list HTTP request headers. hostname: Host header, server hostname maybe port. method: HTTP method. path: Server path. protocol: \"http\" \"https\". query_string: raw query string, without starting ?. query: Parsed query parameters named list. remote_addr: String, domain name IP address client. webfakes runs localhost, 127.0.0.1. url: full URL request. get_header(field): Function query request header. Returns NULL header present. Body parsing middleware adds additional fields request object. See mw_raw(), mw_text(), mw_json(), mw_multipart() mw_urlencoded().","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_request.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"A webfakes request object β€” webfakes_request","text":"","code":"# This is how you can see the request and response objects: app <- new_app() app$get(\"/\", function(req, res) { browser() res$send(\"done\") }) app #> #> routes: #> get / #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Now start this app on a port: # app$listen(3000) # and connect to it from a web browser: http://127.0.0.1:3000 # You can also use another R session to connect: # httr::GET(\"http://127.0.0.1:3000\") # or the command line curl tool: # curl -v http://127.0.0.1:3000 # The app will stop while processing the request."},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_response.html","id":null,"dir":"Reference","previous_headings":"","what":"A webfakes response object β€” webfakes_response","title":"A webfakes response object β€” webfakes_response","text":"webfakes creates webfakes_response object every incoming HTTP request. object passed every matched route middleware, HTTP response sent. reference semantics, handlers can modify .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_response.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"A webfakes response object β€” webfakes_response","text":"Fields methods: app: webfakes_app object . req: request object. headers_sent: Whether response headers already sent . locals: Local variables, shared handler functions. end user, middlewares. delay(secs): delay response number seconds. handler calls delay(), handler called , specified number seconds passed. Use locals environment distinguish calls. using delay(), want serve requests parallel, probably need multi-threaded server, see server_opts(). add_header(field, value): Add response header. Note add_header() may create duplicate headers. usually want set_header(). get_header(field): Query currently set response headers. field present return NULL. on_response(fun): Run fun handler function just response sent . point headers body already properly set. redirect(path, status = 302): Send redirect response. sets Location header, also sends text/plain body. render(view, locals = list()): Render template page. Searches view template page, using registered engine extensions, calls first matching template engine. Returns filled template. send(body). Send specified body. body can raw vector, HTML text. raw vectors sets content type application/octet-stream. send_json(object = NULL, text = NULL, ...): Send JSON response. Either object text must given. object converted JSON using jsonlite::toJSON(). ... passed jsonlite::toJSON(). sets content type appropriately. send_file(path, root = \".\"): Send file. Set root = \"/\" absolute file names. sets content type automatically, based extension file, set already. send_status(status): Send specified HTTP status code, without response body. send_chunk(data): Send chunk response chunked encoding. first chunk automatically send HTTP response headers. Webfakes automatically send final zero-lengh chunk, unless $delay() called. set_header(field, value): Set response header. headers sent already, throws warning, nothing. set_status(status): Set response status code. headers sent already, throws warning, nothing. set_type(type): Set response content type. contains / character set , otherwise assumed file extension, corresponding MIME type set. headers sent already, throws warning, nothing. add_cookie(name, value, options): Adds cookie response. options named list, may contain: domain: Domain name cookie, set default. expires: Expiry date GMT. must POSIXct object, formatted correctly. 'http_only': TRUE, sets 'HttpOnly' attribute, Javasctipt access cookie. max_age: Maximum age, number seconds. path: Path cookie, defaults \"/\". same_site: 'SameSite' cookie attribute. Possible values \"strict\", \"lax\" \"none\". secure: TRUE, sets 'Secure' attribute. clear_cookie(name, options = list()): clears cookie. Typically, web browsers clear cookie options also match. write(data): writes (part ) body response. also sends response headers, sent . Usually need one send() methods, send HTTP response one go, first headers, body. Alternatively, can use $write() send response parts.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_response.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"A webfakes response object β€” webfakes_response","text":"","code":"# This is how you can see the request and response objects: app <- new_app() app$get(\"/\", function(req, res) { browser() res$send(\"done\") }) app #> #> routes: #> get / #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Now start this app on a port: # app$listen(3000) # and connect to it from a web browser: http://127.0.0.1:3000 # You can also use another R session to connect: # httr::GET(\"http://127.0.0.1:3000\") # or the command line curl tool: # curl -v http://127.0.0.1:3000 # The app will stop while processing the request."},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-132","dir":"Changelog","previous_headings":"","what":"webfakes 1.3.2","title":"webfakes 1.3.2","text":"CRAN release: 2025-01-11 New server option: decode_url. set FALSE, web server URL-decode URL (#106).","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-131","dir":"Changelog","previous_headings":"","what":"webfakes 1.3.1","title":"webfakes 1.3.1","text":"CRAN release: 2024-04-25 changes.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-130","dir":"Changelog","previous_headings":"","what":"webfakes 1.3.0","title":"webfakes 1.3.0","text":"CRAN release: 2023-12-11 New git_app() app fake git HTTP server. See webfakes test cases examples. New mw_cgi() middleware call CGI scripts. See new git_app() example.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-121","dir":"Changelog","previous_headings":"","what":"webfakes 1.2.1","title":"webfakes 1.2.1","text":"CRAN release: 2023-10-01 tmpl_glue() now works correctly platforms issue readChar(..., useBytes = TRUE), e.g.Β macOS 14.x Sonoma: https://bugs.r-project.org/show_bug.cgi?id=18605.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-120","dir":"Changelog","previous_headings":"","what":"webfakes 1.2.0","title":"webfakes 1.2.0","text":"CRAN release: 2023-05-16 httpbin app now implements /brotli, /deflate, /digest-auth /forms/post, /hidden-basic-auth, /range/:n, /stream/:n, /cache /cache/:value endpoints. , implements endpoint original Python httpbin app (#3). New middleware mw_cookie_parser() parse Cookie header. Relatedly, new response$add_cookie() response$clear_cookie() methods add cookie response add header clears cookie (#2). Parsing query parametes without value now fail. New utility function http_time_stamp() format time stamp HTTP. httpbin app now implements endpoints related cookies (#3). httpbin app now sends Date header correct format. offset parameter now optional /links endpoint httpbin app. mw_etag() now add ETag header response, one already. (comparision case sensitive.) New middleware: mw_range_parser() parse Range headers.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-117","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.7","title":"webfakes 1.1.7","text":"CRAN release: 2023-02-08 user visible changes.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-116","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.6","title":"webfakes 1.1.6","text":"CRAN release: 2022-11-08 response$send_file() now handles root = \"/\" absolute paths better Windows. new_app_process() local_app_process() now faster, app object need copy subprocess smaller.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-115","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.5","title":"webfakes 1.1.5","text":"CRAN release: 2022-10-25 mw_etag() now handles -None-Match header properly, sets status code response 304, removes response body.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-114","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.4","title":"webfakes 1.1.4","text":"CRAN release: 2022-09-08 user visible changes.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-113","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.3","title":"webfakes 1.1.3","text":"CRAN release: 2021-04-30 webfakes now compiles older macOS versions, hopefully really.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-112","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.2","title":"webfakes 1.1.2","text":"CRAN release: 2021-04-05 webfakes now compiles older macOS versions (10.12).","code":""}] +[{"path":[]},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"our-pledge","dir":"","previous_headings":"","what":"Our Pledge","title":"Contributor Covenant Code of Conduct","text":"members, contributors, leaders pledge make participation community harassment-free experience everyone, regardless age, body size, visible invisible disability, ethnicity, sex characteristics, gender identity expression, level experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, sexual identity orientation. pledge act interact ways contribute open, welcoming, diverse, inclusive, healthy community.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"our-standards","dir":"","previous_headings":"","what":"Our Standards","title":"Contributor Covenant Code of Conduct","text":"Examples behavior contributes positive environment community include: Demonstrating empathy kindness toward people respectful differing opinions, viewpoints, experiences Giving gracefully accepting constructive feedback Accepting responsibility apologizing affected mistakes, learning experience Focusing best just us individuals, overall community Examples unacceptable behavior include: use sexualized language imagery, sexual attention advances kind Trolling, insulting derogatory comments, personal political attacks Public private harassment Publishing others’ private information, physical email address, without explicit permission conduct reasonably considered inappropriate professional setting","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"enforcement-responsibilities","dir":"","previous_headings":"","what":"Enforcement Responsibilities","title":"Contributor Covenant Code of Conduct","text":"Community leaders responsible clarifying enforcing standards acceptable behavior take appropriate fair corrective action response behavior deem inappropriate, threatening, offensive, harmful. Community leaders right responsibility remove, edit, reject comments, commits, code, wiki edits, issues, contributions aligned Code Conduct, communicate reasons moderation decisions appropriate.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"scope","dir":"","previous_headings":"","what":"Scope","title":"Contributor Covenant Code of Conduct","text":"Code Conduct applies within community spaces, also applies individual officially representing community public spaces. Examples representing community include using official e-mail address, posting via official social media account, acting appointed representative online offline event.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"enforcement","dir":"","previous_headings":"","what":"Enforcement","title":"Contributor Covenant Code of Conduct","text":"Instances abusive, harassing, otherwise unacceptable behavior may reported community leaders responsible enforcement codeofconduct@posit.co. complaints reviewed investigated promptly fairly. community leaders obligated respect privacy security reporter incident.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"enforcement-guidelines","dir":"","previous_headings":"","what":"Enforcement Guidelines","title":"Contributor Covenant Code of Conduct","text":"Community leaders follow Community Impact Guidelines determining consequences action deem violation Code Conduct:","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_1-correction","dir":"","previous_headings":"Enforcement Guidelines","what":"1. Correction","title":"Contributor Covenant Code of Conduct","text":"Community Impact: Use inappropriate language behavior deemed unprofessional unwelcome community. Consequence: private, written warning community leaders, providing clarity around nature violation explanation behavior inappropriate. public apology may requested.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_2-warning","dir":"","previous_headings":"Enforcement Guidelines","what":"2. Warning","title":"Contributor Covenant Code of Conduct","text":"Community Impact: violation single incident series actions. Consequence: warning consequences continued behavior. interaction people involved, including unsolicited interaction enforcing Code Conduct, specified period time. includes avoiding interactions community spaces well external channels like social media. Violating terms may lead temporary permanent ban.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_3-temporary-ban","dir":"","previous_headings":"Enforcement Guidelines","what":"3. Temporary Ban","title":"Contributor Covenant Code of Conduct","text":"Community Impact: serious violation community standards, including sustained inappropriate behavior. Consequence: temporary ban sort interaction public communication community specified period time. public private interaction people involved, including unsolicited interaction enforcing Code Conduct, allowed period. Violating terms may lead permanent ban.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"id_4-permanent-ban","dir":"","previous_headings":"Enforcement Guidelines","what":"4. Permanent Ban","title":"Contributor Covenant Code of Conduct","text":"Community Impact: Demonstrating pattern violation community standards, including sustained inappropriate behavior, harassment individual, aggression toward disparagement classes individuals. Consequence: permanent ban sort public interaction within community.","code":""},{"path":"https://webfakes.r-lib.org/dev/CODE_OF_CONDUCT.html","id":"attribution","dir":"","previous_headings":"","what":"Attribution","title":"Contributor Covenant Code of Conduct","text":"Code Conduct adapted Contributor Covenant, version 2.1, available https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. Community Impact Guidelines inspired [Mozilla’s code conduct enforcement ladder][https://github.com/mozilla/inclusion]. answers common questions code conduct, see FAQ https://www.contributor-covenant.org/faq. Translations available https://www.contributor-covenant.org/translations.","code":""},{"path":"https://webfakes.r-lib.org/dev/LICENSE.html","id":null,"dir":"","previous_headings":"","what":"MIT License","title":"MIT License","text":"Copyright (c) 2023 webfakes authors Permission hereby granted, free charge, person obtaining copy software associated documentation files (β€œSoftware”), deal Software without restriction, including without limitation rights use, copy, modify, merge, publish, distribute, sublicense, /sell copies Software, permit persons Software furnished , subject following conditions: copyright notice permission notice shall included copies substantial portions Software. SOFTWARE PROVIDED β€œβ€, WITHOUT WARRANTY KIND, EXPRESS IMPLIED, INCLUDING LIMITED WARRANTIES MERCHANTABILITY, FITNESS PARTICULAR PURPOSE NONINFRINGEMENT. EVENT SHALL AUTHORS COPYRIGHT HOLDERS LIABLE CLAIM, DAMAGES LIABILITY, WHETHER ACTION CONTRACT, TORT OTHERWISE, ARISING , CONNECTION SOFTWARE USE DEALINGS SOFTWARE.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"app","dir":"Articles","previous_headings":"","what":"app","title":"webfakes glossary","text":"(Also: fake web app, webfakes app.) web application can served webfakes’s web server, typically another process, app process. Sometimes call fake web app, emphasize use testing real web apps APIs. can create webfakes app new_app() function. webfakes app R object can save disk saveRDS() , can also include package. can start $listen() method. Since main R process runs test suite code, usually run subprocess, see new_app_process() local_app_process().","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"app-process","dir":"Articles","previous_headings":"","what":"app process","title":"webfakes glossary","text":"(Also: web server process, webfakes subprocess.) app process R subprocess, started main R process, serve webfakes app. can create app process object new_app_process() local_app_process(). default actual process start yet, create . can start explicitly $start method app process object, querying URL $url() port $get_port(). test cases, typically start app processes places: setup*.R file, start app whole test suite can use. Alternatively, helper*.R file, start app whole test suite can use, works better interactive development. beginning test file, create app single test file. Inside test_that(), create app single test block. See -details .","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"handler","dir":"Articles","previous_headings":"","what":"handler","title":"webfakes glossary","text":"(handler function.) handler route middleware.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"handler-stack","dir":"Articles","previous_headings":"","what":"handler stack","title":"webfakes glossary","text":"stack handler functions, called app one , passing request response objects . Handlers typically manipulate request /response objects. terminal handler instructs app return response HTTP client. non-terminal handler tells app keep calling handlers, returning string \"next\".","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"httpbin-app","dir":"Articles","previous_headings":"","what":"httpbin app","title":"webfakes glossary","text":"example app, implements excellent https://httpbin.org/ web service. can use simulate certain HTTP responses. handy HTTP clients, potentially useful tools well. Use httpbin_app() create instance app.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"middleware","dir":"Articles","previous_headings":"","what":"middleware","title":"webfakes glossary","text":"middleware handler function bound path. called router, like handler functions. may manipulate request response, can side effect. example built-middleware functions webfakes: mw_json() parses request’s JSON body R object. mw_log() logs requests responses screen file. mw_static() serves static files directory. can also write middleware functions.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"path-matching","dir":"Articles","previous_headings":"","what":"path matching","title":"webfakes glossary","text":"router performs path matching goes handler stack. HTTP method path route match HTTP method URL request, handler called, otherwise . Paths can parameters regular expressions. See ?new_regexp() regular expressions β€œPath parameters” ?new_app() parameters.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"route","dir":"Articles","previous_headings":"","what":"route","title":"webfakes glossary","text":"route handler function bound certain paths web app. request URL matches path route, handler function called, give chance send appropriate response. Route paths may parameters can regular expressions webfakes.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/glossary.html","id":"routing","dir":"Articles","previous_headings":"","what":"routing","title":"webfakes glossary","text":"Routing process going handlers stack, calling handler functions, one , one handles request. handler function route, router calls path matches request URL.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-use-webfakes-in-my-package","dir":"Articles","previous_headings":"","what":"How do I use webfakes in my package?","title":"How to use webfakes in your tests","text":"First, need add webfakes DESCRIPTION file package. Use Suggests field, webfakes needed testing: , unless URL web service argument package functions, might need tweak package code slightly make sure every call real web service can targeted another URL instead (fake app). See next subsection. Last least, need decide want single web app test cases. alternative use different apps test files. Occasionally may want use special app single test case. app runs new subprocess, takes typically 100-400ms start. See sections later writing tests single app multiple apps.","code":"... Suggests: webfakes, testthat ..."},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-make-my-app-connect-to-webfakes-when-the-tests-are-running","dir":"Articles","previous_headings":"","what":"How do I make my app connect to webfakes when the tests are running?","title":"How to use webfakes in your tests","text":"typical scenario, want package connect test app running tests. URL web service argument functions, one way achieve allow specifying web server URL(s) via environment variables. E.g. writing GitHub API client, package can check use GITHUB_URL environment variable. E.g. set, package connects proper GitHub API. testing, can point test app. new_app_process() helps setting temporary environment variables. active process running, removed reset $stop(). example: $local_env() environment variables, webfakes replaces {url} actual app URL. needed default, web server process starts later, URL known yet.","code":"service_url <- function() { Sys.getenv(\"GITHUB_URL\", \"https://api.github.com\") } # rest of the package code foobar <- function() { httr::GET(service_url()) } http <- webfakes::local_app_process(webfakes::httpbin_app(), start = TRUE) http$local_env(list(GITHUB_API = \"{url}\")) Sys.getenv(\"GITHUB_API\") #> [1] \"http://127.0.0.1:46449/\" http$stop() Sys.getenv(\"GITHUB_API\") #> [1] \"\""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-write-my-own-app","dir":"Articles","previous_headings":"","what":"How can I write my own app?","title":"How to use webfakes in your tests","text":"create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:42131/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-14 09:13:41\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-use-httpbin_app-or-another-app-with-testthat","dir":"Articles","previous_headings":"","what":"How do I use httpbin_app() (or another app) with testthat?","title":"How to use webfakes in your tests","text":"can use testthat’s setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See β€œstart app writing tests?” just .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed πŸ₯‡"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-start-the-app-when-writing-the-tests","dir":"Articles","previous_headings":"","what":"How do I start the app when writing the tests?","title":"How to use webfakes in your tests","text":"convenient start webfakes server process(es) working tests interactively, e.g.Β using devtools::load_all(). local_app_process() testthat setup*.R file automatic, devtools::load_all() run files. need source setup*.R files manually, error prone. One solution create server processes testthat helper*.R files. load_all() executes helper files default. instead using setup file, can simply helper-http.R file: app process created helper file, ready use load_all(), (default) actual process started first $url() $get_port() call. can also start manually $start(). Processes created helper files cleaned automatically end test suite, unless clean registering $stop() call setup file, like : practice necessary, R CMD check runs tests separate process, finishes, webfakes processes cleaned well. running devtools::test(), testthat::test_local() another testthat function run (part ) test suite current session, helper*.R files (re)loaded first. terminate currently running app processes, , create new app process objects. test suite auto-start test processes helper*.R, cleaned end test suite, next load_all() test() call, end R session. lets run test code interactively, either via test() manually, without thinking much webfakes processes.","code":"httpbin <- local_app_process(httpbin_app()) withr::defer(httpbin$stop(), testthat::teardown_env())"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"can-i-have-an-app-for-a-single-testthat-test-file","dir":"Articles","previous_headings":"","what":"Can I have an app for a single testthat test file?","title":"How to use webfakes in your tests","text":"run web app single test file, start new_app_process() beginning file, register cleanup using withr::defer(). Even simpler, use local_app_process() new_app_process() automatically stops web server process, end test file: test cases, use web$url() get URL connect .","code":"app <- webfakes::new_app() app$get(\"/hello/:user\", function(req, res) { res$send(paste0(\"Hello \", req$params$user, \"!\")) }) web <- webfakes::local_app_process(app) test_that(\"can use hello API\", { url <- web$url(\"/hello/Gabor\") expect_equal(httr::content(httr::GET(url)), \"Hello Gabor!\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed 🎊"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"can-i-use-an-app-for-a-single-testthat-test","dir":"Articles","previous_headings":"","what":"Can I use an app for a single testthat test?","title":"How to use webfakes in your tests","text":"Sure. need create app process within testthat::test_that() test case. local_app_process() automatically cleans end block. goes like :","code":"test_that(\"query works\", { app <- webfakes::new_app() app$get(\"/hello\", function(req, res) res$send(\"hello there\")) web <- webfakes::local_app_process(app) echo <- httr::content(httr::GET(web$url(\"/hello\"))) expect_equal(echo, \"hello there\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed 🌈"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-test-a-sequence-of-requests","dir":"Articles","previous_headings":"","what":"How do I test a sequence of requests?","title":"How to use webfakes in your tests","text":"test sequence requests, app needs state information kept requests. app$locals environment belongs app, can used record information retrieve future requests. store anything app$locals, something simple like counter variable, something fancier like sqlite database. can add something app$locals via methods directly creating app. E.g. end point fails three times, succeeds , fails three times, etc. Note counter created code starts 0, 1. Let’s run app another process connect : Another example send information app retrieve . POST request store name query parameter app$locals$packages, can queried GET request. Now start app subprocess, run GET query . Let’s POST new information. Stop app process:","code":"store <- webfakes::new_app() store$locals$packages <- list(\"webfakes\") ls(store$locals) #> [1] \"packages\" store$locals$packages #> [[1]] #> [1] \"webfakes\" flaky <- webfakes::new_app() flaky$get(\"/unstable\", function(req, res) { if (identical(res$app$locals$counter, 3L)) { res$app$locals$counter <- NULL res$send_json(object = list(result = \"ok\")) } else { res$app$locals$counter <- c(res$app$locals$counter, 0L)[[1]] + 1L res$send_status(401) } }) pr <- webfakes::new_app_process(flaky) url <- pr$url(\"/unstable\") httr::RETRY(\"GET\", url, times = 4) #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 3.7 seconds... #> Response [http://127.0.0.1:46793/unstable] #> Date: 2025-01-14 09:13 #> Status: 200 #> Content-Type: application/json #> Size: 17 B store <- webfakes::new_app() # Initial \"data\" for the app store$locals$packages <- list(\"webfakes\") # Get method store$get(\"/packages\", function(req, res) { res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) # Post method, store information from the query store$post(\"/packages\", function(req, res) { res$app$locals$packages <- c(res$app$locals$packages, req$query$name) res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) web <- webfakes::local_app_process(store, start = TRUE) # Get current information get_packages <- function() { httr::content( httr::GET( httr::modify_url( web$url(), path = \"packages\" ) ) ) } get_packages() #> [[1]] #> [1] \"webfakes\" post_package <- function(name) { httr::POST( httr::modify_url( web$url(), path = \"packages\", query = list(name = name) ) ) } post_package(\"vcr\") #> Response [http://127.0.0.1:40119/packages?name=vcr] #> Date: 2025-01-14 09:13 #> Status: 200 #> Content-Type: application/json #> Size: 18 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" post_package(\"httptest\") #> Response [http://127.0.0.1:40119/packages?name=httptest] #> Date: 2025-01-14 09:13 #> Status: 200 #> Content-Type: application/json #> Size: 29 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" #> #> [[3]] #> [1] \"httptest\" web$stop()"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-debug-an-app","dir":"Articles","previous_headings":"","what":"How can I debug an app?","title":"How to use webfakes in your tests","text":"debug app, best run main R process, .e.Β via new_app_process(). can add breakpoints, browser() calls handler functions, invoke app another process. might find curl command line tool send HTTP requests app, can just use another R process. example. simply print incoming request object screen now. real debugging session probably want place browser() command . Now start app port 3000: Connect app another R curl process: main R session print incoming request: Press CTRL+C ESC interrupt app main session.","code":"app <- webfakes::new_app() app$get(\"/debug\", function(req, res) { print(req) res$send(\"Got your back\") }) app$listen(port = 3000) #> Running webfakes web app on port 3000 curl -v http://127.0.0.1:3000/debug #> * Trying 127.0.0.1... #> * TCP_NODELAY set #> * Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0) #> > GET /debug HTTP/1.1 #> > Host: 127.0.0.1:3000 #> > User-Agent: curl/7.54.0 #> > Accept: */* #> > #> < HTTP/1.1 200 OK #> < Content-Type: text/plain #> < Content-Length: 13 #> < #> * Connection #0 to host 127.0.0.1 left intact #> Got your back #> #> method: #> get #> url: #> http://127.0.0.1:3000/debug #> client: #> 127.0.0.1 #> query: #> headers: #> Host: 127.0.0.1:3000 #> User-Agent: curl/7.54.0 #> Accept: */* #> fields and methods: #> app # the webfakes_app the request belongs to #> headers # HTTP request headers #> hostname # server hostname, the Host header #> method # HTTP method of request (lowercase) #> path # server path #> protocol # http or https #> query_string # raw query string without '?' #> query # named list of query parameters #> remote_addr # IP address of the client #> url # full URL of the request #> get_header(field) # get a request header #> # see ?webfakes_request for details"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-test-https-requests","dir":"Articles","previous_headings":"","what":"How can I test HTTPS requests?","title":"How to use webfakes in your tests","text":"Serving HTTPS localhost 127.0.0.1 instead HTTP easy, need 1. Set port HTTPS port adding \"s\" suffix port number. Use \"0s\" OS assigned free port: r new_app_process(app, port = \"0s\") default webfakes uses server key + certificate file r system.file(\"cert/localhost/server.pem\", package = \"webfakes\") certificate includes localhost, 127.0.0.1 localhost.localdomain. need another domain IP address, ’ll need create certificate. generate.sh file directory helps . 2. Specify certificate bundle HTTP client using. default server key use ca.crt file webfakes package: r system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") See examples HTTP clients . using curl package, use ca_info option curl::new_handle() curl::handle_setopt(): httr package, use httr::config(cainfo = ...): httr2 package: utils::download.file point CURL_CA_BUNDLE environment variable ca.crt file. Don’t forget undo , HTTP request done.","code":"cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") curl::curl_fetch_memory( http$url(\"/path/to/endpoint\"), handle = curl::new_handle(cainfo = cainfo) ) httr::GET( http$url(\"/headers\", https = TRUE), httr::config(cainfo = cainfo) ) httr2::request(\"https://example.com\") |> httr2::req_options(cainfo = cainfo) |> httr2::req_perform() Sys.setenv( CURL_CA_BUNDLE = system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") ) download.file(http$url(\"/path/to/endpoint\"), res <- tempfile())"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"special-considerations-for-tests-on-windows","dir":"Articles","previous_headings":"How can I test HTTPS requests?","what":"Special considerations for tests on Windows","title":"How to use webfakes in your tests","text":"Unfortunately things simple Windows, HTTP clients. far can tell, easily possible make HTTP clients accept new self-signed certificate. possible libcurl, though, need set CURL_SSL_BACKEND=openssl environment variable. (libcurl must built openssl support course.) need set env var loading libcurl, best set starting R. One way tests run tests subprocess, callr package. Look test-https.R file webfakes complete, current example. tests use helper function, defined helper.R: Example test case: seems like good idea skip_on_cran() HTTPS tests, least Windows, setup yet tested enough consider robust. webfakes uses Mbed TLS serving HTTPS.","code":"callr_curl <- function(url, options = list()) { callr::r( function(url, options) { h <- curl::new_handle() curl::handle_setopt(h, .list = options) curl::curl_fetch_memory(url, handle = h) }, list(url = url, options = options), env = c( callr::rcmd_safe_env(), CURL_SSL_BACKEND = \"openssl\", CURL_CA_BUNDLE = if (\"cainfo\" %in% names(options)) options$cainfo ) ) } # ... cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") resp <- if (.Platform$OS.type == \"windows\") { callr_curl(http$url(\"/hello\"), list(cainfo = cainfo)) } else { curl::curl_fetch_memory( http$url(\"/hello\"), handle = curl::new_handle(cainfo = cainfo) ) } # ..."},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-can-i-run-a-server-on-multiple-ports","dir":"Articles","previous_headings":"","what":"How can I run a server on multiple ports?","title":"How to use webfakes in your tests","text":"can specify multiple port numbers, vector. webfakes listen ports. can also mix HTTP HTTP ports. redirect HTTP port HTTPS port, append \"r\" suffix HTTP port number. port redirected next HTTPS port. E.g. redirect HTTP port 3000 HTTPS port 3001. redirect OS assigned HTTP port OS assigned HTTPS port, use zeros port numbers: can use http$get_ports() query port numbers. can also use get HTTPS URL instead default one (one first port).","code":"new_app_process(app, port = c(\"3000r\", \"3001s\")) http <- new_app_process(app, port = c(\"0r\", \"0s\")) http$url(..., https = TRUE)"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"can-i-test-asynchronous-or-parallel-http-requests","dir":"Articles","previous_headings":"","what":"Can I test asynchronous or parallel HTTP requests?","title":"How to use webfakes in your tests","text":"R single threaded webfakes app runs R interpreter, process multiple requests time. web server runs separate thread, can also process request separate thread, time one request can use R interpreter. important, sometimes test requests may take longer process. example /delay/:secs end point httpbin_app() wait specified number seconds responding, simulate slow web server. wait implemented via standard Sys.sleep() R function, requests can processed sleep . avoid , webfakes can put waiting request hold, return R interpreter, respond incoming requests. Indeed, /delay/ end point implemented using feature. However, request thread web server still busy hold, take advantage , need allow multiple threads. num_threads argument $listen() method webfakes_app lets specify number request threads web server use. Similarly, num_threads argument local_app_process() lets modify number threads. testing asynchronous parallel code, might invoke multiple, possibly delayed requests, best increase number threads. code calls API request concurrently, three times. request takes 1 second answer, web server three threads, together ’ll still take 1 second. (fail webfakes appears process requests sequentially, see issue #108 possible workarounds.)","code":"web <- webfakes::local_app_process( webfakes::httpbin_app(), opts = webfakes::server_opts(num_threads = 6, enable_keep_alive = TRUE) ) testthat::test_that(\"parallel requests\", { url <- web$url(\"/delay/0.5\") p <- curl::new_pool() handles <- replicate(3, curl::new_handle(url = url)) resps <- list() for (handle in handles) { curl::multi_add( handle, done = function(x) resps <<- c(resps, list(x)), fail = stop, pool = p ) } st <- system.time(curl::multi_run(timeout = 3, pool = p)) testthat::expect_true(st[[\"elapsed\"]] < 1.5) }) #> Test passed 🎊"},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-to-make-sure-that-my-code-works-with-the-real-api","dir":"Articles","previous_headings":"","what":"How to make sure that my code works with the real API?","title":"How to use webfakes in your tests","text":"Indeed, use webfakes test cases, never touch real web server. might suspect, ideal, especially control server. web service might change API, test cases fail warn . One practical solution write (least ) flexible tests, can run local fake webserver, real one, quick switch change behavior. found environment variables work great . E.g. FAKE_HTTP_TESTS environment variable set, tests run real web server, otherwise use fake one. Another solution, works best HTTP requests downstream package code, introduce one environment variable API need connect . might set real API servers, fake ones. tests can use kinds servers, can set continuous integration (CI) framework, run tests agains real server (say) day. special CI run makes sure code works well real API. can run tests, locally CI, fake local web server. See question webfakes helps setting environment variables point local server.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/how-to.html","id":"how-do-i-simulate-a-slow-internet-connection","dir":"Articles","previous_headings":"","what":"How do I simulate a slow internet connection?","title":"How to use webfakes in your tests","text":"need use throttle server option start web app. means can run app different connection speed. goes: throttle gives number bytes per second, downloading 200 random bytes fake app take 2 seconds.","code":"library(webfakes) slow <- new_app_process( httpbin_app(), opts = server_opts(throttle = 100) ) resp <- curl::curl_fetch_memory(slow$url(\"/bytes/200\")) resp$times #> redirect namelookup connect pretransfer starttransfer #> 0.000000 0.000033 0.000173 0.000221 0.008264 #> total #> 2.008760"},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"why-civetwet","dir":"Articles","previous_headings":"","what":"Why civetwet?","title":"Webfakes internals","text":"Civetweb small simple. C code . Embedding trivial. main developer nice responsive. project active. code portable works OOB OSes tried. nice features built , e.g.Β limiting download speed.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"why-not-x","dir":"Articles","previous_headings":"","what":"Why not x?","title":"Webfakes internals","text":"httpuv alternative. heavier, contains libuv, also needs 7 non-core packages. AFAICT easy way delay response. also use libuv directly. difficult, probably need deal internals. .e. IOCPs, polls, etc. Libuv also HTTP, need implement use another library. also use R’s internal web server. means redefining default handlers help, fine, temporarily. internal web server limited, handles GET POST requests, give enough information requests. also support delaying response. Mongoose embedded web server, civetweb forked originally. license GPL-2, restrictive.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"multithreading","dir":"Articles","previous_headings":"","what":"Multithreading","title":"Webfakes internals","text":"Threads: main R thread. main web server thread. Request threads, created new connections. Web server worker threads. main rule main R thread can call function R API. civetweb callbacks run civetweb threads, call R API. Currently use begin_request() callback, called request threads. need synchronize request threads main R thread. essentially producer-consumer problem, single consumer, main R thread, multiple producers, request threads. single consumer means queue store jobs length one. good guide solve problem: https://docs.oracle.com/cd/E36784_01/html/E36868/sync-31.html need two conditions, signal 1) something work , 2) new work may come . also need mutex able wait conditions. stored user_data civetweb server instance: nextconn queue, used pass request request thread main R thread. request thread comes , make sure nextconn NULL, waits process_less. given green light, sets nextconn civetweb connection object, wait finish_cond condition, stored connection specific user data: main R thread can use user_data next_conn access information connection request. main R thread done processing request, sets connection’s req_todo field non-zero, signals connection’s finish_cond condition allow request thread continue. also signals process_less condition server, let request threads . Currently main R thread can set req_todo two different values. WEBFAKES_DONE means request processed, request thread can quit. requests like . WEBFAKES_WAIT means request thread still needs stay around sleep specified number secs. sleeping specified amount time, request thread signal process_more , notifying main R thread, also sets main_todo WEBFAKES_WAIT, main R thread knows new request. main R thread can just take stored request req field connection user data case.","code":"struct server_user_data { ... pthread_cond_t process_more; /* there is something to process */ pthread_cond_t process_less; /* we can process something */ pthread_mutex_t process_lock; struct mg_connection *nextconn; ... }; struct connection_user_data { pthread_cond_t finish_cond; /* can finish callback? */ pthread_mutex_t finish_lock; int main_todo; /* what should the main thread do? */ int req_todo; /* what shoudl the request thread do? */ double secs; /* how much should we wait? */ SEXP req; ... };"},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"error-handling","dir":"Articles","previous_headings":"","what":"Error handling","title":"Webfakes internals","text":"server running, errors must handled server must keep running.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"errors-while-starting-up","dir":"Articles","previous_headings":"Error handling","what":"Errors while starting up","title":"Webfakes internals","text":"caught re-thrown, civetweb error log added. error log typically contains information. E.g. common failure specified port free error log meaningful error message case.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"errors-in-request-handlers","dir":"Articles","previous_headings":"Error handling","what":"Errors in request handlers","title":"Webfakes internals","text":"Errors happen R request handler functions caught server send HTTP 500 response, R error message: response sent multiple pieces, possible status code headers sent already. case just send R error message.","code":"while (TRUE) { req <- server_poll(srv) tryCatch( self$.process_request(req), error = function(err) { cat(as.character(err), file = stderr()) response_send_error(req, as.character(err), 500L) } ) }"},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"errors-in-the-c-code-while-processing-the-request-or-response-","dir":"Articles","previous_headings":"Error handling","what":"Errors in the C code while processing the request or response.","title":"Webfakes internals","text":"Errors happen C code processing request response different, probably send anything meaningful client. E.g. frequent error happens connection breaks client closes connection. errors caught server_poll() response_*() R functions, printed screen (see server.R). originate civetweb, also logged civetweb error log. errors invalidate request, finish processing callback. implemented server.R functions (re)throwing webfakes_error, caught silently ignored processing loop. See β€˜Resource cleanup’ resources cleaned error.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"delayed-responses","dir":"Articles","previous_headings":"","what":"Delayed responses","title":"Webfakes internals","text":"See β€˜Multithreading’ section well. create req object incoming request, passing R C. object environment kept response request completely sent . (connection closed reason.) req object also added connection user data civetweb. Additionally, server keeps list (environment) request objects. latter makes sure request object garbage collected, don’t need worry . response delayed, app makes note position handler function handler stack (.stackptr), handler function can called , delay. calls response_write() sends WEBFAKES_WAIT message request thread. main R thread can continue processing potentially serving requests, assuming server started least two threads. wait, request thread sends message main R thread , app’s poll call get request object second (etc.) time. app starts calling handler functions recorded .stackptr position.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"interruption","dir":"Articles","previous_headings":"","what":"Interruption","title":"Webfakes internals","text":"server runs interrupted. (console remotely via processx::process$interrupt().) need make sure server can interrupted waiting new requests (.e.Β main R thread waiting process_more condition, see β€˜Multithreading’ ). pthread_cond_wait() interrupted SIGINT Unix, seemingly, Windows, need use pthread_cond_timedwait(). currently check interrupts every 50ms. server interrupted point, cleanup needed needed, hold resources. fact functions server.R keep server intact, delayed responses, possible call server_poll() . app$listen() method clean server case. Maybe change future. theory C code interrupted points. hand R API functions might error time, need proper cleanup everywhere, see β€˜Resource cleanup’ section . R code interrupted, server.R functions need cleanup. (theory error messages might get lost timing extremely unfortunate server.R function handling error interrupt happens.) app$listen() method cleans server .exit().","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/internals.html","id":"resource-cleanup","dir":"Articles","previous_headings":"","what":"Resource cleanup","title":"Webfakes internals","text":"See also β€˜Interruption’ just . points C code R errors happen code holding resources. use (copy ) cleancall package take care resource cleanup . first place server_poll() C function, request request thread. Creating R object request involves lot R API calls, one fails, need clean resources associated connection. (error logged R server_poll() function continue polling.) cleanup case involves: Sending WEBFAKES_DONE message request thread, quit. Removing request server’s list current requests. Signaling process_less condition, let threads know ready process requests. functions need cleanup C functions work response: response_delay(), response_send_headers(), response_send() response_write(). cleanups similar one server_poll(). finalizer server object takes care cleaning resources associated server, including request objects request threads. also called server_stop() R function, turn called .exit() listen() method. finalizer uses list requests tag xptr object walk requests, finish request threads. Thread sleeping delayed response frequently check server-wide shutdown flag, finalizer also sets, threads quit well. finalizer calls civetweb function mg_stop(). mg_stop() shutdown flag waits request worker threads quit. Given just cleaned , shouldn’t many, just coming , ’ll also observe shutdown flag quit quickly.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"what-is-webfakes","dir":"Articles","previous_headings":"","what":"What is webfakes?","title":"Happy HTTP testing with webfakes","text":"Webfakes R package can spin web servers machine facilitate testing R code. R code needs HTTP connection trivial test: Connectivity problems might prevent tests accessing web server. web server might need authentication, easy convey login information test suite secure way. web server might rate limits, .e, limits number queries per hour day, causing spurious test failures. might want test non-normal conditions, e.g.Β low bandwidth, client rate limited. conditions don’t normally happen web server hard trigger. webfakes can easily start custom web app, running local machine. Webfakes need network connection. Webfakes need authentication. Well, unless want . Webfakes rate limits. Webfakes can simulate low bandwidth, broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"webfakes-vs-mocking","dir":"Articles","previous_headings":"","what":"Webfakes vs mocking","title":"Happy HTTP testing with webfakes","text":"Mocking general technique mimic behavior function object needed test case. case HTTP requests, typically means request response recorded tests run first time, saved disk. Subsequent test runs intercept HTTP requests, match recorded requests replay corresponding recorded response. See example vcr httptest R packages. advantages using webfakes server, mocking: Simpler infrastructure. separate recording replaying phases, recorded files. request matching. can use web client want. E.g. curl base R’s HTTP functions explicitly support mocking currently. need worry sensitive information recorded requests responses. Often easier use testing non-normal conditions, e.g.Β errors hard trigger, low bandwidth, rate limits. Works stream data HTTP connection, instead reading whole response . can reuse app multiple tests, multiple packages. Easier use tests require multiple rounds requests. Comes built-https://httpbin.org compatible app, chances , don’t even need write testing app, just start writing tests right away. Better test writing experience. subjective, mileage may vary.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"webfakes-vs-the-real-api","dir":"Articles","previous_headings":"","what":"Webfakes vs the real API","title":"Happy HTTP testing with webfakes","text":"network needed. skip_if_offline(). Much faster. rate limits. can simulate one want . can write custom app. Simulate low bandwidth broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"webfakes-vs-httpbin-org","dir":"Articles","previous_headings":"","what":"Webfakes vs httpbin.org","title":"Happy HTTP testing with webfakes","text":"network needed. skip_if_offline(). Much faster. can use built-webfakes::httpbin_app() app, easy switch httpbin.org. can write custom app, httpbin.org might need.","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"using-webfakeshttpbin_app-with-testthat","dir":"Articles","previous_headings":"","what":"Using webfakes::httpbin_app() with testthat","title":"Happy HTTP testing with webfakes","text":"can use testthat’s setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See β€œstart app writing tests?” just . can also create web server test file, even single test case. See vignette(\"-\") details .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed πŸ₯‡"},{"path":"https://webfakes.r-lib.org/dev/articles/introduction.html","id":"writing-apps","dir":"Articles","previous_headings":"","what":"Writing apps","title":"Happy HTTP testing with webfakes","text":"builtin httpbin_app() appropriate tests, can write app. can also extend httpbin_app() app, don’t want start scratch. create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:43371/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-14 09:14:13\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"the-oauth2-0-resource-and-authorization-server","dir":"Articles","previous_headings":"","what":"The OAuth2.0 resource and authorization server","title":"OAuth2.0 webfakes apps","text":"First need create resource server, also performs authorization, create variables holding different URLs.","code":"templog <- tempfile() rsapp <- new_app_process( oauth2_resource_app( refresh_duration = .Machine$integer.max, access_duration = 10L, ), opts = server_opts(num_threads = 3) ) regi_url <- rsapp$url(\"/register\") auth_url <- rsapp$url(\"/authorize\") toke_url <- rsapp$url(\"/token\") rsapp #> #> state: #> live #> auto_start: #> TRUE #> process id: #> 9266 #> http url: #> http://127.0.0.1:36859/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app #> get_ports() # query all ports of the app #> get_state() # query web server process state #> local_env(envvars) # set temporary environment variables #> start() # start the app #> url(path, query) # query url for an api path #> stop() # stop web server process #> # see ?webfakes_app_process for details"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"oauth2-0-app-creation-registration","dir":"Articles","previous_headings":"Fake third party application","what":"OAuth2.0 app creation & registration","title":"OAuth2.0 webfakes apps","text":"create third-party app, create variables holding different URLs. need register third-party app resource server. real life done admin third party app. fake resource server provides endpoint /register (regi_url) automatically, without user interaction. need send name third party app, redirect URL, query parameters. resource app replies client id client secret. ’ll use authenticate third party app. real life included config third party app admin. third party app API endpoint, /login/config (already conf_url) configure .","code":"tpapp <- new_app_process( oauth2_third_party_app(\"3P app\"), opts = server_opts(num_threads = 3) ) redi_url <- tpapp$url(\"/login/redirect\") conf_url <- tpapp$url(\"/login/config\") tpapp #> #> state: #> live #> auto_start: #> TRUE #> process id: #> 9279 #> http url: #> http://127.0.0.1:33549/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app #> get_ports() # query all ports of the app #> get_state() # query web server process state #> local_env(envvars) # set temporary environment variables #> start() # start the app #> url(path, query) # query url for an api path #> stop() # stop web server process #> # see ?webfakes_app_process for details url <- paste0( regi_url, \"?name=3P%20app\", \"&redirect_uri=\", redi_url ) reg_resp <- httr::GET(url) reg_resp #> Response [http://127.0.0.1:36859/register?name=3P%20app&redirect_uri=http://127.0.0.1:33549/login/redirect] #> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 184 B regdata <- httr::content(reg_resp) regdata #> $name #> $name[[1]] #> [1] \"3P app\" #> #> #> $client_id #> $client_id[[1]] #> [1] \"id-71bc087ed76a8feb1c761d5743da97\" #> #> #> $client_secret #> $client_secret[[1]] #> [1] \"secret-0c69b132f31e1e2aec6af2a7df5c07\" #> #> #> $redirect_uri #> $redirect_uri[[1]] #> [1] \"http://127.0.0.1:33549/login/redirect\" auth_data <- list( auth_url = auth_url, token_url = toke_url, client_id = regdata$client_id[[1]], client_secret = regdata$client_secret[[1]] ) httr::POST( conf_url, body = auth_data, encode = \"json\" ) #> Response [http://127.0.0.1:33549/login/config] #> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 41 B"},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"the-oauth2-0-dance","dir":"Articles","previous_headings":"Fake third party application","what":"The OAuth2.0 dance","title":"OAuth2.0 webfakes apps","text":"Now user can go login URL third party app, /login fake app, authenticate. start web page R, can run third party app now token, can use authenticate resource app. See test-oauth.R file within webfakes see part programmatically, without browser. default fake third party app saves token(s) local variable, also returns JSON, can see browser: want change behavior, can define redirect_hook function third party app. example: can authenticate new app fake third party app also endpoint return saved tokens: Now third-party app can get data behalf (whole goal OAuth!) β€” also post data behalf resource app endpoints . example /data endpoint third party app queries resource app needs authentication. run following without OAuth dance, access denied. now works fine: real life, access third-party app might limited scopes, fake apps shipped webfakes handle .","code":"browseURL(tpapp$url(\"/login\")) #> { #> \"access_token\": \"token-c6be45eee35844e7ec1d6ada44bc15\", #> \"expiry\": 10, #> \"refresh_token\": \"refresh-token-ee3f1285a6f4585e9f410375e0512d\" #> } thirdp <- oauth2_third_party_app(\"3P app\") thirdp$redirect_hook <- function(res, tokens) { res$ set_status(200L)$ send(\"Authentication complete, return to R!\") } tpapp2 <- new_app_process( thirdp, opts = server_opts(num_threads = 3) ) redi_url2 <- tpapp2$url(\"/login/redirect\") conf_url2 <- tpapp2$url(\"/login/config\") tpapp2 #> #> state: #> live #> auto_start: #> TRUE #> process id: #> 9293 #> http url: #> http://127.0.0.1:43333/ #> fields and methods: #> get_app() # get the app object #> get_port() # query (first) port of the app #> get_ports() # query all ports of the app #> get_state() # query web server process state #> local_env(envvars) # set temporary environment variables #> start() # start the app #> url(path, query) # query url for an api path #> stop() # stop web server process #> # see ?webfakes_app_process for details url2 <- paste0( regi_url, \"?name=3P%20app2\", \"&redirect_uri=\", redi_url2 ) reg_resp2 <- httr::GET(url2) reg_resp2 #> Response [http://127.0.0.1:36859/register?name=3P%20app2&redirect_uri=http://127.0.0.1:43333/login/redirect] #> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 185 B regdata2 <- httr::content(reg_resp2) regdata2 #> $name #> $name[[1]] #> [1] \"3P app2\" #> #> #> $client_id #> $client_id[[1]] #> [1] \"id-e881e8c22afbe2b2c3c26af6d94e20\" #> #> #> $client_secret #> $client_secret[[1]] #> [1] \"secret-6748f149aaa0d02c1a91cede4ccb04\" #> #> #> $redirect_uri #> $redirect_uri[[1]] #> [1] \"http://127.0.0.1:43333/login/redirect\" auth_data2 <- list( auth_url = auth_url, token_url = toke_url, client_id = regdata2$client_id[[1]], client_secret = regdata2$client_secret[[1]] ) httr::POST( conf_url2, body = auth_data2, encode = \"json\" ) #> Response [http://127.0.0.1:43333/login/config] #> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 41 B browseURL(tpapp2$url(\"/login\")) #> Authentication complete, return to R! httr::content(httr::GET(tpapp2$url(\"/locals\"))) #> $access_token #> [1] \"token-08e1470fb2bbfa9216925390655281\" #> #> $expiry #> [1] 10 #> #> $refresh_token #> [1] \"refresh-token-f70b06b589156b9b5d462b040c500c\" resp_data <- httr::GET(tpapp2$url(\"/data\")) resp_data #> Response [http://127.0.0.1:43333/data] #> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 24 B httr::content(resp_data, as = \"text\") #> No encoding supplied: defaulting to UTF-8. #> [1] \"{\\\"data\\\":[\\\"top secret!\\\"]}\""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"the-fake-resource-server-and-httr","dir":"Articles","previous_headings":"","what":"The fake resource server and httr","title":"OAuth2.0 webfakes apps","text":"use httr’s OAuth tool, ’s gymnastics happening R playing role third-party app via httr httpuv (listen redirect URI).","code":""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"oauth2-0-app-creation-registration-1","dir":"Articles","previous_headings":"The fake resource server and httr","what":"OAuth2.0 app creation & registration","title":"OAuth2.0 webfakes apps","text":"’s crucial setting httr::oauth_callback() redirect URI, creating app R package uses OAuth2.0 authenticate resource server. Now set registration data third-party app. Now can launch token creation. Without token, query resource server fails: token, successful. httr also automatically refreshes token needed.","code":"url3 <- paste0( regi_url, \"?name=3P%20app2\", \"&redirect_uri=\", httr::oauth_callback() ) reg_resp3 <- httr::GET(url3) reg_resp3 #> Response [http://127.0.0.1:36859/register?name=3P%20app2&redirect_uri=http://localhost:1410/] #> Date: 2025-01-14 09:14 #> Status: 200 #> Content-Type: application/json #> Size: 170 B regdata3 <- httr::content(reg_resp3) regdata3 #> $name #> $name[[1]] #> [1] \"3P app2\" #> #> #> $client_id #> $client_id[[1]] #> [1] \"id-eefc6f17f2775997497365921db914\" #> #> #> $client_secret #> $client_secret[[1]] #> [1] \"secret-8632991fdd4c1adc9430558682ca2a\" #> #> #> $redirect_uri #> $redirect_uri[[1]] #> [1] \"http://localhost:1410/\" app <- httr::oauth_app( \"3P app2\", key = regdata3$client_id[[1]], secret = regdata3$client_secret[[1]], redirect_uri = httr::oauth_callback() ) endpoint <- httr::oauth_endpoint( authorize = auth_url, access = toke_url ) token <- oauth2_httr_login( httr::oauth2.0_token(endpoint, app, cache = FALSE) ) #> Waiting for authentication in browser... #> Press Esc/Ctrl + C to abort #> Authentication complete. token #> #> #> authorize: http://127.0.0.1:36859/authorize #> access: http://127.0.0.1:36859/token #> 3P app2 #> key: id-eefc6f17f2775997497365921db914 #> secret: #> access_token, expiry, refresh_token #> --- httr::GET(rsapp$url(\"/data\")) #> Response [http://127.0.0.1:36859/data] #> Date: 2025-01-14 09:14 #> Status: 401 #> Content-Type: text/plain #> Size: 20 B httr::content( httr::GET(rsapp$url(\"/data\"), config = token), as = \"text\" ) #> No encoding supplied: defaulting to UTF-8. #> [1] \"{\\\"data\\\":[\\\"top secret!\\\"]}\""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"applications","dir":"Articles","previous_headings":"Advanced topics","what":"Applications","title":"OAuth2.0 webfakes apps","text":"apps, resource server app, can now test code helps users create store OAuth2.0 token. Like webfakes apps, OAuth2.0 apps extensible: can add endpoints middleware . E.g. add logging via mw_log() new endpoint. want customize one apps apps lot, might make sense use source code starting point inspiration.","code":"rsapp2 <- oauth2_resource_app( refresh_duration = .Machine$integer.max, access_duration = 10L ) logfile <- tempfile(\"oauth-rs-\", fileext = \".log\") rsapp2$use(mw_log(stream = logfile), .first = TRUE) rsapp2$get(\"/docs\", function(req, res) { res$ set_status(200L)$ send(\"See vignette('oauth', package = 'webfakes')\") }) rsapp2_process <- new_app_process( rsapp2, opts = server_opts(num_threads = 3) )"},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"debugging","dir":"Articles","previous_headings":"Advanced topics","what":"Debugging","title":"OAuth2.0 webfakes apps","text":"See usual debugging advice webfakes apps. particular, can add mw_log() middleware write log app file, like . resource app /locals endpoint, returns data stored app, includes tokens refresh tokens:","code":"httr::content( httr::GET(rsapp$url(\"/locals\")) ) #> $apps #> $apps[[1]] #> $apps[[1]]$name #> [1] \"3P app\" #> #> $apps[[1]]$client_id #> [1] \"id-71bc087ed76a8feb1c761d5743da97\" #> #> $apps[[1]]$client_secret #> [1] \"secret-0c69b132f31e1e2aec6af2a7df5c07\" #> #> $apps[[1]]$redirect_uri #> [1] \"http://127.0.0.1:33549/login/redirect\" #> #> #> $apps[[2]] #> $apps[[2]]$name #> [1] \"3P app2\" #> #> $apps[[2]]$client_id #> [1] \"id-e881e8c22afbe2b2c3c26af6d94e20\" #> #> $apps[[2]]$client_secret #> [1] \"secret-6748f149aaa0d02c1a91cede4ccb04\" #> #> $apps[[2]]$redirect_uri #> [1] \"http://127.0.0.1:43333/login/redirect\" #> #> #> $apps[[3]] #> $apps[[3]]$name #> [1] \"3P app2\" #> #> $apps[[3]]$client_id #> [1] \"id-eefc6f17f2775997497365921db914\" #> #> $apps[[3]]$client_secret #> [1] \"secret-8632991fdd4c1adc9430558682ca2a\" #> #> $apps[[3]]$redirect_uri #> [1] \"http://localhost:1410/\" #> #> #> #> $access #> $access[[1]] #> $access[[1]]$client_id #> [1] \"id-71bc087ed76a8feb1c761d5743da97\" #> #> $access[[1]]$token #> [1] \"token-c6be45eee35844e7ec1d6ada44bc15\" #> #> $access[[1]]$expiry #> [1] \"2025-01-14 09:14:28\" #> #> #> $access[[2]] #> $access[[2]]$client_id #> [1] \"id-e881e8c22afbe2b2c3c26af6d94e20\" #> #> $access[[2]]$token #> [1] \"token-08e1470fb2bbfa9216925390655281\" #> #> $access[[2]]$expiry #> [1] \"2025-01-14 09:14:29\" #> #> #> $access[[3]] #> $access[[3]]$client_id #> [1] \"id-eefc6f17f2775997497365921db914\" #> #> $access[[3]]$token #> [1] \"token-1f46a0366717828ac5cc842c163a31\" #> #> $access[[3]]$expiry #> [1] \"2025-01-14 09:14:29\" #> #> #> #> $refresh #> $refresh[[1]] #> $refresh[[1]]$client_id #> [1] \"id-71bc087ed76a8feb1c761d5743da97\" #> #> $refresh[[1]]$token #> [1] \"refresh-token-ee3f1285a6f4585e9f410375e0512d\" #> #> $refresh[[1]]$expiry #> [1] \"2093-02-01 12:28:25\" #> #> #> $refresh[[2]] #> $refresh[[2]]$client_id #> [1] \"id-e881e8c22afbe2b2c3c26af6d94e20\" #> #> $refresh[[2]]$token #> [1] \"refresh-token-f70b06b589156b9b5d462b040c500c\" #> #> $refresh[[2]]$expiry #> [1] \"2093-02-01 12:28:26\" #> #> #> $refresh[[3]] #> $refresh[[3]]$client_id #> [1] \"id-eefc6f17f2775997497365921db914\" #> #> $refresh[[3]]$token #> [1] \"refresh-token-81d2b2f09bcf64302605ab6a9750b3\" #> #> $refresh[[3]]$expiry #> [1] \"2093-02-01 12:28:26\""},{"path":"https://webfakes.r-lib.org/dev/articles/oauth.html","id":"case-study-for-oauth2-0-testing","dir":"Articles","previous_headings":"","what":"Case study for OAuth2.0 testing","title":"OAuth2.0 webfakes apps","text":"Consider package function uses OAuth2.0 access GitHub. section ’ll show can use webfakes test function. gh_repos() uses OAuth2.0 app get token GitHub, uses token authenticate list public repositories current user. real package probably get token separate function, cache , re-use multiple queries. (Also, don’t actually need authorization listing public repositories, somewhat artificial example.) run function, need register app https://github.com/settings/developers. Make sure set http://localhost:1410 authorization callback URL. can set options please. R set GH_CLIENT_ID GH_CLIENT_SECRET environment variables client id client secret app: run function opens Window browser, can authorize app access public information GitHub. subsequent runs browser window still opens, authorization automatic. real package cache OAuth2.0 tokens machine, e.g.Β using keyring package. Let’s write test case now gh_repos(). use oauth2_repource_app() fake GitHub. important points test case: Since test case fail port 1410 taken, safer skip CRAN. fake_app webfakes app, almost oauth2_resource_app(), two changes. first change end point getting token /access_token GitHub uses. second also add /user/repos endpoint. endpoint needs authorization, calls app$is_authorized(), fails without , app returns 401 Access denied. fake_proc app process runs fake GH app. Always run fake OAuth2.0 apps multiple threads. fake GitHub app endpoint register app testing. need send app’s name correct redirect URI, fake_app reply fake client id client secret. set GH_CLIENT_ID GH_CLIENT_SECRET fake ones just got fake_app. also redirect gh_token() fake app, setting FAKE_GH_API_BASE FAKE_GH_AUTH_BASE. Now call gh_repos(), connect fake app. also need make sure httr can log fake app, without browser interaction. webfakes::oauth2_httr_login() wrapper takes care . runs HTTP client background process, perform log . background process webfakes::oauth2_httr_login() fails log reason, test code might freeze. another good reason skip test CRAN. suppressMessages() suppresses (harmless) httr messages authorization. test case lot boilerplate set manage fake apps. Note can refactor code helper function, starts fake app sub-process demand helper file. way can reuse multiple test files test cases. test case ensures OAuth2.0 setup gh_repos() stays correct.","code":"gh_base <- function() { Sys.getenv(\"FAKE_GH_API_BASE\", \"https://api.github.com\") } gh_oauth_base <- function() { Sys.getenv( \"FAKE_GH_AUTH_BASE\", \"https://github.com/login/oauth\" ) } gh_repos <- function() { ghapp <- httr::oauth_app( \"gh_repos\", key = Sys.getenv(\"GH_CLIENT_ID\"), secret = Sys.getenv(\"GH_CLIENT_SECRET\") ) endpoints <- httr::oauth_endpoint( base_url = gh_oauth_base(), request = NULL, authorize = \"authorize\", access = \"access_token\" ) gh_token <- httr::oauth2.0_token( endpoints, ghapp, cache = FALSE ) httr_token <- httr::config(token = gh_token) response <- httr::GET( paste0(gh_base(), \"/user/repos?visibility=public\"), httr::add_headers(Accept = \"application/vnd.github.v3+json\"), config = httr_token ) httr::stop_for_status(response) json <- httr::content(response, as = \"text\") repos <- jsonlite::fromJSON(json, simplifyVector = FALSE) vapply(repos, function(r) r$full_name, character(1)) } Sys.setenv(GH_CLIENT_ID = \"\") Sys.setenv(GH_CLIENT_SECRET = \"\") gh_repos() #> Waiting for authentication in browser... #> Press Esc/Ctrl + C to abort #> Authentication complete. #> [1] \"gaborcsardi/altlist\" \"gaborcsardi/argufy\" #> [3] \"gaborcsardi/async\" \"gaborcsardi/disposables\" #> [5] \"gaborcsardi/dotenv\" \"gaborcsardi/falsy\" #> [7] \"gaborcsardi/franc\" \"gaborcsardi/ISA\" #> [9] \"gaborcsardi/keypress\" \"gaborcsardi/lpSolve\" #> [11] \"gaborcsardi/macBriain\" \"gaborcsardi/maxygen\" #> [13] \"gaborcsardi/MISO\" \"gaborcsardi/msgtools\" #> [15] \"gaborcsardi/notifier\" \"gaborcsardi/odbc\" #> [17] \"gaborcsardi/parr\" \"gaborcsardi/parsedate\" #> [19] \"gaborcsardi/prompt\" \"gaborcsardi/r-font\" #> [21] \"gaborcsardi/r-source\" \"gaborcsardi/rcorpora\" #> [23] \"gaborcsardi/roxygenlabs\" \"gaborcsardi/sankey\" #> [25] \"gaborcsardi/secret\" \"gaborcsardi/spark\" #> [27] \"gaborcsardi/standalones\" testthat::test_that(\"gh_repos\", { testthat::skip_on_cran() fake_app <- oauth2_resource_app(token_endpoint = \"/access_token\") fake_app$get(\"/user/repos\", function(req, res) { if (!app$is_authorized(req, res)) return() res$send_json(list( list(full_name = \"user/repo1\"), list(full_name = \"user/repo2\") ), auto_unbox = TRUE) }) fake_proc <- local_app_process( fake_app, opts = server_opts(num_threads = 3) ) # register the app to our fake GH server reg_url <- paste0( fake_proc$url(\"/register\"), \"?name=gh_repos&redirect_uri=http://localhost:1410/\" ) regdata <- httr::content(httr::GET(reg_url)) withr::local_envvar( GH_CLIENT_ID = regdata$client_id[[1]], GH_CLIENT_SECRET = regdata$client_secret[[1]], FAKE_GH_API_BASE = fake_proc$url(), FAKE_GH_AUTH_BASE = fake_proc$url() ) ret <- suppressMessages(webfakes::oauth2_httr_login( gh_repos() )) testthat::expect_equal(ret, c(\"user/repo1\", \"user/repo2\")) }) #> Test passed πŸŽ‰"},{"path":"https://webfakes.r-lib.org/dev/authors.html","id":null,"dir":"","previous_headings":"","what":"Authors","title":"Authors and Citation","text":"GΓ‘bor CsΓ‘rdi. Author, maintainer. . Copyright holder, funder. Civetweb contributors. Contributor. see inst/credits/ciwetweb.md Redoc contributors. Contributor. see inst/credits/redoc.md L. Peter Deutsch. Contributor. src/md5.h Martin Purschke. Contributor. src/md5.h Aladdin Enterprises. Copyright holder. src/md5.h MaΓ«lle Salmon. Contributor.","code":""},{"path":"https://webfakes.r-lib.org/dev/authors.html","id":"citation","dir":"","previous_headings":"","what":"Citation","title":"Authors and Citation","text":"CsΓ‘rdi G (2025). webfakes: Fake Web Apps HTTP Testing. R package version 1.3.2.9000, https://github.com/r-lib/webfakes, https://webfakes.r-lib.org/.","code":"@Manual{, title = {webfakes: Fake Web Apps for HTTP Testing}, author = {GΓ‘bor CsΓ‘rdi}, year = {2025}, note = {R package version 1.3.2.9000, https://github.com/r-lib/webfakes}, url = {https://webfakes.r-lib.org/}, }"},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"webfakes","dir":"","previous_headings":"","what":"Fake Web Apps for HTTP Testing","title":"Fake Web Apps for HTTP Testing","text":"web server happy HTTP testing Lightweight fake web apps testing. Built using civetweb embedded web server.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"features","dir":"","previous_headings":"","what":"Features","title":"Fake Web Apps for HTTP Testing","text":"Complete web app framework, define handlers HTTP requests R. Write app custom test cases; use app similar https://httpbin.org API, often don’t need write web app (e.g.Β writing HTTP client (httr, curl, crul). Run one web app per test suite, per test file per test case. Flexible path matching, parameters regular expressions. Built templating system using glue bring template engine. Middleware parse JSON, multipart URL encoded request bodies. web app just R object. can saved disk, copied another R process, etc. web app extensible, adding new routes middleware . Helper functions sending JSON, files disk, etc. App-specific environment store data including data requests fake app. web app launched R, can interact R also command line, browser, etc. Nice debugging. web server runs R process, problems local firewalls. Multi-threaded web server supports concurrent HTTP requests. Limit download speed simulate low bandwidth.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"optional-dependencies","dir":"","previous_headings":"","what":"Optional dependencies","title":"Fake Web Apps for HTTP Testing","text":"jsonlite package needed mw_json() middleware, response$send_json() method httpbin_app() app. glue package needed tmpl_glue() template engine. callr package needed new_app_process() local_app_process work. /brotli endpoint httpbin_app() needs brotli package. /deflate endpoint httpbin_app() needs zip package. /digest-auth endpoint httpbin_app() needs digest package. git_app() requires processx package.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"installation","dir":"","previous_headings":"","what":"Installation","title":"Fake Web Apps for HTTP Testing","text":"Install release version CRAN: need development version package, install GitHub:","code":"install.packages(\"webfakes\") pak::pak(\"r-lib/webfakes\")"},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"usage","dir":"","previous_headings":"","what":"Usage","title":"Fake Web Apps for HTTP Testing","text":"Start web app beginning tests test file, stop . example testthat package. Suppose want test get_hello() function can query API: local_app_process() helps clean web server process test block, test file. similar withr::local_* functions. testing HTTP clients can often use built httpbin_app():","code":"app <- webfakes::new_app() app$get(\"/hello/:user\", function(req, res) { res$send(paste0(\"Hello \", req$params$user, \"!\")) }) web <- webfakes::local_app_process(app) test_that(\"can use hello API\", { url <- web$url(\"/hello/Gabor\") expect_equal(get_hello(url), \"Hello Gabor!\") }) httpbin <- webfakes::local_app_process(webfakes::httpbin_app()) test_that(\"HTTP errors are caught\", { url <- httpbin$url(\"/status/404\") resp <- httr::GET(url) expect_error(httr::stop_for_status(resp), class = \"http_404\") }) #> Test passed 😸"},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"documentation","dir":"","previous_headings":"","what":"Documentation","title":"Fake Web Apps for HTTP Testing","text":"See https://webfakes.r-lib.org","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"other-solutions-for-http-testing-in-r","dir":"","previous_headings":"Links","what":"Other solutions for HTTP testing in R:","title":"Fake Web Apps for HTTP Testing","text":"vcr httptest","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"r-web-application-frameworks","dir":"","previous_headings":"Links","what":"R web application frameworks","title":"Fake Web Apps for HTTP Testing","text":"webfakes focuses testing, packages writing real web apps: shiny opencpu plumber fiery RestRserve","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"code-of-conduct","dir":"","previous_headings":"","what":"Code of Conduct","title":"Fake Web Apps for HTTP Testing","text":"Please note webfakes project released Contributor Code Conduct. contributing project, agree abide terms.","code":""},{"path":"https://webfakes.r-lib.org/dev/index.html","id":"license","dir":"","previous_headings":"","what":"License","title":"Fake Web Apps for HTTP Testing","text":"MIT Β© RStudio","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Web app that acts as a git http server β€” git_app","title":"Web app that acts as a git http server β€” git_app","text":"useful tests need HTTP git server.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Web app that acts as a git http server β€” git_app","text":"","code":"git_app( git_root, git_cmd = \"git\", git_timeout = as.difftime(1, units = \"mins\"), filter = TRUE, cleanup = TRUE )"},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Web app that acts as a git http server β€” git_app","text":"git_root Path root directory tree served. git_cmd Command call, default \"git\". may also full path git. git_timeout difftime object, time limit git command. filter Whether support filter capability server. cleanup Whether clean git_root app garbage collected.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/git_app.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Web app that acts as a git http server β€” git_app","text":"","code":"if (FALSE) { dir.create(tmp <- tempfile()) setwd(tmp) system(\"git clone --bare https://github.com/cran/crayon\") system(\"git clone --bare https://github.com/cran/glue\") app <- git_app(tmp) git <- new_app_process(app) system(paste(\"git ls-remote\", git$url(\"/crayon\"))) }"},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":null,"dir":"Reference","previous_headings":"","what":"webfakes glossary β€” glossary","title":"webfakes glossary β€” glossary","text":"webfakes glossary","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"webfakes-glossary","dir":"Reference","previous_headings":"","what":"Webfakes glossary","title":"webfakes glossary β€” glossary","text":"webfakes package uses vocabulary standard web apps, especially developed Express.js, necessarily well known R package developers.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"app","dir":"Reference","previous_headings":"","what":"app","title":"webfakes glossary β€” glossary","text":"(Also: fake web app, webfakes app.) web application can served webfakes's web server, typically another process, app process. Sometimes call fake web app, emphasize use testing real web apps APIs. can create webfakes app new_app() function. webfakes app R object can save disk saveRDS() , can also include package. can start $listen() method. Since main R process runs test suite code, usually run subprocess, see new_app_process() local_app_process().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"app-process","dir":"Reference","previous_headings":"","what":"app process","title":"webfakes glossary β€” glossary","text":"(Also: web server process, webfakes subprocess.) app process R subprocess, started main R process, serve webfakes app. can create app process object new_app_process() local_app_process(). default actual process start yet, create . can start explicitly $start method app process object, querying URL $url() port $get_port(). test cases, typically start app processes places: setup*.R file, start app whole test suite can use. Alternatively, helper*.R file, start app whole test suite can use, works better interactive development. beginning test file, create app single test file. Inside test_that(), create app single test block. See -details .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"handler","dir":"Reference","previous_headings":"","what":"handler","title":"webfakes glossary β€” glossary","text":"(handler function.) handler route middleware.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"handler-stack","dir":"Reference","previous_headings":"","what":"handler stack","title":"webfakes glossary β€” glossary","text":"stack handler functions, called app one , passing request response objects . Handlers typically manipulate request /response objects. terminal handler instructs app return response HTTP client. non-terminal handler tells app keep calling handlers, returning string \"next\".","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"httpbin-app","dir":"Reference","previous_headings":"","what":"httpbin app","title":"webfakes glossary β€” glossary","text":"example app, implements excellent https://httpbin.org/ web service. can use simulate certain HTTP responses. handy HTTP clients, potentially useful tools well. Use httpbin_app() create instance app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"middleware","dir":"Reference","previous_headings":"","what":"middleware","title":"webfakes glossary β€” glossary","text":"middleware handler function bound path. called router, like handler functions. may manipulate request response, can side effect. example built-middleware functions webfakes: mw_json() parses request's JSON body R object. mw_log() logs requests responses screen file. mw_static() serves static files directory. can also write middleware functions.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"path-matching","dir":"Reference","previous_headings":"","what":"path matching","title":"webfakes glossary β€” glossary","text":"router performs path matching goes handler stack. HTTP method path route match HTTP method URL request, handler called, otherwise . Paths can parameters regular expressions. See ?new_regexp() regular expressions \"Path parameters\" ?new_app() parameters.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"route","dir":"Reference","previous_headings":"","what":"route","title":"webfakes glossary β€” glossary","text":"route handler function bound certain paths web app. request URL matches path route, handler function called, give chance send appropriate response. Route paths may parameters can regular expressions webfakes.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/glossary.html","id":"routing","dir":"Reference","previous_headings":"","what":"routing","title":"webfakes glossary β€” glossary","text":"Routing process going handlers stack, calling handler functions, one , one handles request. handler function route, router calls path matches request URL.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":null,"dir":"Reference","previous_headings":"","what":"How to use webfakes in your tests β€” how-to","title":"How to use webfakes in your tests β€” how-to","text":"use webfakes tests","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-use-webfakes-in-my-package-","dir":"Reference","previous_headings":"","what":"How do I use webfakes in my package?","title":"How to use webfakes in your tests β€” how-to","text":"First, need add webfakes DESCRIPTION file package. Use Suggests field, webfakes needed testing: , unless URL web service argument package functions, might need tweak package code slightly make sure every call real web service can targeted another URL instead (fake app). See next subsection. Last least, need decide want single web app test cases. alternative use different apps test files. Occasionally may want use special app single test case. app runs new subprocess, takes typically 100-400ms start. See sections later writing tests single app multiple apps.","code":"... Suggests: webfakes, testthat ..."},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-make-my-app-connect-to-webfakes-when-the-tests-are-running-","dir":"Reference","previous_headings":"","what":"How do I make my app connect to webfakes when the tests are running?","title":"How to use webfakes in your tests β€” how-to","text":"typical scenario, want package connect test app running tests. URL web service argument functions, one way achieve allow specifying web server URL(s) via environment variables. E.g. writing GitHub API client, package can check use GITHUB_URL environment variable. E.g. set, package connects proper GitHub API. testing, can point test app. new_app_process() helps setting temporary environment variables. active process running, removed reset $stop(). example: $local_env() environment variables, webfakes replaces {url} actual app URL. needed default, web server process starts later, URL known yet.","code":"service_url <- function() { Sys.getenv(\"GITHUB_URL\", \"https://api.github.com\") } # rest of the package code foobar <- function() { httr::GET(service_url()) } http <- webfakes::local_app_process(webfakes::httpbin_app(), start = TRUE) http$local_env(list(GITHUB_API = \"{url}\")) Sys.getenv(\"GITHUB_API\") #> [1] \"http://127.0.0.1:64362/\" http$stop() Sys.getenv(\"GITHUB_API\") #> [1] \"\""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-write-my-own-app-","dir":"Reference","previous_headings":"","what":"How can I write my own app?","title":"How to use webfakes in your tests β€” how-to","text":"create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:64364/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-14 10:07:38\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-use-httpbin-app-or-another-app-with-testthat-","dir":"Reference","previous_headings":"","what":"How do I use httpbin_app() (or another app) with testthat?","title":"How to use webfakes in your tests β€” how-to","text":"can use testthat's setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See \"start app writing tests?\" just .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-start-the-app-when-writing-the-tests-","dir":"Reference","previous_headings":"","what":"How do I start the app when writing the tests?","title":"How to use webfakes in your tests β€” how-to","text":"convenient start webfakes server process(es) working tests interactively, e.g. using devtools::load_all(). local_app_process() testthat setup*.R file automatic, devtools::load_all() run files. need source setup*.R files manually, error prone. One solution create server processes testthat helper*.R files. load_all() executes helper files default. instead using setup file, can simply helper-http.R file: app process created helper file, ready use load_all(), (default) actual process started first $url() $get_port() call. can also start manually $start(). Processes created helper files cleaned automatically end test suite, unless clean registering $stop() call setup file, like : practice necessary, R CMD check runs tests separate process, finishes, webfakes processes cleaned well. running devtools::test(), testthat::test_local() another testthat function run (part ) test suite current session, helper*.R files (re)loaded first. terminate currently running app processes, , create new app process objects. test suite auto-start test processes helper*.R, cleaned end test suite, next load_all() test() call, end R session. lets run test code interactively, either via test() manually, without thinking much webfakes processes.","code":"httpbin <- local_app_process(httpbin_app()) withr::defer(httpbin$stop(), testthat::teardown_env())"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"can-i-have-an-app-for-a-single-testthat-test-file-","dir":"Reference","previous_headings":"","what":"Can I have an app for a single testthat test file?","title":"How to use webfakes in your tests β€” how-to","text":"run web app single test file, start new_app_process() beginning file, register cleanup using withr::defer(). Even simpler, use local_app_process() new_app_process() automatically stops web server process, end test file: test cases, use web$url() get URL connect .","code":"app <- webfakes::new_app() app$get(\"/hello/:user\", function(req, res) { res$send(paste0(\"Hello \", req$params$user, \"!\")) }) web <- webfakes::local_app_process(app) test_that(\"can use hello API\", { url <- web$url(\"/hello/Gabor\") expect_equal(httr::content(httr::GET(url)), \"Hello Gabor!\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"can-i-use-an-app-for-a-single-testthat-test-","dir":"Reference","previous_headings":"","what":"Can I use an app for a single testthat test?","title":"How to use webfakes in your tests β€” how-to","text":"Sure. need create app process within testthat::test_that() test case. local_app_process() automatically cleans end block. goes like :","code":"test_that(\"query works\", { app <- webfakes::new_app() app$get(\"/hello\", function(req, res) res$send(\"hello there\")) web <- webfakes::local_app_process(app) echo <- httr::content(httr::GET(web$url(\"/hello\"))) expect_equal(echo, \"hello there\") }) #> No encoding supplied: defaulting to UTF-8. #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-test-a-sequence-of-requests-","dir":"Reference","previous_headings":"","what":"How do I test a sequence of requests?","title":"How to use webfakes in your tests β€” how-to","text":"test sequence requests, app needs state information kept requests. app$locals environment belongs app, can used record information retrieve future requests. store anything app$locals, something simple like counter variable, something fancier like sqlite database. can add something app$locals via methods directly creating app. E.g. end point fails three times, succeeds , fails three times, etc. Note counter created code starts 0, 1. run app another process connect : Another example send information app retrieve . POST request store name query parameter app$locals$packages, can queried GET request. Now start app subprocess, run GET query . POST new information. Stop app process:","code":"store <- webfakes::new_app() store$locals$packages <- list(\"webfakes\") ls(store$locals) #> [1] \"packages\" store$locals$packages #> [[1]] #> [1] \"webfakes\" flaky <- webfakes::new_app() flaky$get(\"/unstable\", function(req, res) { if (identical(res$app$locals$counter, 3L)) { res$app$locals$counter <- NULL res$send_json(object = list(result = \"ok\")) } else { res$app$locals$counter <- c(res$app$locals$counter, 0L)[[1]] + 1L res$send_status(401) } }) pr <- webfakes::new_app_process(flaky) url <- pr$url(\"/unstable\") httr::RETRY(\"GET\", url, times = 4) #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 1 seconds... #> Request failed [401]. Retrying in 2.1 seconds... #> Response [http://127.0.0.1:64374/unstable] #> Date: 2025-01-14 10:07 #> Status: 200 #> Content-Type: application/json #> Size: 17 B store <- webfakes::new_app() # Initial \"data\" for the app store$locals$packages <- list(\"webfakes\") # Get method store$get(\"/packages\", function(req, res) { res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) # Post method, store information from the query store$post(\"/packages\", function(req, res) { res$app$locals$packages <- c(res$app$locals$packages, req$query$name) res$send_json(res$app$locals$packages, auto_unbox = TRUE) }) web <- webfakes::local_app_process(store, start = TRUE) # Get current information get_packages <- function() { httr::content( httr::GET( httr::modify_url( web$url(), path = \"packages\" ) ) ) } get_packages() #> [[1]] #> [1] \"webfakes\" post_package <- function(name) { httr::POST( httr::modify_url( web$url(), path = \"packages\", query = list(name = name) ) ) } post_package(\"vcr\") #> Response [http://127.0.0.1:64380/packages?name=vcr] #> Date: 2025-01-14 10:07 #> Status: 200 #> Content-Type: application/json #> Size: 18 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" post_package(\"httptest\") #> Response [http://127.0.0.1:64380/packages?name=httptest] #> Date: 2025-01-14 10:07 #> Status: 200 #> Content-Type: application/json #> Size: 29 B # Get current information get_packages() #> [[1]] #> [1] \"webfakes\" #> #> [[2]] #> [1] \"vcr\" #> #> [[3]] #> [1] \"httptest\" web$stop()"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-debug-an-app-","dir":"Reference","previous_headings":"","what":"How can I debug an app?","title":"How to use webfakes in your tests β€” how-to","text":"debug app, best run main R process, .e. via new_app_process(). can add breakpoints, browser() calls handler functions, invoke app another process. might find curl command line tool send HTTP requests app, can just use another R process. example. simply print incoming request object screen now. real debugging session probably want place browser() command . Now start app port 3000: Connect app another R curl process: main R session print incoming request: Press CTRL+C ESC interrupt app main session.","code":"app <- webfakes::new_app() app$get(\"/debug\", function(req, res) { print(req) res$send(\"Got your back\") }) app$listen(port = 3000) #> Running webfakes web app on port 3000 curl -v http://127.0.0.1:3000/debug #> * Trying 127.0.0.1... #> * TCP_NODELAY set #> * Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0) #> > GET /debug HTTP/1.1 #> > Host: 127.0.0.1:3000 #> > User-Agent: curl/7.54.0 #> > Accept: */* #> > #> < HTTP/1.1 200 OK #> < Content-Type: text/plain #> < Content-Length: 13 #> < #> * Connection #0 to host 127.0.0.1 left intact #> Got your back #> #> method: #> get #> url: #> http://127.0.0.1:3000/debug #> client: #> 127.0.0.1 #> query: #> headers: #> Host: 127.0.0.1:3000 #> User-Agent: curl/7.54.0 #> Accept: */* #> fields and methods: #> app # the webfakes_app the request belongs to #> headers # HTTP request headers #> hostname # server hostname, the Host header #> method # HTTP method of request (lowercase) #> path # server path #> protocol # http or https #> query_string # raw query string without '?' #> query # named list of query parameters #> remote_addr # IP address of the client #> url # full URL of the request #> get_header(field) # get a request header #> # see ?webfakes_request for details"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-test-https-requests-","dir":"Reference","previous_headings":"","what":"How can I test HTTPS requests?","title":"How to use webfakes in your tests β€” how-to","text":"Serving HTTPS localhost 127.0.0.1 instead HTTP easy, need Set port HTTPS port adding \"s\" suffix port number. Use \"0s\" OS assigned free port: default webfakes uses server key + certificate file certificate includes localhost, 127.0.0.1 localhost.localdomain. need another domain IP address, need create certificate. generate.sh file directory helps . Specify certificate bundle HTTP client using. default server key use ca.crt file webfakes package: See examples HTTP clients . using curl package, use ca_info option curl::new_handle() curl::handle_setopt(): httr package, use httr::config(cainfo = ...): httr2 package: utils::download.file point CURL_CA_BUNDLE environment variable ca.crt file. forget undo , HTTP request done.","code":"new_app_process(app, port = \"0s\") system.file(\"cert/localhost/server.pem\", package = \"webfakes\") system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") curl::curl_fetch_memory( http$url(\"/path/to/endpoint\"), handle = curl::new_handle(cainfo = cainfo) ) httr::GET( http$url(\"/headers\", https = TRUE), httr::config(cainfo = cainfo) ) httr2::request(\"https://example.com\") |> httr2::req_options(cainfo = cainfo) |> httr2::req_perform() Sys.setenv( CURL_CA_BUNDLE = system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") ) download.file(http$url(\"/path/to/endpoint\"), res <- tempfile())"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"special-considerations-for-tests-on-windows","dir":"Reference","previous_headings":"","what":"Special considerations for tests on Windows","title":"How to use webfakes in your tests β€” how-to","text":"Unfortunately things simple Windows, HTTP clients. far can tell, easily possible make HTTP clients accept new self-signed certificate. possible libcurl, though, need set CURL_SSL_BACKEND=openssl environment variable. (libcurl must built openssl support course.) need set env var loading libcurl, best set starting R. One way tests run tests subprocess, callr package. Look test-https.R file webfakes complete, current example. tests use helper function, defined helper.R: Example test case: seems like good idea skip_on_cran() HTTPS tests, least Windows, setup yet tested enough consider robust. webfakes uses Mbed TLS serving HTTPS.","code":"callr_curl <- function(url, options = list()) { callr::r( function(url, options) { h <- curl::new_handle() curl::handle_setopt(h, .list = options) curl::curl_fetch_memory(url, handle = h) }, list(url = url, options = options), env = c( callr::rcmd_safe_env(), CURL_SSL_BACKEND = \"openssl\", CURL_CA_BUNDLE = if (\"cainfo\" %in% names(options)) options$cainfo ) ) } # ... cainfo <- system.file(\"cert/localhost/ca.crt\", package = \"webfakes\") resp <- if (.Platform$OS.type == \"windows\") { callr_curl(http$url(\"/hello\"), list(cainfo = cainfo)) } else { curl::curl_fetch_memory( http$url(\"/hello\"), handle = curl::new_handle(cainfo = cainfo) ) } # ..."},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-can-i-run-a-server-on-multiple-ports-","dir":"Reference","previous_headings":"","what":"How can I run a server on multiple ports?","title":"How to use webfakes in your tests β€” how-to","text":"can specify multiple port numbers, vector. webfakes listen ports. can also mix HTTP HTTP ports. redirect HTTP port HTTPS port, append \"r\" suffix HTTP port number. port redirected next HTTPS port. E.g. redirect HTTP port 3000 HTTPS port 3001. redirect OS assigned HTTP port OS assigned HTTPS port, use zeros port numbers: can use http$get_ports() query port numbers. can also use get HTTPS URL instead default one (one first port).","code":"new_app_process(app, port = c(\"3000r\", \"3001s\")) http <- new_app_process(app, port = c(\"0r\", \"0s\")) http$url(..., https = TRUE)"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"can-i-test-asynchronous-or-parallel-http-requests-","dir":"Reference","previous_headings":"","what":"Can I test asynchronous or parallel HTTP requests?","title":"How to use webfakes in your tests β€” how-to","text":"R single threaded webfakes app runs R interpreter, process multiple requests time. web server runs separate thread, can also process request separate thread, time one request can use R interpreter. important, sometimes test requests may take longer process. example /delay/:secs end point httpbin_app() wait specified number seconds responding, simulate slow web server. wait implemented via standard Sys.sleep() R function, requests can processed sleep . avoid , webfakes can put waiting request hold, return R interpreter, respond incoming requests. Indeed, /delay/ end point implemented using feature. However, request thread web server still busy hold, take advantage , need allow multiple threads. num_threads argument $listen() method webfakes_app lets specify number request threads web server use. Similarly, num_threads argument local_app_process() lets modify number threads. testing asynchronous parallel code, might invoke multiple, possibly delayed requests, best increase number threads. code calls API request concurrently, three times. request takes 1 second answer, web server three threads, together still take 1 second. (fail webfakes appears process requests sequentially, see issue #108 possible workarounds.)","code":"web <- webfakes::local_app_process( webfakes::httpbin_app(), opts = webfakes::server_opts(num_threads = 6, enable_keep_alive = TRUE) ) testthat::test_that(\"parallel requests\", { url <- web$url(\"/delay/0.5\") p <- curl::new_pool() handles <- replicate(3, curl::new_handle(url = url)) resps <- list() for (handle in handles) { curl::multi_add( handle, done = function(x) resps <<- c(resps, list(x)), fail = stop, pool = p ) } st <- system.time(curl::multi_run(timeout = 3, pool = p)) testthat::expect_true(st[[\"elapsed\"]] < 1.5) }) #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-to-make-sure-that-my-code-works-with-the-real-api-","dir":"Reference","previous_headings":"","what":"How to make sure that my code works with the real API?","title":"How to use webfakes in your tests β€” how-to","text":"Indeed, use webfakes test cases, never touch real web server. might suspect, ideal, especially control server. web service might change API, test cases fail warn . One practical solution write (least ) flexible tests, can run local fake webserver, real one, quick switch change behavior. found environment variables work great . E.g. FAKE_HTTP_TESTS environment variable set, tests run real web server, otherwise use fake one. Another solution, works best HTTP requests downstream package code, introduce one environment variable API need connect . might set real API servers, fake ones. tests can use kinds servers, can set continuous integration (CI) framework, run tests agains real server (say) day. special CI run makes sure code works well real API. can run tests, locally CI, fake local web server. See question webfakes helps setting environment variables point local server.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/how-to.html","id":"how-do-i-simulate-a-slow-internet-connection-","dir":"Reference","previous_headings":"","what":"How do I simulate a slow internet connection?","title":"How to use webfakes in your tests β€” how-to","text":"need use throttle server option start web app. means can run app different connection speed. goes: throttle gives number bytes per second, downloading 200 random bytes fake app take 2 seconds.","code":"library(webfakes) slow <- new_app_process( httpbin_app(), opts = server_opts(throttle = 100) ) resp <- curl::curl_fetch_memory(slow$url(\"/bytes/200\")) resp$times #> redirect namelookup connect pretransfer starttransfer #> 0.000000 0.000087 0.000266 0.000284 0.004878 #> total #> 2.013756"},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":null,"dir":"Reference","previous_headings":"","what":"Format a time stamp for HTTP β€” http_time_stamp","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"Format time stamp HTTP","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"","code":"http_time_stamp(t = Sys.time())"},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"t Date-time value format, defaults current date time. must POSIXct object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/http_time_stamp.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Format a time stamp for HTTP β€” http_time_stamp","text":"Character vector, formatted date-time.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Generic web app for testing HTTP clients β€” httpbin_app","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"web app similar https://httpbin.org. See specific docs. can also see docs locally, starting app:","code":"httpbin <- new_app_process(httpbin_app()) browseURL(httpbin$url())"},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"","code":"httpbin_app(log = interactive())"},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"log Whether log requests standard output.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"webfakes_app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/httpbin_app.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Generic web app for testing HTTP clients β€” httpbin_app","text":"","code":"app <- httpbin_app() proc <- new_app_process(app) url <- proc$url(\"/get\") resp <- curl::curl_fetch_memory(url) curl::parse_headers_list(resp$headers) #> $connection #> [1] \"close\" #> #> $date #> [1] \"Tue, 14 Jan 2025 09:13:25 GMT\" #> #> $`content-type` #> [1] \"application/json\" #> #> $`content-length` #> [1] \"313\" #> #> $etag #> [1] \"\\\"793689be\\\"\" #> cat(rawToChar(resp$content)) #> { #> \"args\": {}, #> \"headers\": { #> \"Host\": \"127.0.0.1:32961\", #> \"User-Agent\": \"R/4.4.2 R (4.4.2 x86_64-pc-linux-gnu x86_64 linux-gnu) on GitHub Actions\", #> \"Accept\": \"*/*\", #> \"Accept-Encoding\": \"deflate, gzip, br, zstd\" #> }, #> \"origin\": \"127.0.0.1\", #> \"path\": \"/get\", #> \"url\": \"http://127.0.0.1:32961/get\" #> } proc$stop()"},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":null,"dir":"Reference","previous_headings":"","what":"Happy HTTP testing with webfakes β€” introduction","title":"Happy HTTP testing with webfakes β€” introduction","text":"Happy HTTP testing webfakes","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"what-is-webfakes-","dir":"Reference","previous_headings":"","what":"What is webfakes?","title":"Happy HTTP testing with webfakes β€” introduction","text":"Webfakes R package can spin web servers machine facilitate testing R code. R code needs HTTP connection trivial test: Connectivity problems might prevent tests accessing web server. web server might need authentication, easy convey login information test suite secure way. web server might rate limits, .e, limits number queries per hour day, causing spurious test failures. might want test non-normal conditions, e.g. low bandwidth, client rate limited. conditions normally happen web server hard trigger. webfakes can easily start custom web app, running local machine. Webfakes need network connection. Webfakes need authentication. Well, unless want . Webfakes rate limits. Webfakes can simulate low bandwidth, broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"webfakes-vs-mocking","dir":"Reference","previous_headings":"","what":"Webfakes vs mocking","title":"Happy HTTP testing with webfakes β€” introduction","text":"Mocking general technique mimic behavior function object needed test case. case HTTP requests, typically means request response recorded tests run first time, saved disk. Subsequent test runs intercept HTTP requests, match recorded requests replay corresponding recorded response. See example vcr httptest R packages. advantages using webfakes server, mocking: Simpler infrastructure. separate recording replaying phases, recorded files. request matching. can use web client want. E.g. curl base R's HTTP functions explicitly support mocking currently. need worry sensitive information recorded requests responses. Often easier use testing non-normal conditions, e.g. errors hard trigger, low bandwidth, rate limits. Works stream data HTTP connection, instead reading whole response . can reuse app multiple tests, multiple packages. Easier use tests require multiple rounds requests. Comes built-https://httpbin.org compatible app, chances , even need write testing app, just start writing tests right away. Better test writing experience. subjective, mileage may vary.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"webfakes-vs-the-real-api","dir":"Reference","previous_headings":"","what":"Webfakes vs the real API","title":"Happy HTTP testing with webfakes β€” introduction","text":"network needed. skip_if_offline(). Much faster. rate limits. can simulate one want . can write custom app. Simulate low bandwidth broken connection.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"webfakes-vs-httpbin-org","dir":"Reference","previous_headings":"","what":"Webfakes vs httpbin.org","title":"Happy HTTP testing with webfakes β€” introduction","text":"network needed. skip_if_offline(). Much faster. can use built-webfakes::httpbin_app() app, easy switch httpbin.org. can write custom app, httpbin.org might need.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"using-webfakes-httpbin-app-with-testthat","dir":"Reference","previous_headings":"","what":"Using webfakes::httpbin_app() with testthat","title":"Happy HTTP testing with webfakes β€” introduction","text":"can use testthat's setup files. start app setup file also register teardown expression . local_app_process() can one go. tests/testthat/setup-http.R may look like : (testthat 3.0.0, write teardown expression tests/testthat/teardown-http.R file. still works, single setup file considered better practice, see testthat vignette.) test cases can query http app process get URLs need connect : writing tests interactively, may create http app process global environment, convenience. can source() setup-http.R file . Alternatively, can start app process helper file. See \"start app writing tests?\" just . can also create web server test file, even single test case. See vignette(\"-\") details .","code":"http <- webfakes::local_app_process( webfakes::httpbin_app(), .local_envir = testthat::teardown_env() ) test_that(\"fails on 404\", { url <- http$url(\"/status/404\") response <- httr::GET(url) expect_error( httr::stop_for_status(response), class = \"http_404\" ) }) #> Test passed"},{"path":"https://webfakes.r-lib.org/dev/reference/introduction.html","id":"writing-apps","dir":"Reference","previous_headings":"","what":"Writing apps","title":"Happy HTTP testing with webfakes β€” introduction","text":"builtin httpbin_app() appropriate tests, can write app. can also extend httpbin_app() app, want start scratch. create new app new_app(). returns object methods add middleware API endpoints . example, simple app returns current time JSON look like : Now can start app random port using web$listen(). Alternatively, can start subprocess new_app_process(). Use web$url() query URL app. example: web$stop() stops app subprocess well: local_app_process() similar new_app_process(), stops server process end calling block. means process automatically cleaned end test_that() block end test file. can create app beginning test file. , want use app multiple test files, use testthat helper file. Sometimes useful users can create use test app, example create reproducible examples. can include (possibly internal) function package, creates app. See ?new_app(), ?new_app_process() ?local_app_process details.","code":"time <- webfakes::new_app() time$get(\"/time\", function(req, res) { res$send_json(list(time = format(Sys.time())), auto_unbox = TRUE) }) web <- webfakes::new_app_process(time) web$url() #> [1] \"http://127.0.0.1:64358/\" url <- web$url(\"/time\") httr::content(httr::GET(url)) #> $time #> [1] \"2025-01-14 10:07:33\" web$stop() web$get_state() #> [1] \"not running\""},{"path":"https://webfakes.r-lib.org/dev/reference/local_app_process.html","id":null,"dir":"Reference","previous_headings":"","what":"App process that is cleaned up automatically β€” local_app_process","title":"App process that is cleaned up automatically β€” local_app_process","text":"can start process explicit $start() call. Alternatively starts first $url() $get_port() call.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/local_app_process.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"App process that is cleaned up automatically β€” local_app_process","text":"","code":"local_app_process(app, ..., .local_envir = parent.frame())"},{"path":"https://webfakes.r-lib.org/dev/reference/local_app_process.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"App process that is cleaned up automatically β€” local_app_process","text":"app webfakes_app object, web app run. ... Passed new_app_process(). .local_envir environment attach process cleanup . Typically frame. frame finishes, process stopped.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware that calls a CGI script β€” mw_cgi","title":"Middleware that calls a CGI script β€” mw_cgi","text":"can use unconditional middleware app$use(), handler app$get(), app$post(), etc., can call handler. See examples .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware that calls a CGI script β€” mw_cgi","text":"","code":"mw_cgi(command, args = character(), timeout = as.difftime(Inf, units = \"secs\"))"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware that calls a CGI script β€” mw_cgi","text":"command External command run. args Arguments pass external command. timeout Timeout external command. command terminate time, web server kills returns 500 response.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware that calls a CGI script β€” mw_cgi","text":"function signature See RFC 3875 details CGI protocol. request body () passed external command standard intput. mw_cgi() sets CONTENT_LENGTH, CONTENT_TYPE, GATEWAY_INTERFACE, PATH_INFO, QUERY_STRING, REMOTE_ADDR, REMOTE_HOST, REMOTE_USER, REQUEST_METHOD, SERVER_NAME, SERVER_PORT, SERVER_PROTOCOL, SERVER_SOFTEWARE. currently set AUTH_TYPE, PATH_TRANSLATED, REMOTE_IDENT, SCRIPT_NAME environment variables. standard output external command used set response status code, response headers response body. Example output git's CGI:","code":"function(req, res, env = character()) Status: 200 OK Expires: Fri, 01 Jan 1980 00:00:00 GMT Pragma: no-cache Cache-Control: no-cache, max-age=0, must-revalidate Content-Type: application/x-git-upload-pack-advertisement 000eversion 2 0015agent=git/2.42.0 0013ls-refs=unborn 0020fetch=shallow wait-for-done 0012server-option 0017object-format=sha1 0010object-info 0000"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cgi.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware that calls a CGI script β€” mw_cgi","text":"","code":"app <- new_app() app$use(mw_cgi(\"echo\", \"Status: 200\\n\\nHello\")) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods app2 <- new_app() app2$get(\"/greet\", mw_cgi(\"echo\", \"Status: 200\\n\\nHello\")) app2 #> #> routes: #> get /greet #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Using `mw_cgi()` in a handler, you can pass extra environment variables app3 <- new_app() cgi <- mw_cgi(\"echo\", \"Status: 200\\n\\nHello\") app2$get(\"/greet\", function(req, res) { cgi(req, res, env = c(\"EXTRA_VAR\" = \"EXTRA_VALUE\")) }) app3 #> #> routes: #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse Cookies β€” mw_cookie_parser","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"Adds cookies cookies element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"","code":"mw_cookie_parser()"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"Handler function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_cookie_parser.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Middleware to parse Cookies β€” mw_cookie_parser","text":"ignores cookies invalid format. ignores duplicate cookies: two cookies name, first one included.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware that add an ETag header to the response β€” mw_etag","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"response already ETag header, kept.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"","code":"mw_etag(algorithm = \"crc32\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"algorithm Checksum algorithm use. \"crc32\" implemented currently.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"Handler function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"middleware handles -None-Match headers, sets status code response 304 -None-Match matches ETag. also removes response body case.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_etag.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware that add an ETag header to the response β€” mw_etag","text":"","code":"app <- new_app() app$use(mw_etag()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse a JSON body β€” mw_json","title":"Middleware to parse a JSON body β€” mw_json","text":"Adds parsed object json element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse a JSON body β€” mw_json","text":"","code":"mw_json(type = \"application/json\", simplifyVector = FALSE, ...)"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to parse a JSON body β€” mw_json","text":"type Content type match parsing. match, request object modified. simplifyVector Whether simplify lists vectors, passed jsonlite::fromJSON(). ... Arguments pass jsonlite::fromJSON(), performs JSON parsing.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse a JSON body β€” mw_json","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_json.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to parse a JSON body β€” mw_json","text":"","code":"app <- new_app() app$use(mw_json()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":null,"dir":"Reference","previous_headings":"","what":"Log requests to the standard output or other connection β€” mw_log","title":"Log requests to the standard output or other connection β€” mw_log","text":"one line log entry every request. output looks like : contains HTTP method, full request URL, HTTP status code response, long took process response, ms, size response body, bytes.","code":"GET http://127.0.0.1:3000/image 200 3 ms - 4742"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Log requests to the standard output or other connection β€” mw_log","text":"","code":"mw_log(format = \"dev\", stream = \"stdout\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Log requests to the standard output or other connection β€” mw_log","text":"format Log format. implemented currently. stream R connection log . \"stdout\" means standard output, \"stderr\" standard error. can also supply connection object, need sure valid app actually running.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Log requests to the standard output or other connection β€” mw_log","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_log.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Log requests to the standard output or other connection β€” mw_log","text":"","code":"app <- new_app() app$use(mw_log()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":null,"dir":"Reference","previous_headings":"","what":"Parse a multipart HTTP request body β€” mw_multipart","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"Adds parsed form fields form element request parsed files files element.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"","code":"mw_multipart(type = \"multipart/form-data\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"type Content type match parsing. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_multipart.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Parse a multipart HTTP request body β€” mw_multipart","text":"","code":"app <- new_app() app$use(mw_multipart()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse a Range header β€” mw_range_parser","title":"Middleware to parse a Range header β€” mw_range_parser","text":"Adds requested ranges ranges element request object. request$ranges data frame two columns, . row corresponds one requested interval.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse a Range header β€” mw_range_parser","text":"","code":"mw_range_parser()"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse a Range header β€” mw_range_parser","text":"Handler function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_range_parser.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Middleware to parse a Range header β€” mw_range_parser","text":"last n bytes file requested, matrix row set c(0, -n). bytes p position requested, matrix row set c(p, Inf). intervals overlap, ranges set, .e. Range header ignored. syntax invalid unit bytes, Range header ignored.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to read the raw body of a request β€” mw_raw","title":"Middleware to read the raw body of a request β€” mw_raw","text":"Adds raw body, raw object raw field request.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to read the raw body of a request β€” mw_raw","text":"","code":"mw_raw(type = \"application/octet-stream\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to read the raw body of a request β€” mw_raw","text":"type Content type match. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to read the raw body of a request β€” mw_raw","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_raw.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to read the raw body of a request β€” mw_raw","text":"","code":"app <- new_app() app$use(mw_raw()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware function to serve static files β€” mw_static","title":"Middleware function to serve static files β€” mw_static","text":"content type response set automatically extension file. Note terminal middleware handler function. file served, rest handler functions called. file found, however, rest handlers still called.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware function to serve static files β€” mw_static","text":"","code":"mw_static(root, set_headers = NULL)"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware function to serve static files β€” mw_static","text":"root Root path served files. Everything directory served automatically. Directory lists currently supports. set_headers Callback function call file served.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware function to serve static files β€” mw_static","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_static.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware function to serve static files β€” mw_static","text":"","code":"root <- system.file(package = \"webfakes\", \"examples\", \"static\", \"public\") app <- new_app() app$use(mw_static(root = root)) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse a plain text body β€” mw_text","title":"Middleware to parse a plain text body β€” mw_text","text":"Adds parsed object text element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse a plain text body β€” mw_text","text":"","code":"mw_text(default_charset = \"utf-8\", type = \"text/plain\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to parse a plain text body β€” mw_text","text":"default_charset Encoding set text. type Content type match parsing. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse a plain text body β€” mw_text","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_text.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to parse a plain text body β€” mw_text","text":"","code":"app <- new_app() app$use(mw_text()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":null,"dir":"Reference","previous_headings":"","what":"Middleware to parse an url-encoded request body β€” mw_urlencoded","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"typically data form. parsed data added form element request object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"","code":"mw_urlencoded(type = \"application/x-www-form-urlencoded\")"},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"type Content type match parsing. match, request object modified.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"Handler function.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/mw_urlencoded.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Middleware to parse an url-encoded request body β€” mw_urlencoded","text":"","code":"app <- new_app() app$use(mw_urlencoded()) app #> #> routes: #> use * #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Create a new web application β€” new_app","title":"Create a new web application β€” new_app","text":"Create new web application","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Create a new web application β€” new_app","text":"","code":"new_app()"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Create a new web application β€” new_app","text":"new webfakes_app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Create a new web application β€” new_app","text":"typical workflow creating web application : Create webfakes_app object new_app(). Add middleware /routes . Start webfakes_app$listen() method, start another process new_app_process(). Make queries web app. Stop via CTRL+C / ESC, , running another process, $stop() method new_app_process(). web application can restarted, saved disk, copied another process using callr package, similar way, embedded package, extended simply adding new routes /middleware. webfakes API much influenced express.js project.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"create-web-app-objects","dir":"Reference","previous_headings":"","what":"Create web app objects","title":"Create a new web application β€” new_app","text":"new_app() returns webfakes_app object methods listed page. app environment S3 class webfakes_app.","code":"new_app()"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"the-handler-stack","dir":"Reference","previous_headings":"","what":"The handler stack","title":"Create a new web application β€” new_app","text":"app stack handlers. handler can route middleware. differences two : route bound one paths web server. Middleware (currently) bound paths, run paths. route usually (always) end handler stack request. .e. route takes care sending response request. Middleware typically performs action request response, next handler stack invoked.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"routes","dir":"Reference","previous_headings":"","what":"Routes","title":"Create a new web application β€” new_app","text":"following methods define routes. method corresponds HTTP verb name, except app$(), creates route HTTP methods. path path specification, see 'Path specification' . ... one handler functions. placed handler stack, called match incoming HTTP request. See 'Handler functions' . webfakes also methods less frequently used HTTP verbs: CONNECT, MKCOL, OPTIONS, PROPFIND, REPORT. (method names always lowercase.) request handled routes (handler functions general), webfakes send simple HTTP 404 response.","code":"app$all(path, ...) app$delete(path, ...) app$get(path, ...) app$head(path, ...) app$patch(path, ...) app$post(path, ...) app$put(path, ...) ... (see list below)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"middleware","dir":"Reference","previous_headings":"","what":"Middleware","title":"Create a new web application β€” new_app","text":"app$use() adds middleware handler stack. middleware handler function, see 'Handler functions' . webfakes comes middleware perform common tasks: mw_cookie_parser() parses Cookie headers. mw_etag() adds ETag header response. mw_json() parses JSON request bodies. mw_log() logs requests standard output, another connection. mw_multipart() parses multipart request bodies. mw_range_parser() parses Range headers. mw_raw() parses raw request bodies. mw_static() serves static files directory. mw_text() parses plain text request bodies. mw_urlencoded() parses URL encoded request bodies. ... set (middleware) handler functions. added handler stack, called every HTTP request. (Unless HTTP response created reaching point handler stack.) .first set TRUE want add handler function bottom stack.","code":"app$use(..., .first = FALSE)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"handler-functions","dir":"Reference","previous_headings":"","what":"Handler functions","title":"Create a new web application β€” new_app","text":"handler function route middleware. handler function called webfakes incoming HTTP request outgoing HTTP response objects (built) arguments. handler function may query modify members request /response object. returns string \"next\", terminal handler, returns, webfakes move call next handler stack. typical route: handler belongs API path, wildcard path case. matches /user/alice, /user/bob, etc. handler called GET methods matching API paths. handler receives request (req) response (res). sets HTTP status, additional headers, sends data. (case webfakes_response$send_json() method automatically converts response JSON sets Content-Type Content-Length headers. terminal handler, return \"next\". handler function returns, webfakes send HTTP response. typical middleware: HTTP method API path , webfakes call handler HTTP request. terminal handler, return \"next\", returns webfakes look next handler stack.","code":"app$get(\"/user/:id\", function(req, res) { id <- req$params$id ... res$ set_status(200L)$ set_header(\"X-Custom-Header\", \"foobar\")$ send_json(response, auto_unbox = TRUE) }) app$use(function(req, res) { ... \"next\" })"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"errors","dir":"Reference","previous_headings":"","what":"Errors","title":"Create a new web application β€” new_app","text":"handler function throws error, web server return HTTP 500 text/plain response, error message response body.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"request-and-response-objects","dir":"Reference","previous_headings":"","what":"Request and response objects","title":"Create a new web application β€” new_app","text":"See webfakes_request webfakes_response methods request response objects.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"path-specification","dir":"Reference","previous_headings":"","what":"Path specification","title":"Create a new web application β€” new_app","text":"Routes associated one API paths. path specification can \"plain\" (.e. without parameters) string. (E.g. \"/list\".) parameterized string. (E.g. \"/user/:id\".) regular expression created via new_regexp() function. list character vector previous ones. (Regular expressions must list.)","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"path-parameters","dir":"Reference","previous_headings":"","what":"Path parameters","title":"Create a new web application β€” new_app","text":"Paths specified parameterized strings regular expressions can parameters. parameterized strings keys may contain letters, numbers underscores. webfakes matches API path handler parameterized string path, parameters added request, params. .e. handler function (subsequent handler functions, current one terminal), available req$params list. regular expressions, capture groups also added parameters. best use named capture groups, parameters named list. path handler list parameterized strings regular expressions, parameters set according first matching one.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"templates","dir":"Reference","previous_headings":"","what":"Templates","title":"Create a new web application β€” new_app","text":"webfakes supports templates, using template engine. comes template engine uses glue package, see tmpl_glue(). app$engine() registers template engine, certain file extension. $render() method webfakes_response can called handler function evaluate template file. ext: file extension template engine added. contain dot. E.g. \"html\"', \"brew\"`. engine: template engine, function takes file path (path) template, list local variables (locals) can used template. return result. example template engine uses glue might look like : (built-tmpl_glue() engine features.) template engine can used handler: location templates can set using views configuration parameter, see $set_config() method . template, variables passed locals, also response local variables (see locals webfakes_response), available.","code":"app$engine(ext, engine) app$engine(\"txt\", function(path, locals) { txt <- readChar(path, nchars = file.size(path)) glue::glue_data(locals, txt) }) app$get(\"/view\", function(req, res) { txt <- res$render(\"test\") res$ set_type(\"text/plain\")$ send(txt) })"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"starting-and-stopping","dir":"Reference","previous_headings":"","what":"Starting and stopping","title":"Create a new web application β€” new_app","text":"port: port listen . NULL, operating system automatically select free port. Add \"s\" suffix port use HTTPS. Use \"0s\" use OS assigned port HTTPS. See -manual page want start web server one ports. opts: options web server. See server_opts() list options default values. cleanup: stop server (error) standard input process closed. handy app runs callr::r_session subprocess, stops app (subprocess) main process terminated. method return, can interrupted CTRL+C / ESC SIGINT signal. See new_app_process() interrupting app running another process. port NULL, operating system chooses port app listen. able get port number programmatically, listen method blocks, advertises selected port webfakes_port condition, one can catch : webfakes default binds loopback interface 127.0.0.1, webfakes web app never reachable network.","code":"app$listen(port = NULL, opts = server_opts(), cleanup = TRUE) withCallingHandlers( app$listen(), \"webfakes_port\" = function(msg) print(msg$port) )"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"logging","dir":"Reference","previous_headings":"","what":"Logging","title":"Create a new web application β€” new_app","text":"webfakes can write access log contains entry incoming requests, also error log errors happen server running. default behavior local app (ones started app$listen() remote apps (ones started via new_app_process(): Local apps write access log default. Remote apps write access log /webfakes//access.log file, session temporary directory main process, process id subprocess. Local apps write error log /webfakes/error.log, session temporary directory current process. Remote app write error log /webfakes//error.log, session temporary directory main process process id subprocess`. See server_opts() changing default logging behavior.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"shared-app-data","dir":"Reference","previous_headings":"","what":"Shared app data","title":"Create a new web application β€” new_app","text":"often useful share data handlers requests app. app$locals environment supports . E.g. middleware counts number requests can implemented : webfakes_response objects also locals environment, initially populated copy app$locals.","code":"app$locals app$use(function(req, res) { locals <- req$app$locals if (is.null(locals$num)) locals$num <- 0L locals$num <- locals$num + 1L \"next\" })"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"configuration","dir":"Reference","previous_headings":"","what":"Configuration","title":"Create a new web application β€” new_app","text":"key: configuration key. value: configuration value. Currently used configuration values: views: path webfakes searches templates.","code":"app$get_config(key) app$set_config(key, value)"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/new_app.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Create a new web application β€” new_app","text":"","code":"# see example web apps in the `/examples` directory in system.file(package = \"webfakes\", \"examples\") #> [1] \"/home/runner/work/_temp/Library/webfakes/examples\" app <- new_app() app$use(mw_log()) app$get(\"/hello\", function(req, res) { res$send(\"Hello there!\") }) app$get(new_regexp(\"^/hi(/.*)?$\"), function(req, res) { res$send(\"Hi indeed!\") }) app$post(\"/hello\", function(req, res) { res$send(\"Got it, thanks!\") }) app #> #> routes: #> use * #> get /hello #> get \"^/hi(/.*)?$\" #> post /hello #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Start the app with: app$listen() # Or start it in another R session: new_app_process(app)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":null,"dir":"Reference","previous_headings":"","what":"Run a webfakes app in another process β€” new_app_process","title":"Run a webfakes app in another process β€” new_app_process","text":"Runs app subprocess, using callr::r_session.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Run a webfakes app in another process β€” new_app_process","text":"","code":"new_app_process( app, port = NULL, opts = server_opts(remote = TRUE), start = FALSE, auto_start = TRUE, process_timeout = NULL, callr_opts = NULL )"},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Run a webfakes app in another process β€” new_app_process","text":"app webfakes_app object, web app run. port Port(s) use. default OS assigns port. Add \"s\" suffix port use HTTPS. Use \"0s\" use OS assigned port HTTPS. See -run web server multiple ports. opts Server options. See server_opts() defaults. start Whether start web server immediately. FALSE, auto_start TRUE, started neeed. auto_start Whether start web server process automatically. TRUE process running, $start(), $get_port(), $get_ports() $url() start process. process_timeout long wait subprocess start, milliseconds. callr_opts Options pass callr::r_session_options() setting subprocess.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Run a webfakes app in another process β€” new_app_process","text":"webfakes_app_process object.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"methods","dir":"Reference","previous_headings":"","what":"Methods","title":"Run a webfakes app in another process β€” new_app_process","text":"webfakes_app_process class following methods: envvars: Named list environment variables. {url} substring replaced URL app. path: Path return URL . query: Additional query parameters, named list, add URL. get_app() returns app object. get_port() returns (first) port web server running . get_ports() returns ports web server running , whether uses SSL ports, data frame columns ipv4, ipv6, port ssl. stop() stops web server, also subprocess. error log file empty, dumps contents screen. get_state() returns string, state web server: \"running\" server running (stopped already). \"live\" means server running. \"dead\" means subprocess quit crashed. local_env() sets given environment variables duration app process. resets $stop(). Webfakes replaces {url} value environment variables app URL, can set environment variables point app. url() returns URL web app. can use path parameter return specific path.","code":"get_app() get_port() get_ports() stop() get_state() local_env(envvars) url(path = \"/\", query = NULL)"},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/new_app_process.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Run a webfakes app in another process β€” new_app_process","text":"","code":"app <- new_app() app$get(\"/foo\", function(req, res) { res$send(\"Hello world!\") }) proc <- new_app_process(app) url <- proc$url(\"/foo\") resp <- curl::curl_fetch_memory(url) cat(rawToChar(resp$content)) #> Hello world! proc$stop()"},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":null,"dir":"Reference","previous_headings":"","what":"Create a new regular expression to use in webfakes routes β€” new_regexp","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"Note webfakes uses PERL regular expressions.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"","code":"new_regexp(x)"},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"x String scalar containing regular expression.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"String class webfakes_regexp.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"R data type class regular expressions, can use new_regexp() mark string regular expression, adding routes.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/new_regexp.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Create a new regular expression to use in webfakes routes β€” new_regexp","text":"","code":"new_regexp(\"^/api/match/(?.*)$\") #> \"^/api/match/(?.*)$\""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":null,"dir":"Reference","previous_headings":"","what":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"perform automatic acknowledgement log local OAuth2.0 app, run httr, wrap expression obtains OAuth2.0 token oauth2_httr_login().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"","code":"oauth2_httr_login(expr)"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"expr Expression calls httr::oauth2.0_token(), either directly, indirectly.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"return value expr.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_httr_login.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Helper function to use httr's OAuth2.0 functions non-interactively, e.g. in test cases β€” oauth2_httr_login","text":"interactive sessions, oauth2_httr_login() overrides browser option, httr opens browser page, calls oauth2_login() subprocess. non-interactive sessions, httr open browser page, messages user manually. oauth2_httr_login() listens messages, calls oauth2_login() subprocess.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":null,"dir":"Reference","previous_headings":"","what":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"works oauth2_resource_app(), third party app, including fake oauth2_third_party_app().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"","code":"oauth2_login(login_url)"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"login_url login URL third party app.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"named list login_response curl HTTP response object login page. token_response curl HTTP response object submitting login page.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_login.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Helper function to log in to a third party OAuth2.0 app without a browser β€” oauth2_login","text":"See test-oauth.R webfakes example.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":null,"dir":"Reference","previous_headings":"","what":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"webfakes package comes two fake apps allow imitate OAuth2.0 flow test cases. (See Aaron Parecki’s tutorial good introduction OAuth2.0.) One app (oauth2_resource_app()) API server serves resource provides authorization. oauth2_third_party_app() plays role third-party app. useful testing demonstrating code handling OAuth2.0 authorization, token caching, etc. package. apps can used tests directly, adapt one better mimic particular OAuth2.0 flow.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"","code":"oauth2_resource_app( access_duration = 3600L, refresh_duration = 7200L, refresh = TRUE, seed = NULL, authorize_endpoint = \"/authorize\", token_endpoint = \"/token\" )"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"access_duration many seconds access tokens expire. refresh_duration many seconds refresh tokens expire (ignored refresh FALSE). refresh refresh token returned (logical). seed Random seed used creating tokens. NULL, rely R provide seed. app uses RNG stream, affect reproducibility tests. authorize_endpoint authorization endpoint resource server. Change default real app faking use /authorize. token_endpoint endpoint request tokens. Change real app faking use /token.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"webfakes app webfakes app","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"app following endpoints: GET /register endpoint can use register third party app. needs receive name third party app, redirect_uri query parameters, otherwise returns HTTP 400 error. success returns JSON dictionary entries name (name third party app), client_id, client_secret redirect_uri. GET /authorize endpoint user third party app sent. can change URL endpoint authorize_endpoint argument. needs receive client_id third party app, correct redirect_uri query parameters. may receive state string well, can used client identify request. Otherwise generates random state string. error fails HTTP 400 error. success returns simple HTML login page. POST /authorize/decision endpoint HTML login page generated /authorize connects back , either positive negative result. form login page send state string user's choice action variable. user authorized third party app, redirected redirect_uri app, temporary code state string supplied query parameters. Otherwise simple HTML page returned. POST /token endpoint third party app requests temporary access token. also uses refreshing access token refresh token. can change URL endpoint token_endpoint argument. request new token refresh existing one, following data must included either JSON URL encoded request body: grant_type, must authorization_code new tokens, refresh_token refreshing. code, must temporary code obtained /authorize/decision redirection, new tokens. needed refreshing. client_id must client id third party app. client_secret must client secret third party app. redirect_uri must correct redirection URI third party app. needed refreshing tokens. refresh_token must refresh token obtained previously, refreshing token. needed new tokens. success JSON dictionary returned entries: access_token, expiry refresh_token. (latter omitted refresh argument FALSE). GET /locals returns list current apps, access tokens refresh tokens. GET /data endpoint returns simple JSON response, needs authorization.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"notes","dir":"Reference","previous_headings":"","what":"Notes","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"Using app tests requires glue package, need put Suggests. can add custom endpoints app, needed. need authorization custom endpoint, call app$is_authorized() handler: app$is_authorized() returns HTTP 401 response client authorized, can simply return handler. details see vignette(\"oauth\", package = \"webfakes\").","code":"if (!app$is_authorized(req, res)) return()"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_resource_app.html","id":"oauth-resource-app-","dir":"Reference","previous_headings":"","what":"oauth2_resource_app()","title":"Fake OAuth 2.0 resource and authorization app β€” oauth2_resource_app","text":"App representing API server (resource/authorization)","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":null,"dir":"Reference","previous_headings":"","what":"App representing the third-party app β€” oauth2_third_party_app","title":"App representing the third-party app β€” oauth2_third_party_app","text":"webfakes package comes two fake apps allow imitate OAuth2.0 flow test cases. (See Aaron Parecki’s tutorial good introduction OAuth2.0.) One app (oauth2_resource_app()) API server serves resource provides authorization. oauth2_third_party_app() plays role third-party app. useful testing demonstrating code handling OAuth2.0 authorization, token caching, etc. package. apps can used tests directly, adapt one better mimic particular OAuth2.0 flow.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"App representing the third-party app β€” oauth2_third_party_app","text":"","code":"oauth2_third_party_app(name = \"Third-Party app\")"},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"App representing the third-party app β€” oauth2_third_party_app","text":"name Name third-party app","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"App representing the third-party app β€” oauth2_third_party_app","text":"webfakes app","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/oauth2_third_party_app.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"App representing the third-party app β€” oauth2_third_party_app","text":"Endpoints: POST /login/config Use endpoint configure client ID client secret app, received oauth2_resource_app() (another resource app). need send JSON URL encoded body: auth_url, authorization URL resource app. token_url, token URL resource app. client_id, client ID, received resource app. client_secret client secret, received resource app. GET /login Use endpoint start login process. redirect resource app authorization OAuth2.0 dance /login/redirect. GET /login/redirect, POST /login/redirect redirect URI third party app. (HTTP clients redirect POST GET, others , .) endpoint used resource app, received code can exchanged access token state generated /login. contacts resource app get access token, stores token app$locals local variables. fails HTTP code 500 obtain access token. success returns JSON dictionary access_token, expiry refresh_token (optionally) default. behavior can changed redefining app$redirect_hook() function. GET /locals returns tokens obtained resource app. GET /data endpoint uses obtained token(s) connect /data endpoint resource app. /data endpoint resource app needs authorization. responds response resource app. tries refresh access token app needed. details see vignette(\"oauth\", package = \"webfakes\").","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":null,"dir":"Reference","previous_headings":"","what":"Webfakes web server options β€” server_opts","title":"Webfakes web server options β€” server_opts","text":"Webfakes web server options","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Webfakes web server options β€” server_opts","text":"","code":"server_opts( remote = FALSE, port = NULL, num_threads = 1, interfaces = \"127.0.0.1\", enable_keep_alive = FALSE, access_log_file = remote, error_log_file = TRUE, tcp_nodelay = FALSE, throttle = Inf, decode_url = TRUE, ssl_certificate = NULL )"},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Webfakes web server options β€” server_opts","text":"remote Meta-option. set TRUE, webfakes uses slightly different defaults, appropriate background server process. port Port start web server . Defaults randomly chosen port. num_threads Number request handler threads use. Typically need one thread, unless run test cases parallel make concurrent HTTP requests. interfaces network interfaces listen . test web server, defaults localhost. bind public interface know . webfakes designed serve public web pages. enable_keep_alive Whether server keeps connections alive. access_log_file TRUE, FALSE, path. See 'Logging' . error_log_file TRUE, FALSE, path. See 'Logging' . tcp_nodelay TRUE packages sent soon possible, instead waiting full buffer timeout occur. throttle Limit download speed clients. Inf, maximum number bytes per second, sent connection. decode_url Whether server automatically decode URL-encodded URLs. TRUE (default), /foo%2fbar converted /foo/bar automatically. FALSE, URLs URL-decoded. ssl_certificate Path SSL certificate server, needed want server HTTPS requests.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Webfakes web server options β€” server_opts","text":"List options can passed webfakes_app$listen() (see new_app()), new_app_process().","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"logging","dir":"Reference","previous_headings":"","what":"Logging","title":"Webfakes web server options β€” server_opts","text":"access_log_file, TRUE means /access.log. error_log_file, TRUE means /error.log. set contents WEBFAKES_LOG_DIR environment variable, set. Otherwise set /webfakes local apps //webfakes remote apps (started new_app_procss()). session temporary directory main process. process id subprocess.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/server_opts.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Webfakes web server options β€” server_opts","text":"","code":"# See the defaults server_opts() #> $remote #> [1] FALSE #> #> $port #> NULL #> #> $num_threads #> [1] 1 #> #> $interfaces #> [1] \"127.0.0.1\" #> #> $enable_keep_alive #> [1] FALSE #> #> $access_log_file #> [1] NA #> #> $error_log_file #> [1] \"/tmp/RtmpCq81KX/webfakes/error.log\" #> #> $tcp_nodelay #> [1] FALSE #> #> $throttle #> [1] Inf #> #> $decode_url #> [1] TRUE #> #> $ssl_certificate #> [1] \"/home/runner/work/_temp/Library/webfakes/cert/localhost/server.pem\" #>"},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":null,"dir":"Reference","previous_headings":"","what":"glue based template engine β€” tmpl_glue","title":"glue based template engine β€” tmpl_glue","text":"Use template engine create pages glue templates. See glue::glue() syntax.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"glue based template engine β€” tmpl_glue","text":"","code":"tmpl_glue( sep = \"\", open = \"{\", close = \"}\", na = \"NA\", transformer = NULL, trim = TRUE )"},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"glue based template engine β€” tmpl_glue","text":"sep Separator used separate elements. open opening delimiter. Doubling full delimiter escapes . close closing delimiter. Doubling full delimiter escapes . na Value replace NA values . NULL missing values propagated, NA result cause NA output. Otherwise value replaced value na. transformer function taking three parameters code, envir data used transform output block evaluation. trim Whether trim input template glue::trim() .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"glue based template engine β€” tmpl_glue","text":"Template function.","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/tmpl_glue.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"glue based template engine β€” tmpl_glue","text":"","code":"# See th 'hello' app at hello_root <- system.file(package = \"webfakes\", \"examples\", \"hello\") hello_root #> [1] \"/home/runner/work/_temp/Library/webfakes/examples/hello\" app <- new_app() app$engine(\"txt\", tmpl_glue()) app$use(mw_log()) app$get(\"/view\", function(req, res) { txt <- res$render(\"test\") res$ set_type(\"text/plain\")$ send(txt) }) # Switch to the app's root: setwd(hello_root) # Now start the app with: app$listen(3000L) # Or start it in another process: new_process(app)"},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes-package.html","id":null,"dir":"Reference","previous_headings":"","what":"webfakes: Fake Web Apps for HTTP Testing β€” webfakes-package","title":"webfakes: Fake Web Apps for HTTP Testing β€” webfakes-package","text":"Create web app makes easier test web clients without using internet. includes web app framework path matching, parameters templates. Can parse various 'HTTP' request bodies. Can send 'JSON' data files disk. Includes web app implements 'httpbin.org' web service.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes-package.html","id":"author","dir":"Reference","previous_headings":"","what":"Author","title":"webfakes: Fake Web Apps for HTTP Testing β€” webfakes-package","text":"Maintainer: GΓ‘bor CsΓ‘rdi csardi.gabor@gmail.com contributors: Posit Software, PBC [copyright holder, funder] Civetweb contributors (see inst/credits/ciwetweb.md) [contributor] Redoc contributors (see inst/credits/redoc.md) [contributor] L. Peter Deutsch (src/md5.h) [contributor] Martin Purschke (src/md5.h) [contributor] Aladdin Enterprises (src/md5.h) [copyright holder] MaΓ«lle Salmon maelle.salmon@yahoo.se (ORCID) [contributor]","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_request.html","id":null,"dir":"Reference","previous_headings":"","what":"A webfakes request object β€” webfakes_request","title":"A webfakes request object β€” webfakes_request","text":"webfakes creates webfakes_request object every incoming HTTP request. object passed every matched route middleware, response sent. reference semantics, handlers can modify .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_request.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"A webfakes request object β€” webfakes_request","text":"Fields methods: app: webfakes_app object . headers: Named list HTTP request headers. hostname: Host header, server hostname maybe port. method: HTTP method. path: Server path. protocol: \"http\" \"https\". query_string: raw query string, without starting ?. query: Parsed query parameters named list. remote_addr: String, domain name IP address client. webfakes runs localhost, 127.0.0.1. url: full URL request. get_header(field): Function query request header. Returns NULL header present. Body parsing middleware adds additional fields request object. See mw_raw(), mw_text(), mw_json(), mw_multipart() mw_urlencoded().","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_request.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"A webfakes request object β€” webfakes_request","text":"","code":"# This is how you can see the request and response objects: app <- new_app() app$get(\"/\", function(req, res) { browser() res$send(\"done\") }) app #> #> routes: #> get / #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Now start this app on a port: # app$listen(3000) # and connect to it from a web browser: http://127.0.0.1:3000 # You can also use another R session to connect: # httr::GET(\"http://127.0.0.1:3000\") # or the command line curl tool: # curl -v http://127.0.0.1:3000 # The app will stop while processing the request."},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_response.html","id":null,"dir":"Reference","previous_headings":"","what":"A webfakes response object β€” webfakes_response","title":"A webfakes response object β€” webfakes_response","text":"webfakes creates webfakes_response object every incoming HTTP request. object passed every matched route middleware, HTTP response sent. reference semantics, handlers can modify .","code":""},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_response.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"A webfakes response object β€” webfakes_response","text":"Fields methods: app: webfakes_app object . req: request object. headers_sent: Whether response headers already sent . locals: Local variables, shared handler functions. end user, middlewares. delay(secs): delay response number seconds. handler calls delay(), handler called , specified number seconds passed. Use locals environment distinguish calls. using delay(), want serve requests parallel, probably need multi-threaded server, see server_opts(). add_header(field, value): Add response header. Note add_header() may create duplicate headers. usually want set_header(). get_header(field): Query currently set response headers. field present return NULL. on_response(fun): Run fun handler function just response sent . point headers body already properly set. redirect(path, status = 302): Send redirect response. sets Location header, also sends text/plain body. render(view, locals = list()): Render template page. Searches view template page, using registered engine extensions, calls first matching template engine. Returns filled template. send(body). Send specified body. body can raw vector, HTML text. raw vectors sets content type application/octet-stream. send_json(object = NULL, text = NULL, ...): Send JSON response. Either object text must given. object converted JSON using jsonlite::toJSON(). ... passed jsonlite::toJSON(). sets content type appropriately. send_file(path, root = \".\"): Send file. Set root = \"/\" absolute file names. sets content type automatically, based extension file, set already. send_status(status): Send specified HTTP status code, without response body. send_chunk(data): Send chunk response chunked encoding. first chunk automatically send HTTP response headers. Webfakes automatically send final zero-lengh chunk, unless $delay() called. set_header(field, value): Set response header. headers sent already, throws warning, nothing. set_status(status): Set response status code. headers sent already, throws warning, nothing. set_type(type): Set response content type. contains / character set , otherwise assumed file extension, corresponding MIME type set. headers sent already, throws warning, nothing. add_cookie(name, value, options): Adds cookie response. options named list, may contain: domain: Domain name cookie, set default. expires: Expiry date GMT. must POSIXct object, formatted correctly. 'http_only': TRUE, sets 'HttpOnly' attribute, Javasctipt access cookie. max_age: Maximum age, number seconds. path: Path cookie, defaults \"/\". same_site: 'SameSite' cookie attribute. Possible values \"strict\", \"lax\" \"none\". secure: TRUE, sets 'Secure' attribute. clear_cookie(name, options = list()): clears cookie. Typically, web browsers clear cookie options also match. write(data): writes (part ) body response. also sends response headers, sent . Usually need one send() methods, send HTTP response one go, first headers, body. Alternatively, can use $write() send response parts.","code":""},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/reference/webfakes_response.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"A webfakes response object β€” webfakes_response","text":"","code":"# This is how you can see the request and response objects: app <- new_app() app$get(\"/\", function(req, res) { browser() res$send(\"done\") }) app #> #> routes: #> get / #> fields and methods: #> all(path, ...) # add route for *all* HTTP methods #> delete(path, ...) # add route for DELETE #> engine(ext, engine) # add template engine for file extension #> head(path, ...) # add route for HEAD #> listen(port) # start web app on port #> patch(path, ...) # add route for PATCH #> post(path, ...) # add route for POST #> put(path, ...) # add route for PUT #> use(...) # add middleware #> locals # app-wide shared data #> # see ?webfakes_app for all methods # Now start this app on a port: # app$listen(3000) # and connect to it from a web browser: http://127.0.0.1:3000 # You can also use another R session to connect: # httr::GET(\"http://127.0.0.1:3000\") # or the command line curl tool: # curl -v http://127.0.0.1:3000 # The app will stop while processing the request."},{"path":[]},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-132","dir":"Changelog","previous_headings":"","what":"webfakes 1.3.2","title":"webfakes 1.3.2","text":"CRAN release: 2025-01-11 New server option: decode_url. set FALSE, web server URL-decode URL (#106).","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-131","dir":"Changelog","previous_headings":"","what":"webfakes 1.3.1","title":"webfakes 1.3.1","text":"CRAN release: 2024-04-25 changes.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-130","dir":"Changelog","previous_headings":"","what":"webfakes 1.3.0","title":"webfakes 1.3.0","text":"CRAN release: 2023-12-11 New git_app() app fake git HTTP server. See webfakes test cases examples. New mw_cgi() middleware call CGI scripts. See new git_app() example.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-121","dir":"Changelog","previous_headings":"","what":"webfakes 1.2.1","title":"webfakes 1.2.1","text":"CRAN release: 2023-10-01 tmpl_glue() now works correctly platforms issue readChar(..., useBytes = TRUE), e.g.Β macOS 14.x Sonoma: https://bugs.r-project.org/show_bug.cgi?id=18605.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-120","dir":"Changelog","previous_headings":"","what":"webfakes 1.2.0","title":"webfakes 1.2.0","text":"CRAN release: 2023-05-16 httpbin app now implements /brotli, /deflate, /digest-auth /forms/post, /hidden-basic-auth, /range/:n, /stream/:n, /cache /cache/:value endpoints. , implements endpoint original Python httpbin app (#3). New middleware mw_cookie_parser() parse Cookie header. Relatedly, new response$add_cookie() response$clear_cookie() methods add cookie response add header clears cookie (#2). Parsing query parametes without value now fail. New utility function http_time_stamp() format time stamp HTTP. httpbin app now implements endpoints related cookies (#3). httpbin app now sends Date header correct format. offset parameter now optional /links endpoint httpbin app. mw_etag() now add ETag header response, one already. (comparision case sensitive.) New middleware: mw_range_parser() parse Range headers.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-117","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.7","title":"webfakes 1.1.7","text":"CRAN release: 2023-02-08 user visible changes.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-116","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.6","title":"webfakes 1.1.6","text":"CRAN release: 2022-11-08 response$send_file() now handles root = \"/\" absolute paths better Windows. new_app_process() local_app_process() now faster, app object need copy subprocess smaller.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-115","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.5","title":"webfakes 1.1.5","text":"CRAN release: 2022-10-25 mw_etag() now handles -None-Match header properly, sets status code response 304, removes response body.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-114","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.4","title":"webfakes 1.1.4","text":"CRAN release: 2022-09-08 user visible changes.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-113","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.3","title":"webfakes 1.1.3","text":"CRAN release: 2021-04-30 webfakes now compiles older macOS versions, hopefully really.","code":""},{"path":"https://webfakes.r-lib.org/dev/news/index.html","id":"webfakes-112","dir":"Changelog","previous_headings":"","what":"webfakes 1.1.2","title":"webfakes 1.1.2","text":"CRAN release: 2021-04-05 webfakes now compiles older macOS versions (10.12).","code":""}]