@@ -306,40 +306,48 @@ void webserver::register_resource(const std::string& resource,
306306 register_path (resource, std::move (res));
307307}
308308
309- // TASK-025: lambda registration plumbing.
309+ // TASK-025/TASK-026 : lambda registration plumbing.
310310//
311- // All seven public on_* overloads forward to on_method_, which:
312- // 1. Validates the handler and the path (single_resource constraints).
311+ // All seven public on_* overloads and both public route() overloads
312+ // forward to on_methods_, which:
313+ // 1. Validates the handler, the method set (non-empty, no count_
314+ // sentinel bit), and the path (single_resource constraints).
313315// 2. Looks up any existing entry at the path. If it's a class-based
314316// http_resource, throw -- lambda and class registrations cannot
315- // share a path. If it's an existing lambda_resource shim, merge by
316- // checking the per- method slot is empty (else throw) and writing
317- // the new handler into that slot .
317+ // share a path. If it's an existing lambda_resource shim, check
318+ // that EVERY requested method slot is empty before mutating any
319+ // of them (atomic all-or-nothing); otherwise throw .
318320// 3. If no entry exists, build a fresh lambda_resource shim and
319321// insert it into the same three storage maps used by
320322// register_impl_ (master ordered map; the str fast-path map iff
321323// exact non-parameterized; the regex map iff parameterized).
322- // 4. Invalidate the LRU route cache.
324+ // 4. Write @p handler into each requested method slot.
325+ // 5. Invalidate the LRU route cache.
323326//
324327// The dispatch path in finalize_answer is not modified: it already
325328// looks up via shared_ptr<http_resource>, calls is_allowed(method)
326329// gating the 405 path, then dispatches via the per-method member-
327330// function pointer set in answer_to_connection. The lambda_resource
328331// shim's render_* overrides invoke the stored slot.
329- void webserver::on_method_ (http_method method,
330- const std::string& path,
331- std::function<http_response(const http_request&)> handler) {
332+ void webserver::on_methods_ (method_set methods,
333+ const std::string& path,
334+ std::function<http_response(const http_request&)> handler) {
335+ if (methods.bits == 0u ) {
336+ throw std::invalid_argument (
337+ " route(method_set, ...) requires at least one method bit set" );
338+ }
332339 if (!handler) {
333340 throw std::invalid_argument (
334- " The handler function passed to on_* must be non-empty" );
341+ " The handler function passed to on_*/route must be non-empty" );
335342 }
336343
337344 // Same single-resource constraint as register_path: only "" or "/"
338- // is acceptable, and the matching mode must be exact (which on_* is).
345+ // is acceptable, and the matching mode must be exact (which on_*/
346+ // route are).
339347 if (single_resource && path != " " && path != " /" ) {
340348 throw std::invalid_argument (
341- " When using a single_resource server, on_* requires the "
342- " path to be '' or '/'" );
349+ " When using a single_resource server, on_*/route requires "
350+ " the path to be '' or '/'" );
343351 }
344352
345353 detail::http_endpoint idx (path, /* family=*/ false ,
@@ -360,20 +368,38 @@ void webserver::on_method_(http_method method,
360368 if (!shim) {
361369 throw std::invalid_argument (
362370 " A non-lambda http_resource is already registered at "
363- " this path; on_* cannot share a path with "
371+ " this path; on_*/route cannot share a path with "
364372 " register_path/register_prefix" );
365373 }
366- if (shim->has_slot (method)) {
367- throw std::invalid_argument (
368- " A handler is already registered for this method on "
369- " this path" );
374+ // Atomicity pre-check: every requested slot must be empty
375+ // BEFORE we mutate any of them. Iterates in enum order (get,
376+ // head, post, ...) matching the `Allow:` header serialization.
377+ for (std::uint8_t i = 0 ;
378+ i < static_cast <std::uint8_t >(http_method::count_);
379+ ++i) {
380+ auto m = static_cast <http_method>(i);
381+ if (!methods.contains (m)) continue ;
382+ if (shim->has_slot (m)) {
383+ throw std::invalid_argument (
384+ " A handler is already registered for one of the "
385+ " requested methods on this path" );
386+ }
370387 }
371388 } else {
372389 shim = std::make_shared<detail::lambda_resource>();
373390 fresh = true ;
374391 }
375392
376- shim->set_slot (method, std::move (handler));
393+ // Commit phase: install the handler into every requested slot.
394+ // The shared std::function copies cheaply (type-erased callable),
395+ // so each slot owns its own copy.
396+ for (std::uint8_t i = 0 ;
397+ i < static_cast <std::uint8_t >(http_method::count_);
398+ ++i) {
399+ auto m = static_cast <http_method>(i);
400+ if (!methods.contains (m)) continue ;
401+ shim->set_slot (m, handler);
402+ }
377403
378404 if (fresh) {
379405 impl_->registered_resources .insert ({idx, shim});
@@ -392,42 +418,64 @@ void webserver::on_method_(http_method method,
392418
393419// The seven named forwarders below are the only place that maps the
394420// method name to its http_method enum constant. Each is a thin alias
395- // for on_method_ ; all validation and insertion logic lives there.
421+ // for on_methods_ ; all validation and insertion logic lives there.
396422// on_delete uses http_method::del because `delete` is a C++ keyword;
397423// the wire token is "DELETE" (see http_method::to_string).
398424void webserver::on_get (const std::string& path,
399425 std::function<http_response(const http_request&)> handler) {
400- on_method_ ( http_method::get, path, std::move (handler));
426+ on_methods_ (method_set{}. set ( http_method::get) , path, std::move (handler));
401427}
402428
403429void webserver::on_post (const std::string& path,
404430 std::function<http_response(const http_request&)> handler) {
405- on_method_ ( http_method::post , path, std::move (handler));
431+ on_methods_ (method_set{}. set ( http_method::post ) , path, std::move (handler));
406432}
407433
408434void webserver::on_put (const std::string& path,
409435 std::function<http_response(const http_request&)> handler) {
410- on_method_ ( http_method::put, path, std::move (handler));
436+ on_methods_ (method_set{}. set ( http_method::put) , path, std::move (handler));
411437}
412438
413439void webserver::on_delete (const std::string& path,
414440 std::function<http_response(const http_request&)> handler) {
415- on_method_ ( http_method::del, path, std::move (handler));
441+ on_methods_ (method_set{}. set ( http_method::del) , path, std::move (handler));
416442}
417443
418444void webserver::on_patch (const std::string& path,
419445 std::function<http_response(const http_request&)> handler) {
420- on_method_ ( http_method::patch, path, std::move (handler));
446+ on_methods_ (method_set{}. set ( http_method::patch) , path, std::move (handler));
421447}
422448
423449void webserver::on_options (const std::string& path,
424450 std::function<http_response(const http_request&)> handler) {
425- on_method_ ( http_method::options, path, std::move (handler));
451+ on_methods_ (method_set{}. set ( http_method::options) , path, std::move (handler));
426452}
427453
428454void webserver::on_head (const std::string& path,
429455 std::function<http_response(const http_request&)> handler) {
430- on_method_ (http_method::head, path, std::move (handler));
456+ on_methods_ (method_set{}.set (http_method::head), path, std::move (handler));
457+ }
458+
459+ // TASK-026: generic table-driven entry points. The single-method form
460+ // rejects http_method::count_ explicitly because the public route()
461+ // overload accepts a runtime value (and so the sentinel is reachable);
462+ // the on_* forwarders never pass count_, so the on_methods_ helper
463+ // itself does not guard against it.
464+ void webserver::route (http_method m,
465+ const std::string& path,
466+ std::function<http_response(const http_request&)> handler) {
467+ if (m == http_method::count_) {
468+ throw std::invalid_argument (
469+ " http_method::count_ is a sentinel and may not be "
470+ " registered as a route" );
471+ }
472+ on_methods_ (method_set{}.set (m), path, std::move (handler));
473+ }
474+
475+ void webserver::route (method_set methods,
476+ const std::string& path,
477+ std::function<http_response(const http_request&)> handler) {
478+ on_methods_ (methods, path, std::move (handler));
431479}
432480
433481#ifdef HAVE_WEBSOCKET
0 commit comments