{"id":21068944,"url":"https://github.com/kamchatka-volcano/whaleroute","last_synced_at":"2026-02-22T19:31:12.014Z","repository":{"id":44816554,"uuid":"411444804","full_name":"kamchatka-volcano/whaleroute","owner":"kamchatka-volcano","description":"C++17 request routing library","archived":false,"fork":false,"pushed_at":"2025-01-29T18:40:47.000Z","size":195,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-09T04:40:02.648Z","etag":null,"topics":["cpp17","header-only","request-router","request-routing"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"ms-pl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kamchatka-volcano.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-09-28T21:34:44.000Z","updated_at":"2024-06-11T19:34:37.000Z","dependencies_parsed_at":"2024-01-03T19:30:06.170Z","dependency_job_id":"33a66ec2-1aa2-4e74-9760-a1922612e271","html_url":"https://github.com/kamchatka-volcano/whaleroute","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/kamchatka-volcano/whaleroute","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fwhaleroute","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fwhaleroute/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fwhaleroute/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fwhaleroute/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kamchatka-volcano","download_url":"https://codeload.github.com/kamchatka-volcano/whaleroute/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fwhaleroute/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29724346,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T19:15:09.475Z","status":"ssl_error","status_checked_at":"2026-02-22T19:15:09.045Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp17","header-only","request-router","request-routing"],"created_at":"2024-11-19T18:29:38.336Z","updated_at":"2026-02-22T19:31:11.965Z","avatar_url":"https://github.com/kamchatka-volcano.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg height=\"192\" src=\"doc/logo.png\"/\u003e  \n\u003c/p\u003e\n\n[![build \u0026 test (clang, gcc, MSVC)](https://github.com/kamchatka-volcano/whaleroute/actions/workflows/build_and_test.yml/badge.svg?branch=master)](https://github.com/kamchatka-volcano/whaleroute/actions/workflows/build_and_test.yml)\n\n**whaleroute** - is a C++17 header-only library for request routing. It is designed to bind handlers to HTTP requests,\nbut it can also be easily used with other protocols since the library is implemented as a generic class template.  \nIf your incoming data processing function has a signature like `void(const Request\u0026, Response\u0026)`\nor `Response(const Request\u0026)` and you need to perform\ndifferent actions based on a string value from the request object, **whaleroute** can be of great help.\n\n* [Usage](#implementing-the-router)\n  * [Implementing the router](#implementing-the-router)\n  * [Registering the response converter](#registering-the-response-converter)\n  * [Matching multiple routes](#matching-multiple-routes)\n  * [Registering the route context](#registering-the-route-context)\n  * [Registering route matchers](#registering-route-matchers)\n  * [Using regular expressions](#using-regular-expressions)\n  * [Trailing slash matching](#trailing-slash-matching)\n  * [Processing unmatched requests](#processing-unmatched-requests)\n  * [Using RequestProcessorQueue](#using-requestprocessorqueue)\n* [Installation](#installation)\n* [Running tests](#running-tests)\n* [License](#license)\n\n#### Implementing the router  \nLet's say that our Request and Response classes look like this:\n```c++\nstruct Request{\n    std::string uri;\n    enum class Method{\n        GET,\n        POST\n    } method;\n};\nstruct Response{\n    bool isSent = false;\n    void send(const std::string\u0026 data)\n    {\n        isSent = true;\n        std::cout \u003c\u003c data;\n    }\n};\n```\n\nTo create the simplest router we must use Request and Response types as template arguments and implement two virtual functions:\n* `virtual std::string getRequestPath(const TRequest\u0026) = 0;`\n* `virtual void processUnmatchedRequest(const TRequest\u0026, TResponse\u0026) = 0;`\n\n```c++\n#include \u003cwhaleroute/requestrouter.h\u003e\n\nclass Router : public whaleroute::RequestRouter\u003cRequest, Response\u003e{\n    std::string getRequestPath(const Request\u0026 request) final\n    {\n        return request.uri;\n    }\n    \n    void processUnmatchedRequest(const Request\u0026, Response\u0026 response) final\n    {\n        response.send(\"HTTP/1.1 404 Not Found\\r\\n\\r\\n\");\n    }\n};\n\n```\n\nNow our router can be used like this:\n\n```c++\n    auto router = Router{};\n    router.route(\"/\").process([](const Request\u0026 request, Response\u0026 response){\n        response.send(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n    });\n    //...\n    router.process(request, response);\n```\n\nThe `process` method accepts any callable that can be invoked with the registered request and response objects.\nTherefore, in addition to lambdas, it is possible to use free functions and function objects. It is also possible to\nspecify the type of the invocable class, allowing **whaleroute** to instantiate and take ownership of the request\nprocessor\nobject.\n\n```c++\nstruct Responder{\n    void operator()(const Request\u0026, Response\u0026 response)\n    {\n        response.send(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n    }\n};\n\n    auto router = Router{};\n    router.route(\"/\").process\u003cResponder\u003e();\n    router.process(request, response);\n```\n\n#### Registering the response converter\n\nTo simplify the setup of routes, it is possible to set the response value directly instead of registering a\nrequest processor object. To achieve this, it is necessary to register a response converter that sets the passed value\nto the response object. To do this, provide a callable structure with the following\nsignature: `void(Response\u0026 response, const TValue\u0026 value)`, and pass it as the 3rd template argument\nto `whaleroute::RequestRouter`.\n\n```c++\n#include \u003cwhaleroute/requestrouter.h\u003e\n\nstruct ResponseSetter{\n    void operator()(Response\u0026 response, const std::string\u0026 value)\n    {\n        response.send(value);\n    }\n};\n\nclass Router : public whaleroute::RequestRouter\u003cRequest, Response, ResponseSetter\u003e{\n    std::string getRequestPath(const TRequest\u0026 request) final\n    {\n        return request.uri;\n    }\n    \n    void processUnmatchedRequest(const Request\u0026, Response\u0026 response)\n    {\n        response.send(\"HTTP/1.1 418 I'm a teapot\\r\\n\\r\\n\");\n    }\n};\n```\n\nNow routes have the `set` method available:\n\n```c++\n    auto router = Router{};\n    router.route(\"/\").set(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n    //...\n    router.process(request, response);\n```\n\nWhen a response converter is set, it is also possible to use request processors that return response values instead of\ntaking a reference to the response object.\n\n```c++\n    struct Responder{\n        std::string operator()(const Request\u0026)\n        {\n            return \"HTTP/1.1 200 OK\\r\\n\\r\\n\";\n        }\n    };\n \n    auto router = Router{}; \n    router.route(\"/\").process\u003cResponder\u003e();\n    router.process(request, response);\n```\n\n#### Matching multiple routes\n\nBy default, route processing stops at the first registered route that matches the request's path and any specified\nmatchers. This behavior can be controlled by using the router's virtual function\n\n* `isRouteProcessingFinished(const TRequest\u0026, TResponse\u0026 response)`.\n\nIt's invoked for each matched route after the registered handler, and if the result is false, the processing continues\nand tries to match the subsequent routes.  \nA practical example is to stop route processing only after the response has been sent. This allows you to register\nrequest processors that perform some preparation work before generating the response.\n\n```c++\n\nclass Router : public whaleroute::RequestRouter\u003cRequest, Response\u003e{\n    std::string getRequestPath(const Request\u0026 request) final\n    {\n        return request.uri;\n    }\n    \n    void processUnmatchedRequest(const Request\u0026, Response\u0026 response) final\n    {\n        response.send(\"HTTP/1.1 404 Not Found\\r\\n\\r\\n\");\n    }\n    bool isRouteProcessingFinished(const Request\u0026, Response\u0026 response) final\n    {\n        return response.isSent;\n    }\n    \n};\n\n    auto router = Router{};\n    router.route(whaleroute::rx{\".*\"}, Request::Method::GET).process(const Request\u0026 request, Response\u0026)){\n      log(request);\n    });\n    router.route(\"/\").process([](const Request\u0026, Response\u0026 response)){\n      response.send(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n    });\n```\n\n#### Registering the route context\n\nTo make the matching of multiple routes more useful, it is possible to share data between route processors. This can be\nachieved by registering the route context class as the last template parameter of the whaleroute::RequestRouter\ntemplate. Once registered, it is then possible to take a reference to the context object in the last request processor\nparameter.\n\n```c++\n#include \u003cwhaleroute/requestrouter.h\u003e\n\nstruct Context{\n    bool isAuthorized = false;\n}; \n\nclass Router : public whaleroute::RequestRouter\u003cRequest, Response, whaleroute::_, Context\u003e{\n    //...\n};\n\nvoid authorize(const Request\u0026 request, Response\u0026, Context\u0026 ctx)\n{\n    ctx.isAuthorized = true;\n}\n\n    router.route(whaleroute::rx{\".*\"}, Request::Method::POST).process(authorize);\n    router.route(\"/\").process([](const Request\u0026, Response\u0026 response, const Context\u0026 ctx)){\n      if (ctx.isAuthorized)  \n          response.send(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n      else\n          response.send(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n    });\n\n```\n\n*Notice how it's possible to skip using the response converter in this example by passing the empty type `whaleroute::_`\nin\nits place.*\n\n#### Registering route matchers\n\nBy default, routes are matched based on request paths. To include other matching attributes, you can register them by\nproviding a specialization of the `whaleroute::config::RouteMatcher` class template. The `operator()` function within\nthe specialization should take a matcher's value and the request object, and return a boolean result of comparing\nthe matcher with some property of the request object.\n\n```c++\n#include \u003cwhaleroute/routematcher.h\u003e\n\ntemplate\u003c\u003e\nstruct RouteMatcher\u003cRequest::Method\u003e {\n    bool operator()(Request::Method value, const Request\u0026 request) const\n    {\n        return value == request.method;\n    }\n};\n}\n\n```\n\nNow `Request::Method` can be specified in routes:\n\n```c++\n    auto router = Router{};\n    router.route(\"/\", Request::Method::GET).process([](const Request\u0026 request, Response\u0026 response)){\n        response.send(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n    }); \n```\n\nWhen router has a registered context, it must be present in the route matcher specialization:\n\n```c++\ntemplate\u003c\u003e\nstruct RouteMatcher\u003cRequest::Method, Context\u003e {\n    bool operator()(Request::Method value, const Request\u0026 request, const Context\u0026 ctx) const\n    {\n        return value == request.method;\n    }\n};\n```\n\n#### Using regular expressions\n\nThe `route` method of the Router can accept a regular expression instead of a string to specify the path of the route:\n\n```c++\nrouter.route(whaleroute::rx{\"/.*\"}, Request::Method::GET).set(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n```\n\nCurrently, the regular expressions use the standard C++ library with ECMAScript grammar.\n\nWhen using regular expressions, request processors can accept additional parameters to capture the values of expression\ncapturing groups.\n\n```c++\nvoid showPage(int pageNumber, const Request\u0026, Response\u0026 response)\n{\n    response.send(\"page\" + std::to_string(pageNumber));\n}\nrouter.route(whaleroute::rx{\"/page/(\\\\d+)\"}, Request::Method::GET).process(showPage);\n```\n\nThe conversion of strings from the capturing groups to the parameters of the request processor is performed using the\nstandard `std::stringstream` stream. If the conversion is not possible, a runtime error will be raised. To support the\nconversion of user-defined types, you can use the specialization of `whaleroute::config::StringConverter`.\n\n```c++\nstruct PageNumber{\n    int value;\n};\n\ntemplate\u003c\u003e\nstruct whaleroute::config::StringConverter\u003cPageNumber\u003e {\n    static std::optional\u003cPageNumber\u003e fromString(const std::string\u0026 data)\n    {\n        return PageNumber{std::stoi(data)};\n    }\n};\n\nvoid showPage(PageNumber pageNumber, const Request\u0026, Response\u0026 response)\n{\n    response.send(\"page\" + std::to_string(pageNumber.value));\n}\nrouter.route(whaleroute::rx{\"/page/(\\\\d+)\"}, Request::Method::GET).process(showPage);\n```\n\nWhen the regular expression of a route is set dynamically, you may need to capture an arbitrary number of parameters. In\nsuch cases, you can use the `whaleroute::RouteParameters\u003c\u003e` structure, which stores the values of capturing groups in a\nvector of strings.\n\n```c++\nvoid showBook(const RouteParameters\u003c\u003e\u0026 bookIds, const Request\u0026, Response\u0026 response)\n{\n    if (bookIds.value.size() == 1)\n        response.send(\"book\" + std::to_string(bookIds.value.at(0)));\n    if (bookIds.value.size() == 2)\n        response.send(\"book\" + std::to_string(bookIds.value.at(0)) + std::to_string(bookIds.value.at(1)));\n}\nrouter.route(whaleroute::rx{\"/book/(\\\\d+)/(\\\\d+)\"}, Request::Method::GET).process(showBook);\nrouter.route(whaleroute::rx{\"/book/(\\\\d+)\"}, Request::Method::GET).process(showBook);\n```\n\nIf capturing the string array is more suitable for your request processor, you can use `RouteParameters` with a specific\nnumber of capturing groups that must be present in the regular expression:\n\n```c++\nvoid showPage(const RouteParameters\u003c1\u003e\u0026 pageNumber, const Request\u0026, Response\u0026 response)\n{\n    response.send(\"page\" + pageNumber.value().at(0));\n}\nrouter.route(whaleroute::rx{\"/page/(\\\\d+)\"}, Request::Method::GET).process(showPage);\n```\n\n#### Trailing slash matching\n\nBy default, **whaleroute** treats trailing slashes in requests and route paths as optional. For example, `/path`\nand `/path/` are considered equal.\n\nThis behavior can be changed by using the `setTrailingSlashMode` method of `whaleroute::RequestRouter` and providing the\nvalue `whaleroute::TrailingSlashMode::Strict`.\n\n#### Processing unmatched requests\n\nUsing the `route` method without arguments registers a processor for requests that do not match any existing routes.\nThis is an alternative to using the `processUnmatchedRequest` virtual method, which won't be called if you use `route()`\ninstead.\n\n```c++\n    router.route(\"/\", Request::Method::GET).set(\"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n    router.route().set(\"HTTP/1.1 418 I'm a teapot\\r\\n\\r\\n\");\n```\n\n#### Using RequestProcessorQueue\n\nIf you check the implementation of the `process` method in `whaleroute::RequestRouter`, you'll see that it simply\ncreates a `whale::RequestProcessorQueue` object and forwards the request processing by calling its `launch` method:\n\n```c++\n    auto queue = makeRequestProcessorQueue(request, response);\n    queue.launch();\n```\n\n`whaleroute::RequestProcessorQueue` is a sequence of all matched route processors that can be launched and stopped by\ncalling its `launch` and `stop` methods. It's available in the public interface, allowing you to create and\nuse `RequestProcessorQueue` directly without using the `RequestRouter::process` method. This can be especially useful in\nan asynchronous environment, where route processing can be delayed by stopping the queue and resumed in the request\nhandler's callback using a captured copy of the queue.\n\nOtherwise, you can disregard this information and simply use the `RequestRouter::process` method.\n\n### Installation\n\nDownload and link the library from your project's CMakeLists.txt:\n\n```\ninclude(FetchContent)\n\nFetchContent_Declare(whaleroute\n    GIT_REPOSITORY \"https://github.com/kamchatka-volcano/whaleroute.git\"\n    GIT_TAG \"origin/master\"\n)\n#uncomment if you need to install whaleroot with your target\n#set(INSTALL_WHALEROOT ON)\nFetchContent_MakeAvailable(whaleroute)\n\nadd_executable(${PROJECT_NAME})\ntarget_link_libraries(${PROJECT_NAME} PRIVATE whaleroute::whaleroute)\n```\n\nTo install the library system-wide, use the following commands:\n```\ngit clone https://github.com/kamchatka-volcano/whaleroute.git\ncd whaleroute\ncmake -S . -B build\ncmake --build build\ncmake --install build\n```\n\nAfter installation, you can use the find_package() command to make the installed library available inside your project:\n```\nfind_package(whaleroute 1.0.0 REQUIRED)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE whaleroute::whaleroute)\n```\n\n### Running tests\n```\ncd whaleroute\ncmake -S . -B build -DENABLE_TESTS=ON\ncmake --build build \ncd build/tests \u0026\u0026 ctest\n```\n\n### License\n**whaleroute** is licensed under the [MS-PL license](/LICENSE.md)  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Fwhaleroute","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkamchatka-volcano%2Fwhaleroute","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Fwhaleroute/lists"}