Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
chrislearn committed Jul 29, 2024
1 parent 18ee97d commit d4e0d1e
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 46 deletions.
12 changes: 6 additions & 6 deletions crates/core/src/http/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ pub use http::request::Parts;
use http::uri::{Scheme, Uri};
use http::Extensions;
use http_body_util::{BodyExt, Limited};
use indexmap::IndexMap;
use multimap::MultiMap;
use parking_lot::RwLock;
use serde::de::Deserialize;
Expand All @@ -25,6 +24,7 @@ use crate::fuse::TransProto;
use crate::http::body::ReqBody;
use crate::http::form::{FilePart, FormData};
use crate::http::{Mime, ParseError, Version};
use crate::routing::PathParams;
use crate::serde::{from_request, from_str_map, from_str_multi_map, from_str_multi_val, from_str_val};
use crate::Error;

Expand Down Expand Up @@ -61,7 +61,7 @@ pub struct Request {
#[cfg(feature = "cookie")]
pub(crate) cookies: CookieJar,

pub(crate) params: IndexMap<String, String>,
pub(crate) params: PathParams,

// accept: Option<Vec<Mime>>,
pub(crate) queries: OnceLock<MultiMap<String, String>>,
Expand Down Expand Up @@ -110,7 +110,7 @@ impl Request {
method: Method::default(),
#[cfg(feature = "cookie")]
cookies: CookieJar::default(),
params: IndexMap::new(),
params: PathParams::new(),
queries: OnceLock::new(),
form_data: tokio::sync::OnceCell::new(),
payload: tokio::sync::OnceCell::new(),
Expand Down Expand Up @@ -171,7 +171,7 @@ impl Request {
#[cfg(feature = "cookie")]
cookies,
// accept: None,
params: IndexMap::new(),
params: PathParams::new(),
form_data: tokio::sync::OnceCell::new(),
payload: tokio::sync::OnceCell::new(),
// multipart: OnceLock::new(),
Expand Down Expand Up @@ -567,12 +567,12 @@ impl Request {
}
/// Get params reference.
#[inline]
pub fn params(&self) -> &IndexMap<String, String> {
pub fn params(&self) -> &PathParams {
&self.params
}
/// Get params mutable reference.
#[inline]
pub fn params_mut(&mut self) -> &mut IndexMap<String, String> {
pub fn params_mut(&mut self) -> &mut PathParams {
&mut self.params
}

Expand Down
25 changes: 7 additions & 18 deletions crates/core/src/routing/filters/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,13 @@ impl PathWisp for CharsWisp {
}
if chars.len() == max_width {
state.forward(max_width);
insert_param_to_state(state, &self.name, chars.into_iter().collect());
state.params.insert(&self.name, chars.into_iter().collect());
return true;
}
}
if chars.len() >= self.min_width {
state.forward(chars.len());
insert_param_to_state(state, &self.name, chars.into_iter().collect());
state.params.insert(&self.name, chars.into_iter().collect());
true
} else {
false
Expand All @@ -274,7 +274,7 @@ impl PathWisp for CharsWisp {
}
if chars.len() >= self.min_width {
state.forward(chars.len());
insert_param_to_state(state, &self.name, chars.into_iter().collect());
state.params.insert(&self.name, chars.into_iter().collect());
true
} else {
false
Expand Down Expand Up @@ -403,7 +403,7 @@ impl PathWisp for NamedWisp {
}
if !rest.is_empty() || !self.0.starts_with("*+") {
let rest = rest.to_string();
insert_param_to_state(state, &self.0, rest);
state.params.insert(&self.0, rest);
state.cursor.0 = state.parts.len();
true
} else {
Expand All @@ -416,7 +416,7 @@ impl PathWisp for NamedWisp {
}
let picked = picked.expect("picked should not be `None`").to_owned();
state.forward(picked.len());
insert_param_to_state(state, &self.0, picked);
state.params.insert(&self.0, picked);
true
}
}
Expand Down Expand Up @@ -456,7 +456,7 @@ impl PathWisp for RegexWisp {
if let Some(cap) = cap {
let cap = cap.as_str().to_owned();
state.forward(cap.len());
insert_param_to_state(state, &self.name, cap);
state.params.insert(&self.name, cap);
true
} else {
false
Expand All @@ -472,7 +472,7 @@ impl PathWisp for RegexWisp {
if let Some(cap) = cap {
let cap = cap.as_str().to_owned();
state.forward(cap.len());
insert_param_to_state(state, &self.name, cap);
state.params.insert(&self.name, cap);
true
} else {
false
Expand Down Expand Up @@ -915,17 +915,6 @@ impl PathFilter {
}
}

#[inline]
fn insert_param_to_state(state: &mut PathState, name: &str, value: String) {
if name.starts_with("*+") || name.starts_with("*?") || name.starts_with("**") {
state.params.insert(name[2..].to_owned(), value);
} else if name.starts_with('*') {
state.params.insert(name[1..].to_owned(), value);
} else {
state.params.insert(name.to_owned(), value);
}
}

#[cfg(test)]
mod tests {
use super::PathParser;
Expand Down
54 changes: 52 additions & 2 deletions crates/core/src/routing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ mod router;
pub use router::Router;

use std::borrow::Cow;
use std::ops::Deref;
use std::sync::Arc;

use indexmap::IndexMap;
Expand All @@ -388,8 +389,57 @@ pub struct DetectMatched {
pub goal: Arc<dyn Handler>,
}

#[doc(hidden)]
pub type PathParams = IndexMap<String, String>;
/// The path parameters.
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct PathParams{
inner: IndexMap<String, String>,
greedy: bool,
}
impl Deref for PathParams {
type Target = IndexMap<String, String>;

fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl PathParams {
/// Create new `PathParams`.
pub fn new() -> Self {
PathParams::default()
}
/// If there is a wildcard param, it's value is `true`.
pub fn greedy(&self) -> bool {
self.greedy
}
/// Get the last param starts with '*', for example: <**rest>, <*?rest>.
pub fn star(&self) -> Option<&str> {
if self.greedy {
self.inner.last().map(|(_, v)|&**v)
} else {
None
}
}

/// Insert new param.
pub fn insert(&mut self, name: &str, value: String) {
#[cfg(debug_assertions)]
{
if self.greedy {
panic!("only one wildcard param is allowed and it must be the last one.");
}
}
if name.starts_with("*+") || name.starts_with("*?") || name.starts_with("**") {
self.inner.insert(name[2..].to_owned(), value);
self.greedy = true;
} else if name.starts_with('*') {
self.inner.insert(name[1..].to_owned(), value);
self.greedy = true;
} else {
self.inner.insert(name.to_owned(), value);
}
}
}

#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PathState {
Expand Down
2 changes: 1 addition & 1 deletion crates/oapi/src/extract/parameter/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ mod tests {
let req = TestClient::get("http://127.0.0.1:5801").build_hyper();
let schema = req.uri().scheme().cloned().unwrap();
let mut req = Request::from_hyper(req, schema);
req.params_mut().insert("param".to_string(), "param".to_string());
req.params_mut().insert("param", "param".to_string());
let result = PathParam::<String>::extract_with_arg(&mut req, "param").await;
assert_eq!(result.unwrap().0, "param");
}
Expand Down
10 changes: 7 additions & 3 deletions crates/oapi/src/swagger_ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,16 @@ pub(crate) fn redirect_to_dir_url(req_uri: &Uri, res: &mut Response) {
#[async_trait]
impl Handler for SwaggerUi {
async fn handle(&self, req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) {
let path = req.params().last().map(|(_, s)| &**s).unwrap_or_default();
// Redirect to dir url if path is empty and not end with '/'
if path.is_empty() && !req.uri().path().ends_with('/') {
// Redirect to dir url if path is not end with '/'
if !req.uri().path().ends_with('/') {
redirect_to_dir_url(req.uri(), res);
return;
}
let Some(path) = req.params().star() else {
res.render(StatusError::not_found().detail("The router params is incorrect. The params should ending with a wildcard."));
return;
};

let keywords = self
.keywords
.as_ref()
Expand Down
8 changes: 5 additions & 3 deletions crates/proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,12 @@ where
/// Url part getter. You can use this to get the proxied url path or query.
pub type UrlPartGetter = Box<dyn Fn(&Request, &Depot) -> Option<String> + Send + Sync + 'static>;

/// Default url path getter. This getter will get the url path from request wildcard param, like `<**rest>`, `<*+rest>`.
/// Default url path getter.
///
/// This getter will get the last param as the rest url path from request.
/// In most case you should use wildcard param, like `<**rest>`, `<*+rest>`.
pub fn default_url_path_getter(req: &Request, _depot: &Depot) -> Option<String> {
let param = req.params().iter().find(|(key, _)| key.starts_with('*'));
if let Some((_, rest)) = param {
if let Some(rest) = req.params().star() {
Some(encode_url_path(rest))
} else {
None
Expand Down
7 changes: 3 additions & 4 deletions crates/serve-static/src/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,11 @@ impl DirInfo {
#[async_trait]
impl Handler for StaticDir {
async fn handle(&self, req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) {
let param = req.params().iter().find(|(key, _)| key.starts_with('*'));
let req_path = req.uri().path();
let rel_path = if let Some((_, value)) = param {
value.clone()
let rel_path = if let Some(rest) = req.params().star() {
rest
} else {
decode_url_path_safely(req_path)
&*decode_url_path_safely(req_path)
};
let rel_path = format_url_path_safely(&rel_path);
let mut files: HashMap<String, Metadata> = HashMap::new();
Expand Down
7 changes: 3 additions & 4 deletions crates/serve-static/src/embed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,10 @@ where
T: RustEmbed + Send + Sync + 'static,
{
async fn handle(&self, req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) {
let param = req.params().iter().find(|(key, _)| key.starts_with('*'));
let req_path = if let Some((_, value)) = param {
value.clone()
let req_path = if let Some(rest) = req.params().star() {
rest
} else {
decode_url_path_safely(req.uri().path())
&*decode_url_path_safely(req.uri().path())
};
let req_path = format_url_path_safely(&req_path);
let mut key_path = Cow::Borrowed(&*req_path);
Expand Down
10 changes: 5 additions & 5 deletions crates/serve-static/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,21 +190,21 @@ mod tests {

let router = Router::new()
.push(Router::with_path("test1.txt").get(Assets::get("test1.txt").unwrap().into_handler()))
.push(Router::with_path("files/<*path>").get(serve_file))
.push(Router::with_path("files/<**path>").get(serve_file))
.push(
Router::with_path("dir/<*path>").get(
Router::with_path("dir/<**path>").get(
static_embed::<Assets>()
.defaults("index.html")
.fallback("fallback.html"),
),
)
.push(Router::with_path("dir2/<*path>").get(static_embed::<Assets>()))
.push(Router::with_path("dir3/<*path>").get(static_embed::<Assets>().fallback("notexist.html")));
.push(Router::with_path("dir2/<**path>").get(static_embed::<Assets>()))
.push(Router::with_path("dir3/<**path>").get(static_embed::<Assets>().fallback("notexist.html")));
let service = Service::new(router);

#[handler]
async fn serve_file(req: &mut Request, res: &mut Response) {
let path = req.param::<String>("*path").unwrap();
let path = req.param::<String>("path").unwrap();
if let Some(file) = Assets::get(&path) {
file.render(req, res);
}
Expand Down

0 comments on commit d4e0d1e

Please sign in to comment.