Package {drogonR}


Type: Package
Title: High-Performance HTTP Server for R via 'Drogon'
Version: 0.1.6
Description: Provides an 'R' interface to the 'Drogon' high-performance 'C++' 'HTTP' server framework (https://github.com/drogonframework/drogon). Offers a 'plumber'-style application programming interface for building 'REST' services from 'R' with substantially higher throughput.
Depends: R (≥ 4.1.0)
Imports: jsonlite, processx, later (≥ 1.4.4)
LinkingTo: later
Suggests: testthat (≥ 3.0.0), httr2, curl, plumber, knitr, rmarkdown
VignetteBuilder: knitr
License: MIT + file LICENSE
Encoding: UTF-8
SystemRequirements: C++17, GNU make, OpenSSL (optional, for HTTPS)
NeedsCompilation: yes
RoxygenNote: 7.3.3
Config/testthat/edition: 3
URL: https://github.com/Zabis13/drogonR
BugReports: https://github.com/Zabis13/drogonR/issues
Packaged: 2026-05-09 13:18:54 UTC; yuri
Author: Yuri Baramykov ORCID iD [aut, cre], An Tao [ctb, cph] (Author of the bundled Drogon and Trantor C++ libraries), Shuo Chen [ctb, cph] (Author of the Muduo library, on which Trantor is based), Baptiste Lepilleur [ctb, cph] (Original author of the bundled JsonCpp library), Christopher Dunn [ctb] (Maintainer of JsonCpp), JsonCpp Contributors [ctb, cph] (See src/drogon/third_party/jsoncpp/AUTHORS in the package source), Bert Belder [ctb, cph] (Author of the bundled wepoll library (Windows epoll shim)), mman-win32 contributors [ctb, cph] (Authors of the bundled mman-win32 library; see src/mman-win32/LICENSE)
Maintainer: Yuri Baramykov <lbsbmsu@mail.ru>
Repository: CRAN
Date/Publication: 2026-05-13 08:20:02 UTC

drogonR: High-Performance HTTP Server for R via Drogon

Description

Provides an R interface to the Drogon C++ HTTP server framework.

Author(s)

Maintainer: Yuri Baramykov lbsbmsu@mail.ru (ORCID)

Other contributors:

See Also

Useful links:


Create a drogonR application

Description

Creates a fresh, empty 'drogon_app' object that holds the route table and configuration for a server. Routes are added with [dr_get()], [dr_post()], [dr_put()], [dr_delete()], and the server is started with [dr_serve()].

Usage

dr_app()

Details

The returned object is a mutable [environment] (so route-registration calls modify it in place and return it invisibly for use with '|>').

Value

An object of class 'drogon_app'.

Examples

app <- dr_app()
app <- dr_get(app, "/", function(req) "hello")

Read the request body

Description

Returns the body as raw text, parsed JSON, or a raw byte vector.

Usage

dr_body(req, as = c("text", "json", "raw"))

Arguments

req

A 'drogon_request'.

as

Output form: '"text"' (default), '"json"', or '"raw"'. '"json"' requires the 'jsonlite' package.

Value

A character string, parsed R object, or raw vector.


Build a file response

Description

Reads 'path' into memory as raw bytes and returns it as the body. For v0.1 the entire file is held in R memory; sendfile-style zero-copy delivery is planned for v0.2 via 'dr_static()'. Files larger than 50 MB emit a warning; files larger than 500 MB raise an error to prevent accidental out-of-memory loads.

Usage

dr_file(
  path,
  content_type = NULL,
  status = 200L,
  headers = list(),
  download_as = NULL
)

Arguments

path

Path to a regular file readable by the calling process.

content_type

MIME type for the response. 'NULL' (the default) auto-detects from the file extension via a built-in table; unknown extensions become 'application/octet-stream'.

status

Integer HTTP status code, default 200.

headers

Named list of additional response headers.

download_as

If a non-empty string, sets 'Content-Disposition: attachment; filename="..."' so browsers prompt to save under that name.

Value

A response list (see [dr_response()]).

Examples

## Not run: 
dr_file("/tmp/report.pdf")
dr_file("/tmp/report.pdf", download_as = "Q3-report.pdf")

## End(Not run)

Read a request header

Description

Looks up a header by name, case-insensitively.

Usage

dr_header(req, name)

Arguments

req

A 'drogon_request' passed to your route handler.

name

Header name (e.g. '"Content-Type"').

Value

The header value as a single string, or 'NULL' if absent.


Build an HTML response

Description

Sets 'Content-Type: text/html; charset=utf-8'.

Usage

dr_html(body = "", status = 200L, headers = list())

Arguments

body

Response body as a character string or raw vector.

status

Integer HTTP status code, default 200.

headers

Named list of additional response headers. An explicit 'Content-Type' here wins over the default.

Value

A response list (see [dr_response()]).

Examples

dr_html("<h1>hi</h1>")

Build a JSON response

Description

Serialises 'x' with [jsonlite::toJSON()] and sets 'Content-Type: application/json' (unless already set in 'headers').

Usage

dr_json(x, status = 200L, headers = list(), auto_unbox = TRUE)

Arguments

x

R object to serialise.

status

Integer HTTP status code, default 200.

headers

Named list of additional response headers.

auto_unbox

Passed to [jsonlite::toJSON()]; default 'TRUE' so length-1 vectors become JSON scalars.

Value

A response list (see [dr_response()]).

Examples

dr_json(list(ok = TRUE, n = 1L))

Register a custom error handler

Description

Install a function that builds the response when a route handler or middleware throws an R error. The function receives '(req, err)' — the request object and the captured 'condition' — and must return a response (string, [dr_response()], [dr_json()], etc.). It is called on the main R thread, after the handler / middleware chain has already failed; returning normally short-circuits the default 500.

Usage

dr_on_error(app, fn)

Arguments

app

A 'drogon_app' created by [dr_app()].

fn

A function of two arguments, 'function(req, err)'. Pass 'NULL' to clear a previously-registered handler.

Details

If the on-error function itself throws, drogonR logs **both** the original handler error and the on-error error to stderr (via [message()]) and falls back to the default plain-text 500 — the client never sees a hung connection. Only one on-error handler is active per app; calling 'dr_on_error()' again replaces it.

Value

The 'app', invisibly.

Examples

app <- dr_app() |>
  dr_on_error(function(req, err) {
    dr_json(list(error = conditionMessage(err),
                 path  = req$path),
            status = 500L)
  }) |>
  dr_get("/boom", function(req) stop("nope"))

Read query-string parameters

Description

Returns either the named character vector of all query parameters (when 'name = NULL', the default), or the value of a single parameter. Drogon parses and URL-decodes the query string before delivery.

Usage

dr_query(req, name = NULL)

Arguments

req

A 'drogon_request'.

name

Parameter name, or 'NULL' to get the full named vector.

Value

A named character vector when 'name' is 'NULL', otherwise a single string or 'NULL' if the parameter is absent.


Apply a rate limit to one or more routes

Description

Adds a rate-limit rule to 'app'. On each matching request, the Drogon I/O thread checks the rule's bucket *before* dispatching to R; if the bucket is empty the request is rejected with HTTP 429 (Too Many Requests) and a 'Retry-After' header. Multiple 'dr_rate_limit()' calls add independent rules — a request must satisfy *all* of them to pass.

Usage

dr_rate_limit(
  app,
  capacity,
  window = 60,
  type = c("sliding_window", "fixed_window", "token_bucket"),
  scope = c("per_route", "global"),
  routes = NULL
)

Arguments

app

A 'drogon_app' from [dr_app()].

capacity

Maximum number of requests allowed in 'window' seconds (per-bucket; see 'scope'). Integer '>= 1'.

window

Time window for the bucket, in seconds. Default '60'.

type

One of '"sliding_window"' (default — counts requests in the trailing 'window' seconds), '"fixed_window"' (resets at wall-clock boundaries), or '"token_bucket"' (constant refill rate with burst capacity).

scope

'"per_route"' (default) gives every matched route its own bucket. '"global"' makes one bucket shared across all routes matched by this rule.

routes

Either 'NULL' (the default — applies to every registered route) or a character vector of path **prefixes** (e.g. 'c("/api/", "/stream/")'). A route matches if its path starts with any of the given prefixes.

Details

Per-IP limiting is intentionally not provided: do that in a reverse proxy (nginx, Caddy, Cloudflare). This API is for shaping load on specific endpoints from the application side.

Call 'dr_rate_limit()' *after* registering routes (so prefix matches resolve correctly) and *before* [dr_serve()].

Value

The 'app', invisibly.

Examples

## Not run: 
app <- dr_app() |>
  dr_get("/health", function(req) "ok") |>
  dr_get("/api/users", function(req) "users") |>
  # 100 req/min per route under /api/, health excluded
  dr_rate_limit(capacity = 100L, window = 60, routes = "/api/")
dr_serve(app, port = 8080L)

## End(Not run)

Build a redirect response

Description

Sets the 'Location' header and an empty body. Default status is 302 (Found / temporary). Use 'status = 301L' for permanent moves, '303L' after a POST, or '307L'/'308L' to preserve the request method.

Usage

dr_redirect(location, status = 302L, headers = list())

Arguments

location

Target URL (absolute or relative).

status

Integer HTTP status code, default 302.

headers

Named list of additional response headers.

Value

A response list (see [dr_response()]).

Examples

dr_redirect("/login")
dr_redirect("https://example.com", status = 301L)

Build an HTTP response

Description

Constructs the list shape that route handlers must return: a 'status', a 'body', and a list of headers. Returning the result of 'dr_response()' is interchangeable with returning a plain list with the same fields.

Usage

dr_response(body = "", status = 200L, headers = list())

Arguments

body

Response body as a character string or raw vector.

status

Integer HTTP status code, default 200.

headers

Named list of response headers.

Value

A list with elements 'status', 'body', 'headers'.

Examples

dr_response("ok")
dr_response("not found", status = 404L)

Register HTTP route handlers

Description

Register an R function as the handler for a given HTTP method and path. The handler is called for every matching request with a single argument 'req' — a 'drogon_request' object. The handler must return either a single character string (sent as 'text/plain', status 200) or the result of [dr_response()] / [dr_json()].

Usage

dr_get(app, path, handler)

dr_post(app, path, handler)

dr_put(app, path, handler)

dr_delete(app, path, handler)

Arguments

app

A 'drogon_app' created by [dr_app()].

path

Request path, e.g. '"/users"'.

handler

A function of one argument (the request object).

Details

Routes must be registered *before* calling [dr_serve()]. Each call returns the 'app' invisibly so calls can be chained with '|>'.

Value

The 'app' (modified in place), invisibly.

Examples

app <- dr_app()
app <- dr_get(app, "/ping", function(req) "pong")
app <- dr_post(app, "/echo", function(req) req$body)

Register a native C / C++ route handler

Description

Bind a path to a handler implemented in another R package's C / C++ code, looked up via [base::getNativeSymbolInfo()]-style 'R_RegisterCCallable' / 'R_GetCCallable'. The handler runs on Drogon's worker thread pool — **never** on the R main thread — so its hot path bypasses the R dispatcher entirely. Use this for inference-bound APIs (embeddings, classifiers, GGML/llama.cpp wrappers) where SEXP allocation and 'R_tryEval' per request would dominate latency.

Usage

dr_get_cpp(app, path, package, callable)

dr_post_cpp(app, path, package, callable)

dr_put_cpp(app, path, package, callable)

dr_delete_cpp(app, path, package, callable)

Arguments

app

A 'drogon_app' created by [dr_app()].

path

Request path, with the same ':name' / '<name>' / '{name}' placeholder syntaxes as [dr_get()]. Path parameter values are passed positionally to the handler.

package

Name of the backend R package that registered the callable.

callable

Name passed to the backend's 'R_RegisterCCallable("<package>", "<callable>", ...)' call.

Details

The handler signature is 'drogonr_unary_handler_t', defined in '<drogonR.h>' (shipped under 'inst/include/'). Backend packages should 'LinkingTo: drogonR' in their DESCRIPTION, '#include <drogonR.h>' in their C / C++ sources, and call 'R_RegisterCCallable("<package>", "<callable>", ...)' in their 'R_init_<package>' to expose the function.

Lookup is eager: 'dr_get_cpp()' calls 'requireNamespace(package)' immediately and resolves '<callable>' against the loaded DLL. If the package is not installed or the callable is unregistered, the error fires here (during route registration), not silently at request time.

Value

The 'app', invisibly.

Threading and the R API

Native handlers are invoked on Drogon worker threads. They MUST NOT touch any 'SEXP' or call any function in the R API ('Rf_*', 'R_*', 'Rprintf', etc.) — doing so is undefined behaviour. Load models, allocate caches, and read configuration from R BEFORE [dr_serve()] is called; per-request work runs in pure C / C++.

Middleware and error handler

R-side [dr_use()] middleware and the [dr_on_error()] hook are **not** invoked for native routes — they require the request to enter R, which is exactly what this path avoids. Authentication, logging, header injection, etc. must be done either inside the backend handler itself or in front of drogonR (e.g. in a reverse proxy). Per-route [dr_rate_limit()] rules **are** applied (the check runs on the I/O thread before dispatch).

Examples

## Not run: 
# In package ggmlR, R_init_ggmlR() does:
#   R_RegisterCCallable("ggmlR", "embed",
#                       (DL_FUNC) ggmlr_embed);
app <- dr_app() |>
  dr_post_cpp("/embed", package = "ggmlR", callable = "embed")
dr_serve(app, port = 8080L)

## End(Not run)

Register a streaming native (R-bypass) handler

Description

Like [dr_get_cpp()] but for streaming responses (HTTP chunked / SSE / LLM token streams). The backend handler runs on a drogonR worker thread and pushes chunks via the C callbacks declared in '<drogonR.h>' ('drogonr_stream_handler_t').

Usage

dr_get_cpp_stream(
  app,
  path,
  package,
  callable,
  content_type = "text/event-stream"
)

dr_post_cpp_stream(
  app,
  path,
  package,
  callable,
  content_type = "text/event-stream"
)

Arguments

app

A 'drogon_app' from [dr_app()].

path

URL path; same syntax as [dr_get()].

package

R package that exposes the handler.

callable

Symbol name registered via 'R_RegisterCCallable'.

content_type

Default 'Content-Type' for the response. Defaults to '"text/event-stream"'. The backend may override this per call by writing to '*out_content_type'.

Details

The backend is responsible for 'R_RegisterCCallable("<package>", "<callable>", ...)' with a 'drogonr_stream_handler_t' signature. Mismatched signatures are undefined behavior — there is no runtime type check on the function pointer.

Value

'app', invisibly.

Middleware and error handler

R-side [dr_use()] middleware and the [dr_on_error()] hook are **not** invoked for native streaming routes — the request never enters R. Cross-cutting concerns belong in the backend or in a reverse proxy. Per-route [dr_rate_limit()] rules **are** applied on the I/O thread before the worker is dispatched.


Is the drogonR server currently running?

Description

Is the drogonR server currently running?

Usage

dr_running()

Value

'TRUE' if a server is running in this process, 'FALSE' otherwise.


Start the HTTP server

Description

Starts the bundled Drogon HTTP server on the given port and number of I/O threads. The Drogon event loop runs in dedicated C++ threads; incoming requests are dispatched to R handlers on the main R thread via [later::later_fd()].

Usage

dr_serve(
  app,
  port = 8080L,
  threads = 1L,
  workers = 1L,
  on_worker_start = NULL,
  max_queue = 1024L,
  cpp_workers = 4L,
  upload_path = NULL
)

Arguments

app

A 'drogon_app' with at least one registered route.

port

TCP port to bind, integer in '1..65535'. Defaults to 8080.

threads

Number of Drogon I/O threads per worker, integer '>= 1'. Defaults to 1.

workers

Number of OS-level worker processes. '1L' (default) serves in-process. '> 1' spawns workers as fresh 'Rscript' processes and the calling process becomes a thin supervisor. Not supported on Windows.

on_worker_start

Optional 'function()' run once per worker before its Drogon listener starts. Use it to load models or open per-worker resources. Errors abort that worker (exit status 1).

max_queue

Maximum number of pending requests waiting for an R handler before incoming requests are rejected with HTTP 503 (Service Unavailable). Acts as backpressure when handlers are slower than the arrival rate, preventing unbounded memory growth. 503 responses are sent directly from a Drogon I/O thread without touching R, so overload has no R-side cost. Default '1024L'.

cpp_workers

Size of the worker thread pool that runs native (R-bypass) handlers registered via 'dr_*_cpp()' and 'dr_*_cpp_stream()'. Each in-flight cpp request occupies one thread; streaming handlers hold their thread for the full duration of the response. Default '4L'. Increase if you have many concurrent long-running cpp-stream sessions (e.g. LLM token streams). Has no effect on R-side handlers.

upload_path

Directory where Drogon stores uploaded files. Defaults to 'NULL', in which case a fresh subdirectory inside [tempdir()] is created so the package never writes to the user's home filespace or the installation directory. Pass an explicit path to override.

Details

When 'workers > 1', drogonR spawns 'workers' fresh R processes via 'Rscript' (not 'fork()'); each worker runs its own Drogon listener on the same port (Linux/macOS use 'SO_REUSEPORT' for kernel-side load balancing). The calling process is a thin **supervisor** — it does not serve requests itself, only tracks worker pids and reaps them at [dr_stop()]. 'on_worker_start' runs in each worker immediately before its Drogon listener starts, so per-worker state (models, caches) is loaded before the first request lands. Going through 'Rscript'+'exec' (rather than 'parallel::mcparallel()') costs ~200ms of startup per worker but gives each worker a clean R: no inherited sink stack, no inherited 'later' event-loop fds, no half-initialised C++ globals from the supervisor.

If 'on_worker_start' throws in a child, that child exits with status 1 after writing the error to stderr; the supervisor notices it on the next [dr_status()] call and continues with the surviving workers. There is no auto-restart in v0.1.

Value

'NULL', invisibly. Prints a one-line listening message.

Lifetime

Drogon's event loop cannot be restarted in the same R session. After calling [dr_stop()], a new [dr_serve()] in the same process will raise an error — start a fresh R session instead.

Examples

## Not run: 
app <- dr_app() |>
  dr_get("/hello", function(req) "hi")
dr_serve(app, port = 8080L)

# Multi-process, each worker loads its own model copy
dr_serve(app, port = 8080L, workers = 4L,
         on_worker_start = function() {
           model <<- readRDS("model.rds")
         })

## End(Not run)

Mount a directory as static files

Description

Serve every file under 'dir' at URLs starting with 'mount'. The files are streamed by Drogon directly from a C++ I/O thread (R is never invoked), so this path supports 'Range' requests and auto-detects 'Content-Type'. Both 'GET' and 'HEAD' are accepted; missing files return 404, attempted path traversal returns 403.

Usage

dr_static(app, mount, dir)

Arguments

app

A 'drogon_app' created by [dr_app()].

mount

URL prefix to mount under, e.g. '"/assets"'. Must start with '/'. A trailing '/' is stripped.

dir

Local directory to serve from. Must exist at [dr_serve()] time.

Value

The 'app', invisibly.

Path traversal

The handler resolves the requested path against 'dir' and rejects (HTTP 403) any request whose normalised target escapes 'dir' — a '..' segment, an absolute path, or anything else that would otherwise let a remote caller read files outside the mount.

Middleware and error handler

Static files are served entirely from a C++ I/O thread, so R-side [dr_use()] middleware and the [dr_on_error()] hook do **not** apply — they only run for requests that enter R. If you need authentication, custom headers, or per-file logging on assets, put a reverse proxy in front of drogonR or expose the files through a regular [dr_get()] handler instead.

Examples

## Not run: 
app <- dr_app() |>
  dr_static("/assets", "./public") |>
  dr_get("/api/ping", function(req) "pong")
dr_serve(app, port = 8080L)
# GET /assets/logo.png streams ./public/logo.png from C++.

## End(Not run)

Status of forked worker processes

Description

Reports which workers forked by [dr_serve()] are still alive. Polls only when called — there is no background supervisor in v0.1, so dead workers are noticed only here or at [dr_stop()] time. Returns an empty data frame in single-process mode.

Usage

dr_status()

Value

A data frame with columns 'pid' (integer) and 'alive' (logical), one row per tracked worker child.


Stop the HTTP server

Description

Stops the in-process Drogon event loop (when 'workers == 1L') and joins the I/O threads. In supervisor mode ('workers > 1L'), sends 'SIGTERM' to every tracked worker, waits up to ~2s for them to exit, then 'SIGKILL's any survivor. No-op if no server is running and no workers are tracked.

Usage

dr_stop()

Details

Drogon cannot be restarted in the same R session — see [dr_serve()].

Value

'NULL', invisibly.


Open a streaming HTTP response

Description

Return value for a route handler when the response should be streamed (HTTP chunked transfer). Instead of producing one body string, the handler returns a 'drogon_stream' describing how to generate chunks on demand. The dispatcher pumps 'next_chunk()' on the main R thread, one chunk per pump, until it signals 'done'.

Usage

dr_stream(
  next_chunk,
  state = NULL,
  content_type = "text/event-stream",
  headers = list(),
  min_interval = 0
)

Arguments

next_chunk

Function 'function(state, cancelled)' returning 'list(chunk = , state = , done = )'. See above.

state

Initial state passed to the first pump. Anything an R value can hold; opaque to the dispatcher.

content_type

MIME type for the response. Defaults to '"text/event-stream"' since SSE is the most common use case.

headers

Named list of additional response headers to send in the initial chunked-transfer response (status is always 200). 'Content-Type' here overrides the 'content_type' argument.

min_interval

Minimum delay in seconds between consecutive 'next_chunk()' calls. '0' (default) pumps as fast as the event loop allows. Set to '0.1' to throttle to ~10 chunks/sec, etc. Useful for SSE feeds that should be paced rather than bursted. The delay is a floor, not a guarantee — heavy R-side work or other queued callbacks may push the next pump out further.

Details

Each pump receives the current 'state' and a 'cancelled' flag. 'cancelled' is 'TRUE' when the dispatcher has detected that the client connection is gone; the generator will be invoked exactly once with 'cancelled = TRUE' so it can free state, and the stream is then closed regardless of what the call returns. It returns a list with three slots:

* 'chunk' — character(1) bytes to send right now (sent verbatim; format SSE / NDJSON / etc. yourself, or use one of the helpers built on top of 'dr_stream()'). * 'state' — the value passed to the next pump. Pass back the incoming ‘state' unchanged if you don’t need to mutate it. * 'done' — 'TRUE' to close the response after this chunk; 'FALSE' to schedule another pump.

Value

A list of class 'drogon_stream' carrying 'next_chunk', 'state', 'content_type', 'headers', and 'min_interval'. Return it from a route handler; the dispatcher recognises the class and opens an HTTP chunked-transfer response, then pumps 'next_chunk()' on the main R thread until it signals 'done = TRUE'.

Threading

'next_chunk()' always runs on the main R thread. R is single-threaded, so this is the only place it could safely run. Heavy work inside one pump blocks every other request and every other stream until it returns — keep each step short, and split long generation across many pumps.

Examples

## Not run: 
app <- dr_app() |>
  dr_get("/sse", function(req) {
    dr_stream(
      state      = list(i = 0L, n = 5L),
      next_chunk = function(state, cancelled) {
        if (cancelled || state$i >= state$n) {
          return(list(chunk = "", state = state, done = TRUE))
        }
        state$i <- state$i + 1L
        list(chunk = sprintf("data: %d\n\n", state$i),
             state = state, done = FALSE)
      })
  })
dr_serve(app, port = 8080L)
# curl -N http://127.0.0.1:8080/sse

## End(Not run)

Open a Server-Sent-Events streaming response

Description

Convenience wrapper around [dr_stream()] for the common case of an SSE feed where each tick emits one 'data:' field. The generator returns 'data' (a string), 'state', and 'done'; the helper formats the SSE frame, splitting embedded newlines into multiple 'data:' lines per the SSE spec, and adds the headers a typical SSE client expects (no caching, no proxy buffering).

Usage

dr_stream_sse(generator, state = NULL, headers = list(), min_interval = 0)

Arguments

generator

Function 'function(state, cancelled)' returning 'list(data = , state = , done = )'. 'data' may contain newlines; they are split into multiple 'data:' lines automatically. An empty 'data' is allowed (sends a keep-alive frame).

state

Initial state, as in [dr_stream()].

headers

Extra response headers to merge with the SSE defaults ('Content-Type: text/event-stream', 'Cache-Control: no-cache', 'X-Accel-Buffering: no'). User-supplied entries with the same name win.

min_interval

Floor on the delay between consecutive 'generator()' calls, in seconds. See [dr_stream()] for details. Default '0' (no throttling).

Details

For SSE features beyond plain 'data:' ('event:', 'id:', 'retry:'), use [dr_stream()] directly and format the frame yourself.

Value

A 'drogon_stream' value to return from a route handler.

Examples

## Not run: 
app <- dr_app() |>
  dr_get("/sse", function(req) {
    dr_stream_sse(
      state = list(i = 0L, n = 5L),
      generator = function(state, cancelled) {
        if (cancelled || state$i >= state$n) {
          return(list(data = "", state = state, done = TRUE))
        }
        state$i <- state$i + 1L
        list(data = sprintf("tick %d", state$i),
             state = state, done = FALSE)
      })
  })
dr_serve(app, port = 8080L)
# curl -N http://127.0.0.1:8080/sse

## End(Not run)

Build a plain-text response

Description

Sets 'Content-Type: text/plain; charset=utf-8'. The charset is explicit because some intermediaries / older clients otherwise fall back to a non-UTF-8 default and mangle non-ASCII bodies.

Usage

dr_text(body = "", status = 200L, headers = list())

Arguments

body

Response body as a character string or raw vector.

status

Integer HTTP status code, default 200.

headers

Named list of additional response headers. An explicit 'Content-Type' here wins over the default.

Value

A response list (see [dr_response()]).

Examples

dr_text("hello")
dr_text("not found", status = 404L)

Register middleware

Description

Append a middleware function to the app's middleware chain. Middleware runs in registration order before the matched route handler. Each middleware receives '(req, nxt)': call 'nxt()' to pass control to the next link (its return value is the downstream response, which you may return as-is or modify), or return a response of your own to short- circuit the chain. Throwing an error has the same effect as the route handler throwing — the chain stops and a 500 is returned.

Usage

dr_use(app, middleware)

Arguments

app

A 'drogon_app' created by [dr_app()].

middleware

A function of two arguments, 'function(req, nxt)'.

Value

The 'app', invisibly.

Examples

app <- dr_app() |>
  dr_use(function(req, nxt) {
    t0 <- Sys.time()
    res <- nxt()
    message("served ", req$path, " in ",
            format(Sys.time() - t0))
    res
  }) |>
  dr_get("/ping", function(req) "pong")

Run a plumber router under drogonR (drop-in shim)

Description

Translate a [plumber::pr()] router into [dr_app()] routes and start the drogonR server. The intent is a one-line replacement: existing 'plumber::pr_run(pr)' becomes 'drogonR::pr_run(pr)' without further changes, for the subset of plumber that drogonR can faithfully reproduce.

Usage

pr_run(pr, host = "0.0.0.0", port = 8080L, ...)

Arguments

pr

A 'Plumber' router created by [plumber::pr()].

host

Host to bind. Only '"0.0.0.0"', '"127.0.0.1"', '"localhost"', and '"::"' are accepted; anything else triggers a warning and binds to '0.0.0.0' (drogonR always binds to the wildcard).

port

TCP port, integer in '1..65535'.

...

Additional arguments forwarded to [dr_serve()] (e.g. 'threads', 'workers', 'max_queue'). Plumber-specific arguments that have no analogue in drogonR ('docs', 'swagger', 'swaggerCallback', 'quiet') are silently accepted and ignored, so that an existing 'plumber::pr_run(pr, docs = FALSE)' call site keeps working after swapping in the shim.

Value

'NULL', invisibly. Blocks the calling thread until the drogonR server is stopped from another R session via [dr_stop()] (this matches 'plumber::pr_run()' semantics).

Supported

* '@get', '@post', '@put', '@delete' annotations. * Path placeholders '<name>' and '<name:type>'. Recognised types are 'int'/'integer', 'dbl'/'double'/'numeric', 'bool'/'logical'; anything else is left as a character. Coercion runs only on path parameters (plumber does not coerce query / body args at the serializer layer either). * Handler arguments resolved by name from path > query > JSON body, with 'req' injected if the handler takes a 'req' parameter. * Default plumber serialisation: every return value goes through 'jsonlite::toJSON(auto_unbox = FALSE)' – including bare strings, which become JSON arrays – for byte-level parity with 'plumber::pr_run()'. Returning a [dr_response()] / [dr_json()] / etc. opts out and is forwarded as-is.

Not supported

Filters ('@filter'), hooks ('pr_hook()'), mounts ('pr_mount()'), custom parsers/serialisers, OpenAPI assets, websockets, async handlers, and the 'res' (response) parameter of plumber handlers. Each of these triggers an explicit error or per-route warning at 'pr_run()' time so failure is loud, not silent. If you need any of these, use the native [dr_app()] API.