warp: Rejecting with warp::reply::not_found results in a 405 Method Not Allowed error

Hello, with the following code, I would get a 405 error while I expect a 404:

fn main() {
  let routes = warp::post2().map(warp::reply).or(warp::get2().and(warp::path("test").and_then(send_test)));
  warp::serve(routes).run(([127, 0, 0, 1], 3000));
}

fn send_test() -> Result<String, warp::Rejection> {
  Err(warp::reject::not_found())
}

Is there something I am doing wrong?

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 18
  • Comments: 18 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Presumably there should be:

  • warp::reject::unmatched() - this filter didn’t match but it could be valid for a different filter.
  • warp::reject::not_found() - the request was definitely intended for this filter but the requested resource was not found.

maybe there could be a way to cut backtracking? ie, if we know we got to a specific prefix or argument, we’re in the right route, and should return the error from this route

+1 for this behavior being unexpected.

As an example, in the todos example when you build the filters which all contain paths under /todos: https://github.com/seanmonstar/warp/blob/f334f51dc33cf61f145c8d693a554958a9c6ab61/examples/todos.rs#L93

if you add the lines

let req = warp::test::request().path("/");
assert_eq!(req.reply(&api).status(), 404);

the assertion fails with 405 even though you’d expect to get a 404 since we have no routes on /

The workaround is as you mentioned above, you can add an .or_else(|_| Err(warp::reject::not_found())); to the end of the filter and the assertion will pass. However, now you don’t get proper 405 errors when doing get requests on post-only paths.

I don’t know what the best way to solve this in general is though.

My vote is for some way to cut the backtracking. Currently I’m using the approach proposed by @blm768 , but as he mentioned it’s not quite ergonomic. IMHO any reject provided by a user’s code should by default stop the processing and just return the rejection, and probably it’s not a too bad idea to have a special reject like reject::try_something_different() (bad naming, but I hope you’ll get the point) that should be returned by the combinators to go for furthers ors.

I think so: my original gist was oversimplified for that case. I imagine putting get_dog last in routes would technically fix it, since a blanket Ok(404) response from a recover handler prevents or() from looking for more routes. The simplest design might be just building a response in get_dog that handles both success and the error case for that lookup. Another alternative that should work is using a custom rejection type, and recovering specifically from that. I updated this with both of those alternatives: https://gist.github.com/luke-brown/fbaa1392964befd9a285ca310f0b270a That second option probably looks more complicated than the “inline” option–here it’s certainly a lot more code–but so far I’ve found that using custom rejection types makes handling complex handlers easy to reject early from.

In principle, if you want a particular error to override all others, you can return an Ok(http::Response) with that error code. Maybe not the most ergonomic way to do it, but it’s accepting the mapping of route to resource and then rejecting the request, so it kind of makes sense in an abstract way. Alternatively, in this case, you could probably move the get2() farther down the chain.

Thanks so much for your help on this!

Considering the comments from @Timmmm, I guess we should distinguish parse errors and application errors.