Skip to content

Commit

Permalink
version 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborcsardi authored and cran-robot committed May 6, 2020
0 parents commit ff09f0f
Show file tree
Hide file tree
Showing 115 changed files with 33,610 additions and 0 deletions.
44 changes: 44 additions & 0 deletions DESCRIPTION
@@ -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
2 changes: 2 additions & 0 deletions LICENSE
@@ -0,0 +1,2 @@
YEAR: 2020
COPYRIGHT HOLDER: Gábor Csárdi
114 changes: 114 additions & 0 deletions MD5
@@ -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
27 changes: 27 additions & 0 deletions NAMESPACE
@@ -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_")
4 changes: 4 additions & 0 deletions NEWS.md
@@ -0,0 +1,4 @@

# 1.0.0

First public release.
199 changes: 199 additions & 0 deletions R/app-process.R
@@ -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
}

0 comments on commit ff09f0f

Please sign in to comment.