The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
drogonR wraps the Drogon C++ HTTP framework and exposes it to R. The same server can be driven through three different APIs depending on how much R you want in the request hot path:
dr_*_cpp() — C++ shared path. Handlers
are pure C functions in another R package, resolved via
R_GetCCallable() and called on Drogon’s worker threads. R
is not in the hot path. Intended for inference packages
(ggmlR, llamaR, sd2R) that already do their work in C++.dr_app() + dr_get() /
dr_post() / … — drogonR native. Handlers are R
functions; the bridge marshals each request onto the main R thread, runs
the closure, and ships the response back. Full control over routes,
middleware, response shape. Recommended for new APIs written in R.drogonR::pr_run() — plumber drop-in.
Existing plumber::pr_run(pr) becomes
drogonR::pr_run(pr) with no other changes. The shim
translates the plumber router into drogonR routes and serves them via
dr_serve(). Recommended when you have a plumber codebase
and want a faster runtime without rewriting it.The variants share the same Drogon server underneath; only the registration path differs.
cpp-shared (dr_*_cpp) |
native (dr_get) |
plumber shim (pr_run) |
plumber (baseline) | |
|---|---|---|---|---|
| Handler language | C / C++ | R | R (plumber convention) | R |
| Calls into R per request | 0 | 1 | 1 | 1 |
| Code change vs plumber | rewrite handlers in C | rewrite using dr_app() |
one line (pr_run) |
— |
| Best for | inference / hot loops | new R-side APIs | existing plumber apps | dev / one-offs |
The cpp-shared variant is the only one that bypasses R entirely on
the request path. The native and shim variants both run an R closure per
request — the difference between them is the calling convention
(drogonR-native gets a req object; the shim emulates
plumber’s positional / named-arg matching).
wrk -t4 -c50 -d30s against four servers running the same
two routes on localhost. Workload is intentionally trivial
— /ping returns a fixed {"ok":true},
/ping-text returns "ok" — so the numbers
measure the framework overhead, not the handler cost. See
tools/bench/run.sh for the harness.
| Variant | /ping (JSON) |
/ping-text (plain) |
|---|---|---|
| drogonR cpp-shared | 239 428 rps, 200 µs | 234 753 rps, 202 µs |
| drogonR native | 116 159 rps, 822 µs | 218 163 rps, 252 µs |
| drogonR plumber-shim | 94 400 rps, 591 µs | 99 276 rps, 583 µs |
| plumber (baseline) | 1 078 rps, 44.5 ms | 1 069 rps, 44.9 ms |
(rps = requests per second, single-host loopback; latency is the wrk average.) Two things to read out of this:
The shim is slower than native for the same workload — it pays the cost of plumber’s argument-matching convention (path / query / body lookup, default-serialiser dispatch) on every call. It’s still well above plumber itself because the I/O loop is C++.
Variant 1 — cpp-shared (in your inference package):
// In yourPackage/src/handlers.c
#include <drogonR.h>
#include <R_ext/Rdynload.h>
static int h_predict(const char *body, size_t body_len,
const char *query,
const char *const *path, size_t path_n,
const char *const *hdrs, size_t hdrs_n,
char **out_body, size_t *out_len,
int *out_status, char **out_content_type) {
/* run inference, fill out_body via malloc(), set status, ctype */
return 0;
}
void R_init_yourPackage(DllInfo *dll) {
R_RegisterCCallable("yourPackage", "predict", (DL_FUNC) h_predict);
}# In your serving script
library(drogonR)
app <- dr_app() |>
dr_post_cpp("/predict", "yourPackage", "predict")
dr_serve(app, port = 8080L)Variant 2 — drogonR native:
library(drogonR)
app <- dr_app() |>
dr_get("/users/:id", function(req) {
dr_json(list(id = req$params[["id"]], ok = TRUE))
})
dr_serve(app, port = 8080L)Variant 3 — plumber shim:
# Existing plumber.R, unchanged:
library(plumber)
pr <- pr() |>
pr_get("/users/<id>", function(id) list(id = id, ok = TRUE))
# Only this line changes — drogonR::, not plumber::
drogonR::pr_run(pr, port = 8080L)vignette("mode-cpp-shared", package = "drogonR") —
full ABI for variant 1, including memory ownership and the threading
rule.vignette("mode-native", package = "drogonR") —
req/res shape, response helpers, middleware,
lifecycle of dr_serve().vignette("mode-plumber-shim", package = "drogonR") —
exact subset of plumber the shim implements, what triggers an explicit
error, what’s silently accepted-and-ignored.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.