-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Fix routing for opaque nested services #842
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,62 +42,78 @@ where | |
| } | ||
|
|
||
| fn strip_prefix(uri: &Uri, prefix: &str) -> Option<Uri> { | ||
| let path_and_query: http::uri::PathAndQuery = if let Some(path_and_query) = uri.path_and_query() | ||
| { | ||
| // Check whether the prefix matches the path and if so how long the matching prefix is. | ||
| // | ||
| // # Examples | ||
| // | ||
| // prefix = /api | ||
| // uri = /api/users | ||
| // ^^^^ this much is matched and the length is 4. Thus if we chop off the first 4 | ||
| // characters we get the remainder | ||
| // | ||
| // prefix = /api/:version | ||
| // uri = /api/v0/users | ||
| // ^^^^^^^ this much is matched and the length is 7. | ||
| let mut matching_prefix_length = Some(0); | ||
| for item in zip_longest(segments(path_and_query.path()), segments(prefix)) { | ||
| // count the `/` | ||
| *matching_prefix_length.as_mut().unwrap() += 1; | ||
|
|
||
| match item { | ||
| Item::Both(path_segment, prefix_segment) => { | ||
| if prefix_segment.starts_with(':') || path_segment == prefix_segment { | ||
| *matching_prefix_length.as_mut().unwrap() += path_segment.len(); | ||
| } else { | ||
| matching_prefix_length = None; | ||
| break; | ||
| } | ||
| } | ||
| Item::First(_) => { | ||
| let path_and_query = uri.path_and_query()?; | ||
|
|
||
| // Check whether the prefix matches the path and if so how long the matching prefix is. | ||
| // | ||
| // For example: | ||
| // | ||
| // prefix = /api | ||
| // path = /api/users | ||
| // ^^^^ this much is matched and the length is 4. Thus if we chop off the first 4 | ||
| // characters we get the remainder | ||
| // | ||
| // prefix = /api/:version | ||
| // path = /api/v0/users | ||
| // ^^^^^^^ this much is matched and the length is 7. | ||
| let mut matching_prefix_length = Some(0); | ||
| for item in zip_longest(segments(path_and_query.path()), segments(prefix)) { | ||
| // count the `/` | ||
| *matching_prefix_length.as_mut().unwrap() += 1; | ||
|
|
||
| match item { | ||
| Item::Both(path_segment, prefix_segment) => { | ||
| if prefix_segment.starts_with(':') || path_segment == prefix_segment { | ||
| // the prefix segment is either a param, which matches anything, or | ||
| // it actually matches the path segment | ||
| *matching_prefix_length.as_mut().unwrap() += path_segment.len(); | ||
| } else if prefix_segment.is_empty() { | ||
| // the prefix ended in a `/` so we got a match. | ||
| // | ||
| // For example: | ||
| // | ||
| // prefix = /foo/ | ||
| // path = /foo/bar | ||
| // | ||
| // The prefix matches and the new path should be `/bar` | ||
| break; | ||
| } | ||
| Item::Second(_) => { | ||
| } else { | ||
| // the prefix segment didn't match so there is no match | ||
| matching_prefix_length = None; | ||
| break; | ||
| } | ||
| } | ||
| // the path had more segments than the prefix but we got a match. | ||
| // | ||
| // For example: | ||
| // | ||
| // prefix = /foo | ||
| // path = /foo/bar | ||
| Item::First(_) => { | ||
| break; | ||
| } | ||
| // the prefix had more segments than the path so there is no match | ||
| Item::Second(_) => { | ||
| matching_prefix_length = None; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| let after_prefix = if let Some(idx) = matching_prefix_length { | ||
| uri.path().split_at(idx).1 | ||
| } else { | ||
| return None; | ||
| }; | ||
| // if the prefix matches it will always do so up until a `/`, it cannot match only | ||
| // part of a segment. Therefore this will always be at a char boundary and `split_at` wont | ||
| // panic | ||
| let after_prefix = uri.path().split_at(matching_prefix_length?).1; | ||
|
|
||
| match (after_prefix.starts_with('/'), path_and_query.query()) { | ||
| (true, None) => after_prefix.parse().unwrap(), | ||
| (true, Some(query)) => format!("{}?{}", after_prefix, query).parse().unwrap(), | ||
| (false, None) => format!("/{}", after_prefix).parse().unwrap(), | ||
| (false, Some(query)) => format!("/{}?{}", after_prefix, query).parse().unwrap(), | ||
| } | ||
| } else { | ||
| return None; | ||
| let new_path_and_query = match (after_prefix.starts_with('/'), path_and_query.query()) { | ||
| (true, None) => after_prefix.parse().unwrap(), | ||
| (true, Some(query)) => format!("{}?{}", after_prefix, query).parse().unwrap(), | ||
| (false, None) => format!("/{}", after_prefix).parse().unwrap(), | ||
| (false, Some(query)) => format!("/{}?{}", after_prefix, query).parse().unwrap(), | ||
| }; | ||
|
|
||
| let mut parts = uri.clone().into_parts(); | ||
| parts.path_and_query = Some(path_and_query); | ||
| parts.path_and_query = Some(new_path_and_query); | ||
|
|
||
| Some(Uri::from_parts(parts).unwrap()) | ||
| } | ||
|
|
@@ -183,7 +199,7 @@ mod tests { | |
| single_segment_root_prefix, | ||
| uri = "/a", | ||
| prefix = "/", | ||
| expected = None, | ||
| expected = Some("/a"), | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The outcome of this is the same. Not matching the prefix or matching and removing nothing from the path is equivalent. |
||
| ); | ||
|
|
||
| test!( | ||
|
|
@@ -334,6 +350,13 @@ mod tests { | |
| expected = Some("/"), | ||
| ); | ||
|
|
||
| test!( | ||
| param_13, | ||
| uri = "/a/a", | ||
| prefix = "/a/", | ||
| expected = Some("/a"), | ||
| ); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Case that wasn't handled properly before. |
||
|
|
||
| #[quickcheck] | ||
| fn does_not_panic(uri_and_prefix: UriAndPrefix) -> bool { | ||
| let UriAndPrefix { uri, prefix } = uri_and_prefix; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we would add a double slash to
nest("/foo/", _).