Skip to content

feat: multiple URIs per handler (setUri/addUri/clearUris) - #495

Closed
mathieucarbou wants to merge 1 commit into
mainfrom
feat/routing_table_v2
Closed

mathieucarbou wants to merge 1 commit into
mainfrom
feat/routing_table_v2

Conversation

@mathieucarbou

@mathieucarbou mathieucarbou commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Summary

A handler can now serve multiple URIs and accept multiple HTTP methods by holding a list of matchers instead of a single one.

Method Effect
handler.setUri(matcher) Replace the list with one matcher (backward-compatible)
handler.addUri(matcher) Append an additional URI matcher
handler.clearUris() Remove all URI matchers

setMethod() accepts a composite mask — one method mask applies to all URIs of a handler:

handler.setMethod(HTTP_GET | HTTP_POST);  // accepts both GET and POST on all URIs

When different methods need different callbacks (e.g. POST with onBody/onUpload), use separate handler objects.

Applies to both AsyncCallbackWebHandler and AsyncCallbackJsonWebHandler.

Motivation

In the #468 discussion, @inF1704 pointed out that URI matching lives inside handler objects (AsyncCallbackWebHandler carries _uri and _method), which prevents one handler from serving multiple routes.

Instead of introducing a server-side routing table (addRoute()), this PR moves URI matching to a list inside the handler. The handler declares what URIs it serves — no parallel routing data structure in the server, no split-brain matching logic.

Usage

One Handler, Multiple URIs

static AsyncWebServer server(80);

// One handler object — lives in static RAM
static AsyncCallbackWebHandler greetHandler;

void setup() {
  greetHandler.onRequest([](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "Hello from " + request->url());
  });
  greetHandler.setMethod(HTTP_GET | HTTP_POST);

  // Same handler object serving three different URIs:
  greetHandler.setUri(AsyncURIMatcher::exact("/hello"));
  greetHandler.addUri(AsyncURIMatcher::exact("/hi"));
  greetHandler.addUri(AsyncURIMatcher::prefix("/greet/"));

  // Non-owning registration (by reference) — handler lives in static RAM:
  server.addHandler(greetHandler);

  server.begin();
}

Multiple URIs × Multiple Methods

Combine both — one handler, several URIs, accepting several HTTP methods:

static AsyncCallbackWebHandler apiHandler;
apiHandler.setMethod(HTTP_GET | HTTP_POST | HTTP_PUT);
apiHandler.setUri(AsyncURIMatcher::exact("/api/data"));
apiHandler.addUri(AsyncURIMatcher::exact("/api/status"));
apiHandler.onRequest([](AsyncWebServerRequest *request) {
  request->send(200, "application/json", "{\"ok\":true}");
});
server.addHandler(apiHandler);
// Serves: GET/POST/PUT /api/data  and  GET/POST/PUT /api/status

Different Methods Need Different Callbacks → Different Handlers

When methods need different callbacks (e.g. POST with onBody/onUpload), use separate handler objects:

static AsyncCallbackWebHandler getHandler;
getHandler.setMethod(HTTP_GET);
getHandler.setUri(AsyncURIMatcher::exact("/data"));
getHandler.onRequest([](AsyncWebServerRequest *req) { req->send(200, "text/plain", "GET"); });
server.addHandler(getHandler);

static AsyncCallbackWebHandler postHandler;
postHandler.setMethod(HTTP_POST);
postHandler.setUri(AsyncURIMatcher::exact("/data"));
postHandler.onRequest([](AsyncWebServerRequest *req) { req->send(200, "text/plain", "POST"); });
postHandler.onBody([](AsyncWebServerRequest *req, uint8_t *data, size_t len, size_t index, size_t total) {
  // handle request body
});
server.addHandler(postHandler);

PSRAM Handler on Multiple URIs (ESP32)

#include <esp_heap_caps.h>

void *mem = heap_caps_malloc(sizeof(AsyncCallbackWebHandler), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
auto *handler = new (mem) AsyncCallbackWebHandler();
handler->onRequest([](AsyncWebServerRequest *req) { req->send(200, "application/json", "{\"ok\":true}"); });
handler->setMethod(HTTP_GET);

// Same PSRAM handler on two URIs — non-owning (borrow by reference):
handler->setUri(AsyncURIMatcher::prefix("/api/"));
handler->addUri(AsyncURIMatcher::exact("/status"));
server.addHandler(*handler);

Backward Compatibility

Fully backward compatible. Existing code using server.on(), addHandler(), serveStatic(), and single-URI handlers (setUri()) works identically — no migration required.

setUri() replaces the list with one matcher, so existing code that calls setUri() once behaves exactly as before. AsyncCallbackJsonWebHandler's constructor still takes a single AsyncURIMatcher and internally calls setUri().

Changes

File Change
src/WebHandlerImpl.h AsyncCallbackWebHandler::_uri → std::list<AsyncURIMatcher> _uris; new addUri() / clearUris(); setMethod() doc notes composite support
src/WebHandlers.cpp setUri() clears+pushes; canHandle() iterates _uris
src/AsyncJson.h AsyncCallbackJsonWebHandler::_uri → _uris; inline setUri()/addUri()/clearUris(); setMethod() doc notes composite support
src/AsyncJson.cpp Constructor pushes URI into _uris; canHandle() iterates _uris
examples/arduino/MultiRoute/MultiRoute.ino New example: multiple URIs × composite methods, separate handlers for callbacks
docs/routing.md New "Multiple URIs per Handler" section
docs/configuration.md New "Multiple URIs per Handler" section
docs/concepts.md Handler concept updated
platformio.ini Add MultiRoute example

Example

See MultiRoute — one handler serving multiple URIs with HTTP_GET | HTTP_POST, a PSRAM handler on multiple URIs, and a separate handler for POST with onBody.

Documentation

@mathieucarbou
mathieucarbou marked this pull request as draft October 12, 2026 16:29
@mathieucarbou mathieucarbou changed the title feat: Phase 2 — route table, one handler serving multiple routes feat: multiple URIs per handler + non-owning registration & nullable catch-all Oct 15, 2026
A handler can now serve multiple URIs by holding a list of matchers
instead of a single one:
- setUri() replaces the list with one matcher (backward-compatible)
- addUri() appends an additional URI matcher
- clearUris() removes all matchers

One HTTP method mask (setMethod) applies to all URIs of a handler.
Use separate handlers when different URIs need different methods
(e.g. a GET handler and a POST handler with onBody/onUpload callbacks).

Applies to both AsyncCallbackWebHandler and AsyncCallbackJsonWebHandler.

- New MultiRoute example: one handler serving multiple URIs in static
  RAM, PSRAM handler on multiple URIs, separate handlers per method
- Updated docs: routing.md (Multiple URIs per Handler section),
  configuration.md, concepts.md

Fully backward compatible: existing code using on(), addHandler(),
serveStatic(), and single-URI handlers works identically.

Motivation from #468 discussion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mathieucarbou mathieucarbou changed the title feat: multiple URIs per handler + non-owning registration & nullable catch-all feat: multiple URIs per handler (setUri/addUri/clearUris) Oct 15, 2026
@mathieucarbou
mathieucarbou deleted the feat/routing_table_v2 branch October 15, 2026 13:09
@mathieucarbou

Copy link
Copy Markdown
Member Author

Superseded by #496 - branch renamed to feat/routing_table (removed _v2 suffix)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant