Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ff09f0f
Showing
115 changed files
with
33,610 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
Package: presser | ||
Title: Lightweight Web Server for Testing | ||
Version: 1.0.0 | ||
Authors@R: c( | ||
person("Gábor", "Csárdi", role = c("aut", "cre"), | ||
email = "csardi.gabor@gmail.com"), | ||
person(family = "RStudio, Pbc.", role = "cph"), | ||
person(family = "Civetweb contributors", role = "ctb", | ||
comment = "see inst/credits/ciwetweb.md"), | ||
person(family = "Redoc contributors", role = "ctb", | ||
comment = "see inst/credits/redoc.md"), | ||
person("L. Peter", "Deutsch", rol = "ctb", | ||
comment = "src/md5.h"), | ||
person("Martin", "Purschke", rol = "ctb", | ||
comment = "src/md5.h"), | ||
person(family = "Aladdin Enterprises", rol = "cph", | ||
comment = "src/md5.h") | ||
) | ||
Description: Create a web app that makes it easier to test web clients | ||
without using the internet. It includes a web app framework with path | ||
matching and parameters and templates. Can parse various 'HTTP' request | ||
bodies. Can send 'JSON' data or files from the disk. Includes a web app | ||
that implements the <https://httpbin.org> web service. | ||
License: MIT + file LICENSE | ||
LazyData: true | ||
URL: https://r-lib.github.io/presser, | ||
https://github.com/gaborcsardi/presser#readme | ||
BugReports: https://github.com/gaborcsardi/presser/issues | ||
RoxygenNote: 7.1.0 | ||
Imports: stats, tools, utils | ||
Suggests: callr, curl, glue, jsonlite, testthat, withr | ||
Encoding: UTF-8 | ||
NeedsCompilation: yes | ||
Packaged: 2020-05-02 15:21:40 UTC; gaborcsardi | ||
Author: Gábor Csárdi [aut, cre], | ||
RStudio, Pbc. [cph], | ||
Civetweb contributors [ctb] (see inst/credits/ciwetweb.md), | ||
Redoc contributors [ctb] (see inst/credits/redoc.md), | ||
L. Peter Deutsch [ctb] (src/md5.h), | ||
Martin Purschke [ctb] (src/md5.h), | ||
Aladdin Enterprises [cph] (src/md5.h) | ||
Maintainer: Gábor Csárdi <csardi.gabor@gmail.com> | ||
Repository: CRAN | ||
Date/Publication: 2020-05-06 13:00:06 UTC |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
YEAR: 2020 | ||
COPYRIGHT HOLDER: Gábor Csárdi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
4936f2c617abaad70e890c9bee1c7eef *DESCRIPTION | ||
fd6302a7bb0d29c2fba79b66644039bc *LICENSE | ||
e11a92d3755b33a34481ebad53f3c372 *NAMESPACE | ||
104b43a7414eeb73afc5f408f76d9163 *NEWS.md | ||
9add72249cc7f897213604fd6f7ff378 *R/app-process.R | ||
41cfa41c2d54046cd56c3ff5c9c93cde *R/app.R | ||
d8dbb0089c19547d728fd8f9cf8bf41c *R/base64.R | ||
a15ee3dddecc6fe3faaf206de2e61591 *R/cleancall.R | ||
124254ca1dda89cd7de9e49d5f0acbac *R/digest.R | ||
fb1c469d7b9630976770c169b6fb9ce3 *R/httpbin.R | ||
75ecdf4307a22a8de5e067e4e29f9ee2 *R/mime.R | ||
f417ce7bb0aa79fb5516378c50b2cc1e *R/mw-etag.R | ||
f5b4e1e2238d94ceb1192dafd04fa145 *R/mw-json.R | ||
88ce945ebe476b948bd81fb1b14785bb *R/mw-log.R | ||
016fc22ce24383234d77c649b00e5d7f *R/mw-multipart.R | ||
b42c2679443555f47707a64edb108739 *R/mw-raw.R | ||
62284fd7c50d1f709aae2e053377a6fa *R/mw-static.R | ||
1cc38594e39b091d2a6ab87a44983929 *R/mw-text.R | ||
5dddf314894f1d91e4f5a4366c385058 *R/mw-urlencoded.R | ||
06a1a2cbedf28f121170e4f2fc582d48 *R/package.R | ||
093d21258b860d25a24477c144e68f71 *R/path.R | ||
793499436d38cf98c1f2b6a52f8f7eea *R/print.R | ||
260e9f54b6f663b907494ad53dd14d53 *R/rematch2.R | ||
3de8af312d17eb0efa956a466d5c3f05 *R/request.R | ||
1804793b5457d7a127b82745a02aa0f8 *R/response.R | ||
9111c6f1e2724f001f966b81c37576ab *R/server.R | ||
d8b172f2ffc307fb39342b869b3bda25 *R/status.R | ||
16f27008c3063313bcf4893d8ac351d6 *R/tmpl-glue.R | ||
50b79f63000438a61244a8a10d77dd5d *R/utils.R | ||
4aca6f8e0b573be0301335a8be038814 *R/uuid.R | ||
8da8ed50fcc7c0fac9982f0a4cd880f2 *README.md | ||
d3943445ebf976356df3874cc85c2f43 *inst/credits/ciwetweb.md | ||
bb9faae548cd71cee0803bd99f1df5da *inst/credits/redoc.md | ||
1f3bc957b4005a2c7c13080362fbf712 *inst/examples/hello/app.R | ||
c5a7448fdb6c04af06978c875f979b84 *inst/examples/hello/views/test.txt | ||
6068c6eb4deb8c0587113fea266399dc *inst/examples/httpbin/app.R | ||
d0c8fe99d9d39486fe00ffc0809a7233 *inst/examples/httpbin/assets/httpbin.html | ||
d439215e6d34774c286caeaa5dddb2d0 *inst/examples/httpbin/data/deny.txt | ||
49b221c274d8d1545e577538278e25f9 *inst/examples/httpbin/data/example.html | ||
302c1d78011b9926fcf3e6a5e556e7a1 *inst/examples/httpbin/data/example.json | ||
a88c5078ffed61c2fff326de48cdb740 *inst/examples/httpbin/data/example.xml | ||
46fd03688e49c6b6b0a2b7d3553c1e42 *inst/examples/httpbin/data/robots.txt | ||
c159c6400f3b55c71740339832e11795 *inst/examples/httpbin/data/utf8.html | ||
e1f9ee4cf1f2be1c1d3d824ba25133b4 *inst/examples/httpbin/doc-template.hbs | ||
d67bd2c5bff4abba2bd11fb0ab07d404 *inst/examples/httpbin/images/Rlogo.jpeg | ||
7381224c65138a2acdf3a8346f8275c4 *inst/examples/httpbin/images/Rlogo.png | ||
2b5719958c377e203c20463e340671f3 *inst/examples/httpbin/images/Rlogo.svg | ||
1a5760d159f118ba8d495ce2c7e082d7 *inst/examples/httpbin/images/Rlogo.webp | ||
8af9680f9fa56dcab838c68d3ff0b2e4 *inst/examples/httpbin/openapi.yaml | ||
7381224c65138a2acdf3a8346f8275c4 *inst/examples/send-file/Rlogo.png | ||
6f7411658c569d0b54fbf1e247f52c07 *inst/examples/send-file/app.R | ||
cd1581100f7264b0e22f2b4137054d08 *inst/examples/static/app.R | ||
b264e4c43787cb81e7d4c5eb4b006427 *inst/examples/static/public/bar/foo.txt | ||
5186b0805ea39b8efd70a1d3c85f7a89 *inst/examples/static/public/foo/bar.html | ||
7c2c080a60bdbcf4ebe6de2a4c27f8ac *inst/examples/static/public/foo/bar.json | ||
748b60e20cab7d096f27f37cbb768d37 *man/httpbin_app.Rd | ||
932f8cc4807d4653260d126bbb202a92 *man/mw_etag.Rd | ||
0630ecb57c8c2a509fc810df6a06b042 *man/mw_json.Rd | ||
2201850258512a99f17c8202929306f7 *man/mw_log.Rd | ||
7aeb6d8b5deb8aa866140c6c0e9f120b *man/mw_multipart.Rd | ||
fb77afb0bb216ab048f81ec103ac4193 *man/mw_raw.Rd | ||
16a0ef4185741babc2036ea22ba1a7f8 *man/mw_static.Rd | ||
592e0ba8c0303d3feb02b3d8f45d7b2c *man/mw_text.Rd | ||
e7562fc5e83a445cd02c0709e4b9b05c *man/mw_urlencoded.Rd | ||
b46de99b693688a2fd8fb05ebbcbe98f *man/new_app.Rd | ||
baedab059c8fda5b55d2ac075c219d8b *man/new_app_process.Rd | ||
3ae1c346073e68b13298dff3ab434d7b *man/new_regexp.Rd | ||
2f4cb0e91b6e753008db6b43411214e6 *man/presser_request.Rd | ||
b2f1afff7d4f0e320bcc574fee32a96d *man/presser_response.Rd | ||
4b8058ad95e1c3acbaf982f4221fd28e *man/server_opts.Rd | ||
cf703e506b519c30752f5588b0f15cf2 *man/tmpl_glue.Rd | ||
f1cdc3b5b0687e925e45abf0ab515d7f *src/Makevars | ||
8d444e303c54e774935a3c9539c6d6ba *src/Makevars.win | ||
88176388fdea2c81f17eeb0076e8e6e1 *src/civetweb.c | ||
aab3ed6a469fa6d0dc9f0f3c68d3ffcd *src/civetweb.h | ||
eeab79e425da47c75b561bd2970aad7d *src/cleancall.c | ||
bff2a7876350a82b2c7d7e402588cddf *src/cleancall.h | ||
b5b5bf8f3f35455c5db13cf997275539 *src/crc32.c | ||
bc57a7d677a95e5b3e78250e4a5e0beb *src/errors.c | ||
1ac3a7b49282794e006a1715f6ccf8af *src/errors.h | ||
b479f6dbea9393aea0b3b765d5d56722 *src/handle_form.h | ||
29f9c450b1c517bc4dea762ee2a8057e *src/md5.h | ||
df94c2215e1b5175af697b5ecc50f48c *src/rweb.c | ||
b2d15d5dadf9a0909c74a3378af5a7d4 *tests/testthat.R | ||
d9aa59f3965a0c5ea4d2481351c5b572 *tests/testthat/fixtures/output/presser_app.txt | ||
6b5e22d94f4889ff180b9520b6aaf39a *tests/testthat/fixtures/output/presser_app_process.txt | ||
f78682f080e53a8600a525292786f3c0 *tests/testthat/fixtures/output/presser_regexp.txt | ||
db38d674fbd7409054e4fa452411153d *tests/testthat/fixtures/output/presser_request.txt | ||
d2532dfe1734f75248b6a3ec5f273e7d *tests/testthat/fixtures/output/presser_response.txt | ||
5186b0805ea39b8efd70a1d3c85f7a89 *tests/testthat/fixtures/static/static.html | ||
cef8a5c5d7fcfb9cf601bf3a146a47e7 *tests/testthat/fixtures/static/subdir/static.json | ||
5186b0805ea39b8efd70a1d3c85f7a89 *tests/testthat/fixtures/static2/static.html | ||
f6b1e97351bf27f84ff66285cfd3e033 *tests/testthat/fixtures/static2/static.tar.gz | ||
cef8a5c5d7fcfb9cf601bf3a146a47e7 *tests/testthat/fixtures/static2/subdir/static.json | ||
01d5c74047c909bf938df6556a623e39 *tests/testthat/fixtures/views/test-view.html | ||
bc43ae8a16b3cdcbb35157c917474e77 *tests/testthat/helper.R | ||
e9a75bddd5c8f728be2107066edcca1d *tests/testthat/test-app-process.R | ||
e0308990109b68463aa1d7c6b4f08eb2 *tests/testthat/test-app.R | ||
8561f7c34ea02be68ee231e1fef2d18a *tests/testthat/test-base64.R | ||
eef99255cd2c175cf5fa3d75ef07e29a *tests/testthat/test-delay.R | ||
05093fd52438c9445a0a4f34f0b75bea *tests/testthat/test-http-methods.R | ||
6cea2f069ec769ae4ef61904d2c62063 *tests/testthat/test-httpbin.R | ||
3298e815bfcc7198a44fc6dc904f8b98 *tests/testthat/test-mime.R | ||
ece93b8f01ed08b9aa0304475d029f6a *tests/testthat/test-mw-etag.R | ||
bfb38e7adca7441de10f40aaa40d5c64 *tests/testthat/test-mw-log.R | ||
1971759cdf1d176b39d7d0ea47efad3f *tests/testthat/test-mw-multipart.R | ||
11796ade5290b0ae17a884ed0bf8b982 *tests/testthat/test-mw-raw.R | ||
24c6814ebf58ad551f77bb6c677eb837 *tests/testthat/test-mw-static.R | ||
9f672d89d1da1fccba2ae46b796db017 *tests/testthat/test-mw-urlencoded.R | ||
11b6488e4a8debdf26dfd91c5c1f5764 *tests/testthat/test-path-matching.R | ||
b8fe0e22ede1acfa55fceaca13b92480 *tests/testthat/test-print.R | ||
9bd6e967fe4536ad83d2d94e414d5bd1 *tests/testthat/test-response.R | ||
a03d84ebaf51d793e42535d8ad3d7543 *tests/testthat/test-tmpl-glue.R | ||
f8b065cc5967c5ece35bbff6dd056eaf *tests/testthat/test-uuid.R |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Generated by roxygen2: do not edit by hand | ||
|
||
S3method(format,presser_app) | ||
S3method(format,presser_app_process) | ||
S3method(format,presser_regexp) | ||
S3method(format,presser_request) | ||
S3method(format,presser_response) | ||
S3method(print,presser_app) | ||
S3method(print,presser_app_process) | ||
S3method(print,presser_regexp) | ||
S3method(print,presser_request) | ||
S3method(print,presser_response) | ||
export(httpbin_app) | ||
export(mw_etag) | ||
export(mw_json) | ||
export(mw_log) | ||
export(mw_multipart) | ||
export(mw_raw) | ||
export(mw_static) | ||
export(mw_text) | ||
export(mw_urlencoded) | ||
export(new_app) | ||
export(new_app_process) | ||
export(new_regexp) | ||
export(server_opts) | ||
export(tmpl_glue) | ||
useDynLib(presser, .registration = TRUE, .fixes = "c_") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
|
||
# 1.0.0 | ||
|
||
First public release. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
|
||
#' Run a presser app in another process | ||
#' | ||
#' Runs an app in a subprocess, using [callr::r_session]. | ||
#' | ||
#' @param app `presser_app` object, the web app to run. | ||
#' @param port Port to use. By default the OS assigns a port. | ||
#' @param opts Server options. See [server_opts()] for the defaults. | ||
#' @param process_timeout How long to wait for the subprocess to start, in | ||
#' milliseconds. | ||
#' @param callr_opts Options to pass to [callr::r_session_options()] | ||
#' when setting up the subprocess. | ||
#' @return A `presser_app_process` object. | ||
#' | ||
#' ## Methods | ||
#' | ||
#' The `presser_app_process` class has the following methods: | ||
#' | ||
#' ```r | ||
#' get_app() | ||
#' get_port() | ||
#' stop() | ||
#' get_state() | ||
#' local_env(envvars) | ||
#' url(path = "/", query = NULL) | ||
#' ``` | ||
#' | ||
#' * `envvars`: Named list of environment variables. | ||
#' * `path`: Path to return the URL for. | ||
#' * `query`: Additional query parameters, a named list, to add to the URL. | ||
#' | ||
#' `get_app()` returns the app object. | ||
#' | ||
#' `get_port()` returns the port the web server is running on. | ||
#' | ||
#' `stop()` stops the web server, and also the subprocess. If the error | ||
#' log file is not empty, then it dumps its contents to the screen. | ||
#' | ||
#' `get_state()` returns a string, the state of the web server: | ||
#' * `"not running"` the server is not running (because it was stopped | ||
#' already). | ||
#' * `"live"` means that the server is running. | ||
#' * `"dead"` means that the subprocess has quit or crashed. | ||
#' | ||
#' `local_env()` sets the given environment variables for the duration of | ||
#' the app process. It resets them in `$stop()`. | ||
#' | ||
#' `url()` returns the URL of the web app. You can use the `path` | ||
#' parameter to return a specific path. | ||
#' | ||
#' @aliases presser_app_process | ||
#' @export | ||
#' @examples | ||
#' 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)) | ||
#' | ||
#' proc$stop() | ||
|
||
new_app_process <- function(app, port = NULL, | ||
opts = server_opts(remote = TRUE), | ||
process_timeout = 5000, callr_opts = NULL) { | ||
|
||
app; port; opts; process_timeout; callr_opts | ||
|
||
self <- new_object( | ||
"presser_app_process", | ||
|
||
new = function(app, port, opts, callr_opts) { | ||
self$.app <- app | ||
callr_opts <- do.call(callr::r_session_options, as.list(callr_opts)) | ||
self$.process <- callr::r_session$new(callr_opts, wait = TRUE) | ||
self$.process$call( | ||
args = list(app, port, opts), | ||
function(app, port, opts) { | ||
library(presser) | ||
.GlobalEnv$app <- app | ||
app$listen(port = port, opts = opts) | ||
} | ||
) | ||
|
||
if (self$.process$poll_process(process_timeout) != "ready") { | ||
self$.process$kill() | ||
stop("presser app subprocess did not start :(") | ||
} | ||
msg <- self$.process$read() | ||
if (msg$code == 200 && !is.null(msg$error)) { | ||
msg$error$message <- paste0( | ||
"failed to start presser app process: ", | ||
msg$error$message | ||
) | ||
stop(msg$error) | ||
} | ||
if (msg$code != 301) { | ||
stop("Unexpected message from presser app subprocess. ", | ||
"Report a bug please.") | ||
} | ||
self$.port <- msg$message$port | ||
self$.access_log <- msg$message$access_log | ||
self$.error_log <- msg$message$error_log | ||
|
||
invisible(self) | ||
}, | ||
|
||
get_app = function() self$.app, | ||
|
||
get_port = function() self$.port, | ||
|
||
stop = function() { | ||
if (is.null(self$.process)) return(invisible(self)) | ||
if (!is.null(self$.old_env)) set_envvar(self$.old_env) | ||
|
||
if (!self$.process$is_alive()) { | ||
status <- self$.process$get_exit_status() | ||
out <- err <- NULL | ||
try_silently(out <- self$.process$read_output()) | ||
try_silently(err <- self$.process$read_error()) | ||
cat0("presser process dead, exit code: ", status, "\n") | ||
if (!is.null(out)) cat0("stdout:", out, "\n") | ||
if (!is.null(err)) cat0("stderr:", err, "\n") | ||
} | ||
|
||
# The details are important here, for the sake of covr, | ||
# so that we can test the presser package itself. | ||
# 1. The subprocess serving the app is in Sys.sleep(), which we | ||
# need to interrupt first. | ||
# 2. Then we need to read out the result of that $call() | ||
# (i.e. the interruption), because otherwise the subprocess is | ||
# stuck at a blocking write() system call, and cannot be | ||
# interrupted in the $close() call, and will be killed, and | ||
# then it cannot write out the coverage results. | ||
# 3. Once we $read(), we can call $close() because that will | ||
# close the standard input of the subprocess, which is reading | ||
# the standard input, so it will quit. | ||
|
||
self$.process$interrupt() | ||
self$.process$poll_process(1000) | ||
try_silently(self$.process$read()) | ||
try_silently(self$.process$close()) | ||
self$.print_errors() | ||
self$.process <- NULL | ||
invisible(self) | ||
}, | ||
|
||
get_state = function() { | ||
if (is.null(self$.process)) { | ||
"not running" | ||
} else if (self$.process$is_alive()) { | ||
"live" | ||
} else { | ||
"dead" | ||
} | ||
}, | ||
|
||
local_env = function(envvars) { | ||
self$.old_env <- c(self$.old_env, set_envvar(envvars)) | ||
invisible(self) | ||
}, | ||
|
||
url = function(path = "/", query = NULL) { | ||
if (!is.null(query)) { | ||
query <- paste0("?", paste0(names(query), "=", query, collapse = "&")) | ||
} | ||
paste0("http://127.0.0.1:", self$.port, path, query) | ||
}, | ||
|
||
.process = NULL, | ||
.app = NULL, | ||
.port = NULL, | ||
.old_env = NULL, | ||
.access_log = NA_character_, | ||
.error_log = NA_character_, | ||
|
||
.print_errors = function() { | ||
if (!is.na(self$.error_log) && file.exists(self$.error_log) && | ||
file.info(self$.error_log)$size > 0) { | ||
err <- readLines(self$.error_log, warn = FALSE) | ||
cat("presser web server errors:\n") | ||
cat(err, sep = "\n") | ||
} | ||
} | ||
) | ||
|
||
self$new( | ||
app, | ||
port = port, | ||
opts = opts, | ||
callr_opts = callr_opts | ||
) | ||
self$new <- NULL | ||
|
||
self | ||
} |
Oops, something went wrong.