Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: let the Request& know which route matched #2101

Open
betsch85 opened this issue Mar 12, 2025 · 12 comments · May be fixed by #2104
Open

Feature request: let the Request& know which route matched #2101

betsch85 opened this issue Mar 12, 2025 · 12 comments · May be fixed by #2104

Comments

@betsch85
Copy link

In the handler functions I'd like to know which route matched:

server.Get("/user/:id", [](const httplib::Request &req, httplib::Response &res) 
{
    std::cout << req.matched_route << std::endl; // /user/:id
});

For the example above it doesn't make sense because the handler specifically gets added to that route, but if you re-use the handler then it's unclear.
It would also come in handy for set_pre_routing_handler. I only know the req.path and would have to write something myself to find out which route matched while the internals of the library (probably) just know which route matched.

Background:
I've wrapped the server.(Get|Post|...) methods and gave it an additional parameter, needed_permissions which is a std::vector<std::string>

void HTTPServer::AddRoute(const std::string &method, const std::string &route, httplib::Server::Handler handler, StringList needed_permissions = {})
{
    // add route
    // ...

    // map "route -> needed_permissions"
    // ...
}

then:

server.set_pre_routing_handler([](const httplib::Request& req, httplib::Response& res)
{
    // look up needed_permissions for req.matched_route
    // ...

    // respond with "401 Unauthorized" if there's no auth header or "403 Forbidden" if the permissions of the requester don't fulfill needed_permissions 
    // ...
    // return httplib::Server::HandlerResponse::Handled; 

    // otherwise
    return httplib::Server::HandlerResponse::Unhandled;
});
@falbrechtskirchinger
Copy link
Contributor

It would also come in handy for set_pre_routing_handler. I only know the req.path and would have to write something myself to find out which route matched while the internals of the library (probably) just know which route matched.

I assume you mean post-routing handler?

I second the request to store the route pattern in the matcher – a prerequisite to making this work. But please don't add another std::string to the Request object. const char * works and saves ~24 bytes (depending on the std::string implementation).

@falbrechtskirchinger
Copy link
Contributor

Alright, it's easy enough to implement, and I know I'll need it for removable endpoints in the WebSockets code.

So here it is: #2104

@zon7
Copy link

zon7 commented Mar 12, 2025

Thanks! This will solve me some headaches.
Btw, is Websocket support in the roadmap?

@betsch85
Copy link
Author

Alright, it's easy enough to implement, and I know I'll need it for removable endpoints in the WebSockets code.

So here it is: #2104

Thanks!

And no, I meant pre-routing, since I want to check required permissions for routes, it'd be too late for that in the post-routing handler

@falbrechtskirchinger
Copy link
Contributor

And no, I meant pre-routing, since I want to check required permissions for routes, it'd be too late for that in the post-routing handler

You can still reject requests in post-routing; handlers are invoked after. In pre-routing, we don't know which handler we're going to invoke … because that's decided during routing.

@falbrechtskirchinger
Copy link
Contributor

Thanks! This will solve me some headaches. Btw, is Websocket support in the roadmap?

