actix-web icon indicating copy to clipboard operation
actix-web copied to clipboard

multiple handler routing / "Next" responder

Open ramiroaisen opened this issue 4 years ago • 8 comments

It's there a way to pass a request to the next handler? Something like

async fn handler() -> impl Responder {
  if some_condition() {
    Response::Ok()
  } else {
    Next
  }
}

ramiroaisen avatar Mar 19 '20 02:03 ramiroaisen

What would the “next handler” mean exactly?

robjtede avatar Mar 19 '20 14:03 robjtede

What would the “next handler” mean exactly?

It would mean that the request continues the handling process as if it did not match the handler something like the (req, res, next <--) function in express (nodejs) if you know waht i mean.

ramiroaisen avatar Apr 02 '20 00:04 ramiroaisen

Alright, just checking there was a distinction here against the other semantic in Node land for next (which, in my experience with Koa, was yielding control to inner middlewares).

There's been a couple discussion in various issues that have mentioned having multiple handlers be able to answer the same query. Some of the discussions have been around guard matching but I like the idea of the express-like system where you can use "fallback handlers".

Just needs to pass request to the next out of the list of other, resource-matching handlers if the primary handler declares, by way of returning Next, that it isn't able to handle the current request. (It appears that current implementation will only service the last defined handler for a resource path but its plausible to change that.)

I see a couple issues that need to be addressed for any kind of multi-handler system to be considered viable:

  • What happens when the last or only handler of a route returns Next, should it be treated as a 404, a 400, maybe a 500? The semantics of what Next means for every possibility should be clearly defineable and potentially recoverable/configurable with app data.
  • How should ownership be handled? Obviously Rust's mutability and ownership rules are much tighter than Node's. It would be very easy to end up in situations where parts of the request are consumed or modified in the first handler but are expected to work in the second handler, with the full request. I can think of a few approaches for handling this but all have drawbacks for now.
  • Should there be an alternative method to .to() for multiple handlers to prevent breaking existing routing tables that consider only the last entry for a path?

Interested to hear your thoughts @ramiroaisen and even @actix/contributors?

robjtede avatar Apr 02 '20 01:04 robjtede

the next solution also brings the posibility to match two different string formats in the same slot of the path, the handler then check if the format is the required and if not just pass it to the next handler

What happens when the last or only handler of a route returns Next, should it be treated as a 404, a 400, maybe a 500? The semantics of what Next means for every possibility should be clearly defineable and potentially recoverable/configurable with app data.

I think the better solution if there's no more handlers if just return an empty 404,

How should ownership be handled? Obviously Rust's mutability and ownership rules are much tighter than Node's. It would be very easy to end up in situations where parts of the request are consumed or modified in the first handler but are expected to work in the second handler, with the full request. I can think of a few approaches for handling this but all have drawbacks for now.

I think it can be done if the handler returns ownership to the system something like this:

async fn handler(request: Request) -> impl Responder {
  if some_condition() {
    Response::Ok()
  } else {
    Next(request)
  }
}

ramiroaisen avatar Apr 04 '20 20:04 ramiroaisen

Interested to get @actix/contributors take on this?

robjtede avatar Apr 04 '20 21:04 robjtede

I'm also seeing a need for this.

My specific use-case is wanting to stack services

.service(Files::new("/", "dir1"))
.service(Files::new("/", "dir2"))
.service(Proxy::new("https://example.com"))

Where the first 2 handlers would next if they cannot serve a specific file from disk (instead of erroring/404'in) finally ending up with a proxy services taking over the request.

shakyShane avatar Oct 29 '20 21:10 shakyShane

This is not useful or easy to do with built in services types of actix-web. The service chain in actix-web always want a ServiceRequest in and ServiceResponse out. Adding addional types to input or output would be major break change and additional overhead for everyone to pay for no matter they use it or not.

You can always build your own service types for nested service logic. Like Files<Files<Proxy>> would easily achieve what you want and it acts very similar to middlewares anyway(The difference would be with custom nested services you can have self defined input output type).

TLDR: The built in service types of actix-web is to satisfy majority usage with a stable pattern and you can always customize services easily for your advanced usage.

fakeshadow avatar Jan 24 '21 06:01 fakeshadow

I have something somewhat related, but I could do it if I could access a Database in app_data from a guard.

ModProg avatar Dec 11 '21 11:12 ModProg