Skip to content

Commit 81f0248

Browse files
committed
Per-route middleware
1 parent 935498f commit 81f0248

File tree

4 files changed

+202
-11
lines changed

4 files changed

+202
-11
lines changed

src/endpoint.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
use std::sync::Arc;
2+
13
use async_std::future::Future;
24

5+
use crate::middleware::Next;
36
use crate::utils::BoxFuture;
4-
use crate::{response::IntoResponse, Request, Response};
7+
use crate::{response::IntoResponse, Middleware, Request, Response};
58

69
/// An HTTP request handler.
710
///
@@ -63,3 +66,52 @@ where
6366
Box::pin(async move { fut.await.into_response() })
6467
}
6568
}
69+
70+
pub struct MiddlewareEndpoint<E, State> {
71+
endpoint: E,
72+
middleware: Vec<Arc<dyn Middleware<State>>>,
73+
}
74+
75+
impl<E: Clone, State> Clone for MiddlewareEndpoint<E, State> {
76+
fn clone(&self) -> Self {
77+
Self {
78+
endpoint: self.endpoint.clone(),
79+
middleware: self.middleware.clone(),
80+
}
81+
}
82+
}
83+
84+
impl<E, State> std::fmt::Debug for MiddlewareEndpoint<E, State> {
85+
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86+
write!(
87+
fmt,
88+
"MiddlewareEndpoint (length: {})",
89+
self.middleware.len(),
90+
)
91+
}
92+
}
93+
94+
impl<E, State> MiddlewareEndpoint<E, State>
95+
where
96+
E: Endpoint<State>,
97+
{
98+
pub fn wrap_with_middleware(ep: E, middleware: &[Arc<dyn Middleware<State>>]) -> Self {
99+
Self {
100+
endpoint: ep,
101+
middleware: middleware.to_vec(),
102+
}
103+
}
104+
}
105+
106+
impl<E, State: 'static> Endpoint<State> for MiddlewareEndpoint<E, State>
107+
where
108+
E: Endpoint<State>,
109+
{
110+
fn call<'a>(&'a self, req: Request<State>) -> BoxFuture<'a, Response> {
111+
let next = Next {
112+
endpoint: &self.endpoint,
113+
next_middleware: &self.middleware,
114+
};
115+
next.run(req)
116+
}
117+
}

src/router.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use route_recognizer::{Match, Params, Router as MethodRouter};
22
use std::collections::HashMap;
33

4-
use crate::endpoint::{DynEndpoint, Endpoint};
4+
use crate::endpoint::DynEndpoint;
55
use crate::utils::BoxFuture;
66
use crate::{Request, Response};
77

@@ -29,15 +29,15 @@ impl<State: 'static> Router<State> {
2929
}
3030
}
3131

32-
pub(crate) fn add(&mut self, path: &str, method: http::Method, ep: impl Endpoint<State>) {
32+
pub(crate) fn add(&mut self, path: &str, method: http::Method, ep: Box<DynEndpoint<State>>) {
3333
self.method_map
3434
.entry(method)
3535
.or_insert_with(MethodRouter::new)
36-
.add(path, Box::new(ep))
36+
.add(path, ep)
3737
}
3838

39-
pub(crate) fn add_all(&mut self, path: &str, ep: impl Endpoint<State>) {
40-
self.all_method_router.add(path, Box::new(ep))
39+
pub(crate) fn add_all(&mut self, path: &str, ep: Box<DynEndpoint<State>>) {
40+
self.all_method_router.add(path, ep)
4141
}
4242

4343
pub(crate) fn route(&self, path: &str, method: http::Method) -> Selection<'_, State> {

src/server/route.rs

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
use std::sync::Arc;
2+
3+
use crate::endpoint::MiddlewareEndpoint;
14
use crate::utils::BoxFuture;
2-
use crate::{router::Router, Endpoint, Response};
5+
use crate::{router::Router, Endpoint, Middleware, Response};
36

47
/// A handle to a route.
58
///
@@ -13,6 +16,7 @@ use crate::{router::Router, Endpoint, Response};
1316
pub struct Route<'a, State> {
1417
router: &'a mut Router<State>,
1518
path: String,
19+
middleware: Vec<Arc<dyn Middleware<State>>>,
1620
/// Indicates whether the path of current route is treated as a prefix. Set by
1721
/// [`strip_prefix`].
1822
///
@@ -25,11 +29,14 @@ impl<'a, State: 'static> Route<'a, State> {
2529
Route {
2630
router,
2731
path,
32+
middleware: Vec::new(),
2833
prefix: false,
2934
}
3035
}
3136

3237
/// Extend the route with the given `path`.
38+
///
39+
/// The returned route won't have any middleware applied.
3340
pub fn at<'b>(&'b mut self, path: &str) -> Route<'b, State> {
3441
let mut p = self.path.clone();
3542