@zon7 Not officially. I have a (nearly) fully working and compliant implementation of the client and server (the SSL client is the last to-do item, and it's very minor).

There are a few PRs that need to be merged first; after that, I hope to convince @yhirose to host (but not maintain) the WebSocket implementation in this repo. I'll ping you when it's time, and you can add your support if you're interested. :-)

@betsch85
Copy link
Author

And no, I meant pre-routing, since I want to check required permissions for routes, it'd be too late for that in the post-routing handler

You can still reject requests in post-routing; handlers are invoked after. In pre-routing, we don't know which handler we're going to invoke … because that's decided during routing.

Ah, ok then. It's post-routing and not post-request after all so it makes sense. The example in README.md returns from the pre-routing handler but doesn't return anything from the post-routing handler so I assumed that's called after the routing AND request are done

@falbrechtskirchinger
Copy link
Contributor

falbrechtskirchinger commented Mar 12, 2025

And no, I meant pre-routing, since I want to check required permissions for routes, it'd be too late for that in the post-routing handler

You can still reject requests in post-routing; handlers are invoked after. In pre-routing, we don't know which handler we're going to invoke … because that's decided during routing.

Ah, ok then. It's post-routing and not post-request after all so it makes sense. The example in README.md returns from the pre-routing handler but doesn't return anything from the post-routing handler so I assumed that's called after the routing AND request are done

Nope, sorry, I made a mistake. This needs some more thought to fully address your scenario. @yhirose What use case did you have in mind for the post-routing handler? Why is that not invoked before the handlers?

Edit: Would this make sense? (in Server::dispatch_request())

    if (matcher->match(req)) {
      req.matched_route = matcher->pattern().c_str();
      if(pre_handler_ && pre_handler_(req, res) == HandlerResponse::Handled)
        return true;
      handler(req, res);
      return true;
    }

@betsch85
Copy link
Author

Edit: Would this make sense? (in Server::dispatch_request())

if (matcher->match(req)) {
  req.matched_route = matcher->pattern().c_str();
  if(pre_handler_ && pre_handler_(req, res) == HandlerResponse::Handled)
    return true;
  handler(req, res);
  return true;
}

I hope this questions wasn't addressed to me because I don't know :)

While having the matched_route in the pre-routing handler (or as you suggested in the post-routing handler) would be the ideal solution for me (and probably others) I could also live with it being only available in the handlers. I'm calling a function at the start of every handler anyway, so I could check for permissions in there

@falbrechtskirchinger
Copy link
Contributor

Edit: Would this make sense? (in Server::dispatch_request())

if (matcher->match(req)) {
  req.matched_route = matcher->pattern().c_str();
  if(pre_handler_ && pre_handler_(req, res) == HandlerResponse::Handled)
    return true;
  handler(req, res);
  return true;
}

I hope this questions wasn't addressed to me because I don't know :)

I was partly thinking out loud and partly asking @yhirose. :-)

While having the matched_route in the pre-routing handler (or as you suggested in the post-routing handler) would be the ideal solution for me (and probably others) I could also live with it being only available in the handlers. I'm calling a function at the start of every handler anyway, so I could check for permissions in there

I'm thinking about adding set_pre_request_handler(). The pre-request handler would run immediately before the main handler and can return a HandlerResponse to control whether we call the main handler at all. I'm open to alternative names, but pre-request handler makes sense to me. (Grok guessed correctly that the post-routing handler runs after the main handler, so I'm clearly out of my element concerning HTTP library APIs. ;-))

@yhirose
Copy link
Owner

yhirose commented Mar 17, 2025

@betsch85 sorry that I wasn't able to reply sooner... Could you please explain a bit more about req.matched_route and why req.path in the pre routing handler isn't sufficient? Thanks!

@betsch85
Copy link
Author

@betsch85 sorry that I wasn't able to reply sooner... Could you please explain a bit more about req.matched_route and why req.path in the pre routing handler isn't sufficient? Thanks!

Becuase I want to know which route matched, not what the user typed in the browser

If I have a route, lets say "/whatever/:action/with/:id", I want to be able to specify somewhere that for this specific route (not path) , a user needs the "whatever_with"-permission, and for that I would need req.matched_route (which would be "/whatever/:action/with/:id"). req.path would just contain what the user actually sent to the server, e.g. /whatever/list/with/5.

I'd have to split the path and check the route myself which makes everything unnecessarily more complicated, especially if a static route and a route with a parameter overlap, e.g. /whatever/:action/with/:id and /whatever/try/with/0

In the pre-routing handler I'd like to do:

if (user_permissions.contains(needed_permissions[req.matched_route]) {
    return httplib::Server::HandlerResponse::Unhandled;
} else {
    res.status = 401;
    return httplib::Server::HandlerResponse::Handled;
}

instead of:

auto matched_route = find_out_what_the_route_probably_was(req.path);
if (user_permissions.contains(needed_permissions[matched_route]) {
    return httplib::Server::HandlerResponse::Unhandled;
} else {
    res.status = 401;
    return httplib::Server::HandlerResponse::Handled;
}

I hope that makes sense

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

Successfully merging a pull request may close this issue.

4 participants