@@ -44,6 +51,7 @@ impl<'a, State: 'static> Route<'a, State> {
4451
Route {
4552
router: &mut self.router,
4653
path: p,
54+
middleware: Vec::new(),
4755
prefix: false,
4856
}
4957
}
@@ -60,6 +68,18 @@ impl<'a, State: 'static> Route<'a, State> {
6068
self
6169
}
6270

71+
/// Apply the given middleware to the current route.
72+
pub fn middleware(&mut self, middleware: impl Middleware<State>) -> &mut Self {
73+
self.middleware.push(Arc::new(middleware));
74+
self
75+
}
76+
77+
/// Reset the middleware chain for the current route, if any.
78+
pub fn reset_middleware(&mut self) -> &mut Self {
79+
self.middleware.clear();
80+
self
81+
}
82+
6383
/// Nest a [`Server`] at the current path.
6484
///
6585
/// [`Server`]: struct.Server.html
@@ -78,10 +98,29 @@ impl<'a, State: 'static> Route<'a, State> {
7898
pub fn method(&mut self, method: http::Method, ep: impl Endpoint<State>) -> &mut Self {
7999
if self.prefix {
80100
let ep = StripPrefixEndpoint::new(ep);
81-
self.router.add(&self.path, method.clone(), ep.clone());
101+
let (ep1, ep2): (Box<dyn Endpoint<_>>, Box<dyn Endpoint<_>>) =
102+
if self.middleware.is_empty() {
103+
let ep = Box::new(ep);
104+
(ep.clone(), ep)
105+
} else {
106+
let ep = Box::new(MiddlewareEndpoint::wrap_with_middleware(
107+
ep,
108+
&self.middleware,
109+
));
110+
(ep.clone(), ep)
111+
};
112+
self.router.add(&self.path, method.clone(), ep1);
82113
let wildcard = self.at("*--tide-path-rest");
83-
wildcard.router.add(&wildcard.path, method, ep);
114+
wildcard.router.add(&wildcard.path, method, ep2);
84115
} else {
116+
let ep: Box<dyn Endpoint<_>> = if self.middleware.is_empty() {
117+
Box::new(ep)
118+
} else {
119+
Box::new(MiddlewareEndpoint::wrap_with_middleware(
120+
ep,
121+
&self.middleware,
122+
))
123+
};
85124
self.router.add(&self.path, method, ep);
86125
}
87126
self
@@ -93,10 +132,29 @@ impl<'a, State: 'static> Route<'a, State> {
93132
pub fn all(&mut self, ep: impl Endpoint<State>) -> &mut Self {
94133
if self.prefix {
95134
let ep = StripPrefixEndpoint::new(ep);
96-
self.router.add_all(&self.path, ep.clone());
135+
let (ep1, ep2): (Box<dyn Endpoint<_>>, Box<dyn Endpoint<_>>) =
136+
if self.middleware.is_empty() {
137+
let ep = Box::new(ep);
138+
(ep.clone(), ep)
139+
} else {
140+
let ep = Box::new(MiddlewareEndpoint::wrap_with_middleware(
141+
ep,
142+
&self.middleware,
143+
));
144+
(ep.clone(), ep)
145+
};
146+
self.router.add_all(&self.path, ep1);
97147
let wildcard = self.at("*--tide-path-rest");
98-
wildcard.router.add_all(&wildcard.path, ep);
148+
wildcard.router.add_all(&wildcard.path, ep2);
99149
} else {
150+
let ep: Box<dyn Endpoint<_>> = if self.middleware.is_empty() {
151+
Box::new(ep)
152+
} else {
153+
Box::new(MiddlewareEndpoint::wrap_with_middleware(
154+
ep,
155+
&self.middleware,
156+
))
157+
};
100158
self.router.add_all(&self.path, ep);
101159
}
102160
self

tests/route_middleware.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use async_std::io::prelude::*;
2+
use futures::executor::block_on;
3+
use futures::future::BoxFuture;
4+
use http_service::Body;
5+
use http_service_mock::make_server;
6+
use tide::Middleware;
7+
8+
struct TestMiddleware(&'static str);
9+
10+
impl<State: Send + Sync + 'static> Middleware<State> for TestMiddleware {
11+
fn handle<'a>(
12+
&'a self,
13+
req: tide::Request<State>,
14+
next: tide::Next<'a, State>,
15+
) -> BoxFuture<'a, tide::Response> {
16+
Box::pin(async move {
17+
let res = next.run(req).await;
18+
res.set_header("X-Tide-Test", self.0)
19+
})
20+
}
21+
}
22+
23+
async fn echo_path<State>(req: tide::Request<State>) -> String {
24+
req.uri().path().to_string()
25+
}
26+
27+
#[test]
28+
fn route_middleware() {
29+
let mut app = tide::new();
30+
let mut foo_route = app.at("/foo");
31+
foo_route.middleware(TestMiddleware("foo"))
32+
.get(echo_path);
33+
foo_route.at("/bar")
34+
.middleware(TestMiddleware("bar"))
35+
.get(echo_path);
36+
foo_route.post(echo_path)
37+
.reset_middleware()
38+
.put(echo_path);
39+
let mut server = make_server(app.into_http_service()).unwrap();
40+
41+
let mut buf = Vec::new();
42+
let req = http::Request::get("/foo").body(Body::empty()).unwrap();
43+
let res = server.simulate(req).unwrap();
44+
assert_eq!(
45+
res.headers().get("X-Tide-Test"),
46+
Some(&"foo".parse().unwrap())
47+
);
48+
assert_eq!(res.status(), 200);
49+
block_on(res.into_body().read_to_end(&mut buf)).unwrap();
50+
assert_eq!(&*buf, &*b"/foo");
51+
52+
buf.clear();
53+
let req = http::Request::post("/foo").body(Body::empty()).unwrap();
54+
let res = server.simulate(req).unwrap();
55+
assert_eq!(
56+
res.headers().get("X-Tide-Test"),
57+
Some(&"foo".parse().unwrap())
58+
);
59+
assert_eq!(res.status(), 200);
60+
block_on(res.into_body().read_to_end(&mut buf)).unwrap();
61+
assert_eq!(&*buf, &*b"/foo");
62+
63+
buf.clear();
64+
let req = http::Request::put("/foo").body(Body::empty()).unwrap();
65+
let res = server.simulate(req).unwrap();
66+
assert_eq!(res.headers().get("X-Tide-Test"), None);
67+
assert_eq!(res.status(), 200);
68+
block_on(res.into_body().read_to_end(&mut buf)).unwrap();
69+
assert_eq!(&*buf, &*b"/foo");
70+
71+
buf.clear();
72+
let req = http::Request::get("/foo/bar").body(Body::empty()).unwrap();
73+
let res = server.simulate(req).unwrap();
74+
assert_eq!(
75+
res.headers().get("X-Tide-Test"),
76+
Some(&"bar".parse().unwrap())
77+
);
78+
assert_eq!(res.status(), 200);
79+
block_on(res.into_body().read_to_end(&mut buf)).unwrap();
80+
assert_eq!(&*buf, &*b"/foo/bar");
81+
}

0 commit comments

Comments
 (0)