pub trait Filter: FilterBase {
fn and<F>(self, other: F) -> And<Self, F>
where
Self: Sized,
<Self::Extract as Tuple>::HList: Combine<<F::Extract as Tuple>::HList>,
F: Filter + Clone,
F::Error: CombineRejection<Self::Error>,
{ ... }
fn or<F>(self, other: F) -> Or<Self, F>
where
Self: Filter<Error = Rejection> + Sized,
F: Filter,
F::Error: CombineRejection<Self::Error>,
{ ... }
fn map<F>(self, fun: F) -> Map<Self, F>
where
Self: Sized,
F: Func<Self::Extract> + Clone,
{ ... }
fn and_then<F>(self, fun: F) -> AndThen<Self, F>
where
Self: Sized,
F: Func<Self::Extract> + Clone,
F::Output: TryFuture + Send,
<F::Output as TryFuture>::Error: CombineRejection<Self::Error>,
{ ... }
fn or_else<F>(self, fun: F) -> OrElse<Self, F>
where
Self: Filter<Error = Rejection> + Sized,
F: Func<Rejection>,
F::Output: TryFuture<Ok = Self::Extract> + Send,
<F::Output as TryFuture>::Error: IsReject,
{ ... }
fn recover<F>(self, fun: F) -> Recover<Self, F>
where
Self: Filter<Error = Rejection> + Sized,
F: Func<Rejection>,
F::Output: TryFuture + Send,
<F::Output as TryFuture>::Error: IsReject,
{ ... }
fn unify<T>(self) -> Unify<Self>
where
Self: Filter<Extract = (Either<T, T>,)> + Sized,
T: Tuple,
{ ... }
fn untuple_one<T>(self) -> UntupleOne<Self>
where
Self: Filter<Extract = (T,)> + Sized,
T: Tuple,
{ ... }
fn with<W>(self, wrapper: W) -> W::Wrapped
where
Self: Sized,
W: Wrap<Self>,
{ ... }
fn boxed(self) -> BoxedFilter<Self::Extract>
where
Self: Sized + Send + Sync + 'static,
Self::Extract: Send,
Self::Error: Into<Rejection>,
{ ... }
}
Expand description
Composable request filters.
A Filter
can optionally extract some data from a request, combine
it with others, mutate it, and return back some value as a reply. The
power of Filter
s come from being able to isolate small subsets, and then
chain and reuse them in various parts of your app.
Extracting Tuples
You may notice that several of these filters extract some tuple, often times a tuple of just 1 item! Why?
If a filter extracts a (String,)
, that simply means that it
extracts a String
. If you were to map
the filter, the argument type
would be exactly that, just a String
.
What is it? It’s just some type magic that allows for automatic combining
and flattening of tuples. Without it, combining two filters together with
and
, where one extracted ()
, and another String
, would mean the
map
would be given a single argument of ((), String,)
, which is just
no fun.
Provided methods
Composes a new Filter
that requires both this and the other to filter a request.
Additionally, this will join together the extracted values of both
filters, so that map
and and_then
receive them as separate arguments.
If a Filter
extracts nothing (so, ()
), combining with any other
filter will simply discard the ()
. If a Filter
extracts one or
more items, combining will mean it extracts the values of itself
combined with the other.
Example
use warp::Filter;
// Match `/hello/:name`...
warp::path("hello")
.and(warp::path::param::<String>());
Examples found in repository?
More examples
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
pub fn todos_list(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("todos")
.and(warp::get())
.and(warp::query::<ListOptions>())
.and(with_db(db))
.and_then(handlers::list_todos)
}
/// POST /todos with JSON body
pub fn todos_create(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("todos")
.and(warp::post())
.and(json_body())
.and(with_db(db))
.and_then(handlers::create_todo)
}
/// PUT /todos/:id with JSON body
pub fn todos_update(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("todos" / u64)
.and(warp::put())
.and(json_body())
.and(with_db(db))
.and_then(handlers::update_todo)
}
/// DELETE /todos/:id
pub fn todos_delete(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
// We'll make one of our endpoints admin-only to show how authentication filters are used
let admin_only = warp::header::exact("authorization", "Bearer admin");
warp::path!("todos" / u64)
// It is important to put the auth check _after_ the path filters.
// If we put the auth check before, the request `PUT /todos/invalid-string`
// would try this filter and reject because the authorization header doesn't match,
// rather because the param is wrong for that other path.
.and(admin_only)
.and(warp::delete())
.and(with_db(db))
.and_then(handlers::delete_todo)
}
fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || db.clone())
}
fn json_body() -> impl Filter<Extract = (Todo,), Error = warp::Rejection> + Clone {
// When accepting a body, we want a JSON body
// (and to reject huge payloads)...
warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
pub fn file(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
let path = Arc::new(path.into());
crate::any()
.map(move || {
tracing::trace!("file: {:?}", path);
ArcPath(path.clone())
})
.and(conditionals())
.and_then(|path, conditionals| file_reply(path, conditionals))
}
/// Creates a `Filter` that serves a directory at the base `path` joined
/// by the request path.
///
/// This can be used to serve "static files" from a directory. By far the most
/// common pattern of serving static files is for `GET` requests, so this
/// filter automatically includes a `GET` check.
///
/// # Example
///
/// ```
/// use warp::Filter;
///
/// // Matches requests that start with `/static`,
/// // and then uses the rest of that path to lookup
/// // and serve a file from `/www/static`.
/// let route = warp::path("static")
/// .and(warp::fs::dir("/www/static"));
///
/// // For example:
/// // - `GET /static/app.js` would serve the file `/www/static/app.js`
/// // - `GET /static/css/app.css` would serve the file `/www/static/css/app.css`
/// ```
pub fn dir(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
let base = Arc::new(path.into());
crate::get()
.and(path_from_tail(base))
.and(conditionals())
.and_then(file_reply)
}
fn path_from_tail(
base: Arc<PathBuf>,
) -> impl FilterClone<Extract = One<ArcPath>, Error = Rejection> {
crate::path::tail().and_then(move |tail: crate::path::Tail| {
future::ready(sanitize_path(base.as_ref(), tail.as_str())).and_then(|mut buf| async {
let is_dir = tokio::fs::metadata(buf.clone())
.await
.map(|m| m.is_dir())
.unwrap_or(false);
if is_dir {
tracing::debug!("dir: appending index.html to directory path");
buf.push("index.html");
}
tracing::trace!("dir: {:?}", buf);
Ok(ArcPath(Arc::new(buf)))
})
})
}
fn sanitize_path(base: impl AsRef<Path>, tail: &str) -> Result<PathBuf, Rejection> {
let mut buf = PathBuf::from(base.as_ref());
let p = match percent_decode_str(tail).decode_utf8() {
Ok(p) => p,
Err(err) => {
tracing::debug!("dir: failed to decode route={:?}: {:?}", tail, err);
return Err(reject::not_found());
}
};
tracing::trace!("dir? base={:?}, route={:?}", base.as_ref(), p);
for seg in p.split('/') {
if seg.starts_with("..") {
tracing::warn!("dir: rejecting segment starting with '..'");
return Err(reject::not_found());
} else if seg.contains('\\') {
tracing::warn!("dir: rejecting segment containing with backslash (\\)");
return Err(reject::not_found());
} else {
buf.push(seg);
}
}
Ok(buf)
}
#[derive(Debug)]
struct Conditionals {
if_modified_since: Option<IfModifiedSince>,
if_unmodified_since: Option<IfUnmodifiedSince>,
if_range: Option<IfRange>,
range: Option<Range>,
}
enum Cond {
NoBody(Response),
WithBody(Option<Range>),
}
impl Conditionals {
fn check(self, last_modified: Option<LastModified>) -> Cond {
if let Some(since) = self.if_unmodified_since {
let precondition = last_modified
.map(|time| since.precondition_passes(time.into()))
.unwrap_or(false);
tracing::trace!(
"if-unmodified-since? {:?} vs {:?} = {}",
since,
last_modified,
precondition
);
if !precondition {
let mut res = Response::new(Body::empty());
*res.status_mut() = StatusCode::PRECONDITION_FAILED;
return Cond::NoBody(res);
}
}
if let Some(since) = self.if_modified_since {
tracing::trace!(
"if-modified-since? header = {:?}, file = {:?}",
since,
last_modified
);
let unmodified = last_modified
.map(|time| !since.is_modified(time.into()))
// no last_modified means its always modified
.unwrap_or(false);
if unmodified {
let mut res = Response::new(Body::empty());
*res.status_mut() = StatusCode::NOT_MODIFIED;
return Cond::NoBody(res);
}
}
if let Some(if_range) = self.if_range {
tracing::trace!("if-range? {:?} vs {:?}", if_range, last_modified);
let can_range = !if_range.is_modified(None, last_modified.as_ref());
if !can_range {
return Cond::WithBody(None);
}
}
Cond::WithBody(self.range)
}
}
fn conditionals() -> impl Filter<Extract = One<Conditionals>, Error = Infallible> + Copy {
crate::header::optional2()
.and(crate::header::optional2())
.and(crate::header::optional2())
.and(crate::header::optional2())
.map(
|if_modified_since, if_unmodified_since, if_range, range| Conditionals {
if_modified_since,
if_unmodified_since,
if_range,
range,
},
)
}
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
pub fn json<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
is_content_type::<Json>()
.and(bytes())
.and_then(|buf| async move {
Json::decode(buf).map_err(|err| {
tracing::debug!("request json body error: {}", err);
reject::known(BodyDeserializeError { cause: err })
})
})
}
/// Returns a `Filter` that matches any request and extracts a
/// `Future` of a form encoded body.
///
/// # Note
///
/// This filter is for the simpler `application/x-www-form-urlencoded` format,
/// not `multipart/form-data`.
///
/// # Warning
///
/// This does not have a default size limit, it would be wise to use one to
/// prevent a overly large request from using too much memory.
///
///
/// ```
/// use std::collections::HashMap;
/// use warp::Filter;
///
/// let route = warp::body::content_length_limit(1024 * 32)
/// .and(warp::body::form())
/// .map(|simple_map: HashMap<String, String>| {
/// "Got a urlencoded body!"
/// });
/// ```
pub fn form<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
is_content_type::<Form>()
.and(aggregate())
.and_then(|buf| async move {
Form::decode(buf).map_err(|err| {
tracing::debug!("request form body error: {}", err);
reject::known(BodyDeserializeError { cause: err })
})
})
}
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
fn hello_wrapper<F, T>(
filter: F,
) -> impl Filter<Extract = (&'static str,)> + Clone + Send + Sync + 'static
where
F: Filter<Extract = (T,), Error = std::convert::Infallible> + Clone + Send + Sync + 'static,
F::Extract: warp::Reply,
{
warp::any()
.map(|| {
println!("before filter");
})
.untuple_one()
.and(filter)
.map(|_arg| "wrapped hello world")
}
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
async fn main() {
pretty_env_logger::init();
let readme = warp::get()
.and(warp::path::end())
.and(warp::fs::file("./README.md"));
// dir already requires GET...
let examples = warp::path("ex").and(warp::fs::dir("./examples/"));
// GET / => README.md
// GET /ex/... => ./examples/..
let routes = readme.or(examples);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
Composes a new Filter
of either this or the other filter.
Example
use std::net::SocketAddr;
use warp::Filter;
// Match either `/:u32` or `/:socketaddr`
warp::path::param::<u32>()
.or(warp::path::param::<SocketAddr>());
Examples found in repository?
More examples
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
async fn main() {
pretty_env_logger::init();
let readme = warp::get()
.and(warp::path::end())
.and(warp::fs::file("./README.md"));
// dir already requires GET...
let examples = warp::path("ex").and(warp::fs::dir("./examples/"));
// GET / => README.md
// GET /ex/... => ./examples/..
let routes = readme.or(examples);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
async fn main() {
let math = warp::path!("math" / u16);
let div_with_header = math
.and(warp::get())
.and(div_by())
.map(|num: u16, denom: NonZeroU16| {
warp::reply::json(&Math {
op: format!("{} / {}", num, denom),
output: num / denom.get(),
})
});
let div_with_body =
math.and(warp::post())
.and(warp::body::json())
.map(|num: u16, body: DenomRequest| {
warp::reply::json(&Math {
op: format!("{} / {}", num, body.denom),
output: num / body.denom.get(),
})
});
let routes = div_with_header.or(div_with_body).recover(handle_rejection);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
async fn main() {
pretty_env_logger::init();
// Keep track of all connected users, key is usize, value
// is a websocket sender.
let users = Users::default();
// Turn our "state" into a new Filter...
let users = warp::any().map(move || users.clone());
// GET /chat -> websocket upgrade
let chat = warp::path("chat")
// The `ws()` filter will prepare Websocket handshake...
.and(warp::ws())
.and(users)
.map(|ws: warp::ws::Ws, users| {
// This will call our function if the handshake succeeds.
ws.on_upgrade(move |socket| user_connected(socket, users))
});
// GET / -> index html
let index = warp::path::end().map(|| warp::reply::html(INDEX_HTML));
let routes = index.or(chat);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
async fn main() {
let file = warp::path("todos").and(warp::fs::file("./examples/todos.rs"));
// NOTE: You could double compress something by adding a compression
// filter here, a la
// ```
// let file = warp::path("todos")
// .and(warp::fs::file("./examples/todos.rs"))
// .with(warp::compression::brotli());
// ```
// This would result in a browser error, or downloading a file whose contents
// are compressed
let dir = warp::path("ws_chat").and(warp::fs::file("./examples/websockets_chat.rs"));
let file_and_dir = warp::get()
.and(file.or(dir))
.with(warp::compression::gzip());
let examples = warp::path("ex")
.and(warp::fs::dir("./examples/"))
.with(warp::compression::deflate());
// GET /todos => gzip -> toods.rs
// GET /ws_chat => gzip -> ws_chat.rs
// GET /ex/... => deflate -> ./examples/...
let routes = file_and_dir.or(examples);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
Composes this Filter
with a function receiving the extracted value.
Example
use warp::Filter;
// Map `/:id`
warp::path::param().map(|id: u64| {
format!("Hello #{}", id)
});
Func
The generic Func
trait is implemented for any function that receives
the same arguments as this Filter
extracts. In practice, this
shouldn’t ever bother you, and simply makes things feel more natural.
For example, if three Filter
s were combined together, suppose one
extracts nothing (so ()
), and the other two extract two integers,
a function that accepts exactly two integer arguments is allowed.
Specifically, any Fn(u32, u32)
.
Without Product
and Func
, this would be a lot messier. First of
all, the ()
s couldn’t be discarded, and the tuples would be nested.
So, instead, you’d need to pass an Fn(((), (u32, u32)))
. That’s just
a single argument. Bleck!
Even worse, the tuples would shuffle the types around depending on
the exact invocation of and
s. So, unit.and(int).and(int)
would
result in a different extracted type from unit.and(int.and(int))
,
or from int.and(unit).and(int)
. If you changed around the order
of filters, while still having them be semantically equivalent, you’d
need to update all your map
s as well.
Product
, HList
, and Func
do all the heavy work so that none of
this is a bother to you. What’s more, the types are enforced at
compile-time, and tuple flattening is optimized away to nothing by
LLVM.
Examples found in repository?
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
fn wrap(&self, filter: F) -> Self::Wrapped {
let with = WithHeader_ { with: self.clone() };
filter.map(with)
}
}
/// Wrap a `Filter` to always set multiple headers.
#[derive(Clone, Debug)]
pub struct WithHeaders {
headers: Arc<HeaderMap>,
}
impl<F, R> WrapSealed<F> for WithHeaders
where
F: Filter<Extract = (R,)>,
R: Reply,
{
type Wrapped = Map<F, WithHeaders_>;
fn wrap(&self, filter: F) -> Self::Wrapped {
let with = WithHeaders_ { with: self.clone() };
filter.map(with)
}
}
/// Wrap a `Filter` to set a header if it is not already set.
#[derive(Clone, Debug)]
pub struct WithDefaultHeader {
name: HeaderName,
value: HeaderValue,
}
impl<F, R> WrapSealed<F> for WithDefaultHeader
where
F: Filter<Extract = (R,)>,
R: Reply,
{
type Wrapped = Map<F, WithDefaultHeader_>;
fn wrap(&self, filter: F) -> Self::Wrapped {
let with = WithDefaultHeader_ { with: self.clone() };
filter.map(with)
}
More examples
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
pub fn file(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
let path = Arc::new(path.into());
crate::any()
.map(move || {
tracing::trace!("file: {:?}", path);
ArcPath(path.clone())
})
.and(conditionals())
.and_then(|path, conditionals| file_reply(path, conditionals))
}
/// Creates a `Filter` that serves a directory at the base `path` joined
/// by the request path.
///
/// This can be used to serve "static files" from a directory. By far the most
/// common pattern of serving static files is for `GET` requests, so this
/// filter automatically includes a `GET` check.
///
/// # Example
///
/// ```
/// use warp::Filter;
///
/// // Matches requests that start with `/static`,
/// // and then uses the rest of that path to lookup
/// // and serve a file from `/www/static`.
/// let route = warp::path("static")
/// .and(warp::fs::dir("/www/static"));
///
/// // For example:
/// // - `GET /static/app.js` would serve the file `/www/static/app.js`
/// // - `GET /static/css/app.css` would serve the file `/www/static/css/app.css`
/// ```
pub fn dir(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
let base = Arc::new(path.into());
crate::get()
.and(path_from_tail(base))
.and(conditionals())
.and_then(file_reply)
}
fn path_from_tail(
base: Arc<PathBuf>,
) -> impl FilterClone<Extract = One<ArcPath>, Error = Rejection> {
crate::path::tail().and_then(move |tail: crate::path::Tail| {
future::ready(sanitize_path(base.as_ref(), tail.as_str())).and_then(|mut buf| async {
let is_dir = tokio::fs::metadata(buf.clone())
.await
.map(|m| m.is_dir())
.unwrap_or(false);
if is_dir {
tracing::debug!("dir: appending index.html to directory path");
buf.push("index.html");
}
tracing::trace!("dir: {:?}", buf);
Ok(ArcPath(Arc::new(buf)))
})
})
}
fn sanitize_path(base: impl AsRef<Path>, tail: &str) -> Result<PathBuf, Rejection> {
let mut buf = PathBuf::from(base.as_ref());
let p = match percent_decode_str(tail).decode_utf8() {
Ok(p) => p,
Err(err) => {
tracing::debug!("dir: failed to decode route={:?}: {:?}", tail, err);
return Err(reject::not_found());
}
};
tracing::trace!("dir? base={:?}, route={:?}", base.as_ref(), p);
for seg in p.split('/') {
if seg.starts_with("..") {
tracing::warn!("dir: rejecting segment starting with '..'");
return Err(reject::not_found());
} else if seg.contains('\\') {
tracing::warn!("dir: rejecting segment containing with backslash (\\)");
return Err(reject::not_found());
} else {
buf.push(seg);
}
}
Ok(buf)
}
#[derive(Debug)]
struct Conditionals {
if_modified_since: Option<IfModifiedSince>,
if_unmodified_since: Option<IfUnmodifiedSince>,
if_range: Option<IfRange>,
range: Option<Range>,
}
enum Cond {
NoBody(Response),
WithBody(Option<Range>),
}
impl Conditionals {
fn check(self, last_modified: Option<LastModified>) -> Cond {
if let Some(since) = self.if_unmodified_since {
let precondition = last_modified
.map(|time| since.precondition_passes(time.into()))
.unwrap_or(false);
tracing::trace!(
"if-unmodified-since? {:?} vs {:?} = {}",
since,
last_modified,
precondition
);
if !precondition {
let mut res = Response::new(Body::empty());
*res.status_mut() = StatusCode::PRECONDITION_FAILED;
return Cond::NoBody(res);
}
}
if let Some(since) = self.if_modified_since {
tracing::trace!(
"if-modified-since? header = {:?}, file = {:?}",
since,
last_modified
);
let unmodified = last_modified
.map(|time| !since.is_modified(time.into()))
// no last_modified means its always modified
.unwrap_or(false);
if unmodified {
let mut res = Response::new(Body::empty());
*res.status_mut() = StatusCode::NOT_MODIFIED;
return Cond::NoBody(res);
}
}
if let Some(if_range) = self.if_range {
tracing::trace!("if-range? {:?} vs {:?}", if_range, last_modified);
let can_range = !if_range.is_modified(None, last_modified.as_ref());
if !can_range {
return Cond::WithBody(None);
}
}
Cond::WithBody(self.range)
}
}
fn conditionals() -> impl Filter<Extract = One<Conditionals>, Error = Infallible> + Copy {
crate::header::optional2()
.and(crate::header::optional2())
.and(crate::header::optional2())
.and(crate::header::optional2())
.map(
|if_modified_since, if_unmodified_since, if_range, range| Conditionals {
if_modified_since,
if_unmodified_since,
if_range,
range,
},
)
}
- examples/wrapping.rs
- src/filters/cookie.rs
- examples/headers.rs
- examples/body.rs
- examples/sse.rs
- examples/websockets.rs
- src/filters/multipart.rs
- examples/rejections.rs
- examples/websockets_chat.rs
- src/filters/ws.rs
- examples/handlebars_template.rs
- examples/autoreload.rs
- examples/sse_chat.rs
- examples/query_string.rs
- examples/tracing.rs
- examples/routing.rs
Composes this Filter
with a function receiving the extracted value.
The function should return some TryFuture
type.
The Error
type of the return Future
needs be a Rejection
, which
means most futures will need to have their error mapped into one.
Example
use warp::Filter;
// Validate after `/:id`
warp::path::param().and_then(|id: u64| async move {
if id != 0 {
Ok(format!("Hello #{}", id))
} else {
Err(warp::reject::not_found())
}
});
Examples found in repository?
More examples
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
pub fn todos_list(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("todos")
.and(warp::get())
.and(warp::query::<ListOptions>())
.and(with_db(db))
.and_then(handlers::list_todos)
}
/// POST /todos with JSON body
pub fn todos_create(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("todos")
.and(warp::post())
.and(json_body())
.and(with_db(db))
.and_then(handlers::create_todo)
}
/// PUT /todos/:id with JSON body
pub fn todos_update(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("todos" / u64)
.and(warp::put())
.and(json_body())
.and(with_db(db))
.and_then(handlers::update_todo)
}
/// DELETE /todos/:id
pub fn todos_delete(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
// We'll make one of our endpoints admin-only to show how authentication filters are used
let admin_only = warp::header::exact("authorization", "Bearer admin");
warp::path!("todos" / u64)
// It is important to put the auth check _after_ the path filters.
// If we put the auth check before, the request `PUT /todos/invalid-string`
// would try this filter and reject because the authorization header doesn't match,
// rather because the param is wrong for that other path.
.and(admin_only)
.and(warp::delete())
.and(with_db(db))
.and_then(handlers::delete_todo)
}
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
pub fn file(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
let path = Arc::new(path.into());
crate::any()
.map(move || {
tracing::trace!("file: {:?}", path);
ArcPath(path.clone())
})
.and(conditionals())
.and_then(|path, conditionals| file_reply(path, conditionals))
}
/// Creates a `Filter` that serves a directory at the base `path` joined
/// by the request path.
///
/// This can be used to serve "static files" from a directory. By far the most
/// common pattern of serving static files is for `GET` requests, so this
/// filter automatically includes a `GET` check.
///
/// # Example
///
/// ```
/// use warp::Filter;
///
/// // Matches requests that start with `/static`,
/// // and then uses the rest of that path to lookup
/// // and serve a file from `/www/static`.
/// let route = warp::path("static")
/// .and(warp::fs::dir("/www/static"));
///
/// // For example:
/// // - `GET /static/app.js` would serve the file `/www/static/app.js`
/// // - `GET /static/css/app.css` would serve the file `/www/static/css/app.css`
/// ```
pub fn dir(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
let base = Arc::new(path.into());
crate::get()
.and(path_from_tail(base))
.and(conditionals())
.and_then(file_reply)
}
fn path_from_tail(
base: Arc<PathBuf>,
) -> impl FilterClone<Extract = One<ArcPath>, Error = Rejection> {
crate::path::tail().and_then(move |tail: crate::path::Tail| {
future::ready(sanitize_path(base.as_ref(), tail.as_str())).and_then(|mut buf| async {
let is_dir = tokio::fs::metadata(buf.clone())
.await
.map(|m| m.is_dir())
.unwrap_or(false);
if is_dir {
tracing::debug!("dir: appending index.html to directory path");
buf.push("index.html");
}
tracing::trace!("dir: {:?}", buf);
Ok(ArcPath(Arc::new(buf)))
})
})
}
Compose this Filter
with a function receiving an error.
The function should return some TryFuture
type yielding the
same item and error types.
Examples found in repository?
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
async fn main() {
pretty_env_logger::init();
// get /example1?key=value
// demonstrates an optional parameter.
let example1 = warp::get()
.and(warp::path("example1"))
.and(warp::query::<HashMap<String, String>>())
.map(|p: HashMap<String, String>| match p.get("key") {
Some(key) => Response::builder().body(format!("key = {}", key)),
None => Response::builder().body(String::from("No \"key\" param in query.")),
});
// get /example2?key1=value&key2=42
// uses the query string to populate a custom object
let example2 = warp::get()
.and(warp::path("example2"))
.and(warp::query::<MyObject>())
.map(|p: MyObject| {
Response::builder().body(format!("key1 = {}, key2 = {}", p.key1, p.key2))
});
let opt_query = warp::query::<MyObject>()
.map(Some)
.or_else(|_| async { Ok::<(Option<MyObject>,), std::convert::Infallible>((None,)) });
// get /example3?key1=value&key2=42
// builds on example2 but adds custom error handling
let example3 =
warp::get()
.and(warp::path("example3"))
.and(opt_query)
.map(|p: Option<MyObject>| match p {
Some(obj) => {
Response::builder().body(format!("key1 = {}, key2 = {}", obj.key1, obj.key2))
}
None => Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(String::from("Failed to decode query param.")),
});
warp::serve(example1.or(example2).or(example3))
.run(([127, 0, 0, 1], 3030))
.await
}
Compose this Filter
with a function receiving an error and
returning a new type, instead of the same type.
This is useful for “customizing” rejections into new response types. See also the rejections example.
Examples found in repository?
More examples
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
async fn main() {
let math = warp::path!("math" / u16);
let div_with_header = math
.and(warp::get())
.and(div_by())
.map(|num: u16, denom: NonZeroU16| {
warp::reply::json(&Math {
op: format!("{} / {}", num, denom),
output: num / denom.get(),
})
});
let div_with_body =
math.and(warp::post())
.and(warp::body::json())
.map(|num: u16, body: DenomRequest| {
warp::reply::json(&Math {
op: format!("{} / {}", num, body.denom),
output: num / body.denom.get(),
})
});
let routes = div_with_header.or(div_with_body).recover(handle_rejection);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
Unifies the extracted value of Filter
s composed with or
.
When a Filter
extracts some Either<T, T>
, where both sides
are the same type, this combinator can be used to grab the
inner value, regardless of which side of Either
it was. This
is useful for values that could be extracted from multiple parts
of a request, and the exact place isn’t important.
Example
use std::net::SocketAddr;
use warp::Filter;
let client_ip = warp::header("x-real-ip")
.or(warp::header("x-forwarded-for"))
.unify()
.map(|ip: SocketAddr| {
// Get the IP from either header,
// and unify into the inner type.
});
Convenience method to remove one layer of tupling.
This is useful for when things like map
don’t return a new value,
but just ()
, since warp will wrap it up into a ((),)
.
Example
use warp::Filter;
let route = warp::path::param()
.map(|num: u64| {
println!("just logging: {}", num);
// returning "nothing"
})
.untuple_one()
.map(|| {
println!("the ((),) was removed");
warp::reply()
});
use warp::Filter;
let route = warp::any()
.map(|| {
// wanting to return a tuple
(true, 33)
})
.untuple_one()
.map(|is_enabled: bool, count: i32| {
println!("untupled: ({}, {})", is_enabled, count);
});
Examples found in repository?
More examples
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
fn hello_wrapper<F, T>(
filter: F,
) -> impl Filter<Extract = (&'static str,)> + Clone + Send + Sync + 'static
where
F: Filter<Extract = (T,), Error = std::convert::Infallible> + Clone + Send + Sync + 'static,
F::Extract: warp::Reply,
{
warp::any()
.map(|| {
println!("before filter");
})
.untuple_one()
.and(filter)
.map(|_arg| "wrapped hello world")
}
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
pub fn content_length_limit(limit: u64) -> impl Filter<Extract = (), Error = Rejection> + Copy {
crate::filters::header::header2()
.map_err(crate::filter::Internal, |_| {
tracing::debug!("content-length missing");
reject::length_required()
})
.and_then(move |ContentLength(length)| {
if length <= limit {
future::ok(())
} else {
tracing::debug!("content-length: {} is over limit {}", length, limit);
future::err(reject::payload_too_large())
}
})
.untuple_one()
}
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
pub fn ws() -> impl Filter<Extract = One<Ws>, Error = Rejection> + Copy {
let connection_has_upgrade = header::header2()
.and_then(|conn: ::headers::Connection| {
if conn.contains("upgrade") {
future::ok(())
} else {
future::err(crate::reject::known(MissingConnectionUpgrade))
}
})
.untuple_one();
crate::get()
.and(connection_has_upgrade)
.and(header::exact_ignore_case("upgrade", "websocket"))
.and(header::exact("sec-websocket-version", "13"))
//.and(header::exact2(Upgrade::websocket()))
//.and(header::exact2(SecWebsocketVersion::V13))
.and(header::header2::<SecWebsocketKey>())
.and(on_upgrade())
.map(
move |key: SecWebsocketKey, on_upgrade: Option<OnUpgrade>| Ws {
config: None,
key,
on_upgrade,
},
)
}
Wraps the current filter with some wrapper.
The wrapper may do some preparation work before starting this filter, and may do post-processing after the filter completes.
Example
use warp::Filter;
let route = warp::any()
.map(warp::reply);
// Wrap the route with a log wrapper.
let route = route.with(warp::log("example"));
Examples found in repository?
More examples
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
async fn main() {
if env::var_os("RUST_LOG").is_none() {
// Set `RUST_LOG=todos=debug` to see debug logs,
// this only shows access logs.
env::set_var("RUST_LOG", "todos=info");
}
pretty_env_logger::init();
let db = models::blank_db();
let api = filters::todos(db);
// View access logs by setting `RUST_LOG=todos`.
let routes = api.with(warp::log("todos"));
// Start up the server...
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
async fn main() {
let file = warp::path("todos").and(warp::fs::file("./examples/todos.rs"));
// NOTE: You could double compress something by adding a compression
// filter here, a la
// ```
// let file = warp::path("todos")
// .and(warp::fs::file("./examples/todos.rs"))
// .with(warp::compression::brotli());
// ```
// This would result in a browser error, or downloading a file whose contents
// are compressed
let dir = warp::path("ws_chat").and(warp::fs::file("./examples/websockets_chat.rs"));
let file_and_dir = warp::get()
.and(file.or(dir))
.with(warp::compression::gzip());
let examples = warp::path("ex")
.and(warp::fs::dir("./examples/"))
.with(warp::compression::deflate());
// GET /todos => gzip -> toods.rs
// GET /ws_chat => gzip -> ws_chat.rs
// GET /ex/... => deflate -> ./examples/...
let routes = file_and_dir.or(examples);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
async fn main() {
// Filter traces based on the RUST_LOG env var, or, if it's not set,
// default to show the output of the example.
let filter = std::env::var("RUST_LOG").unwrap_or_else(|_| "tracing=info,warp=debug".to_owned());
// Configure the default `tracing` subscriber.
// The `fmt` subscriber from the `tracing-subscriber` crate logs `tracing`
// events to stdout. Other subscribers are available for integrating with
// distributed tracing systems such as OpenTelemetry.
tracing_subscriber::fmt()
// Use the filter we built above to determine which traces to record.
.with_env_filter(filter)
// Record an event when each span closes. This can be used to time our
// routes' durations!
.with_span_events(FmtSpan::CLOSE)
.init();
let hello = warp::path("hello")
.and(warp::get())
// When the `hello` route is called, emit a `tracing` event.
.map(|| {
tracing::info!("saying hello...");
"Hello, World!"
})
// Wrap the route in a `tracing` span to add the route's name as context
// to any events that occur inside it.
.with(warp::trace::named("hello"));
let goodbye = warp::path("goodbye")
.and(warp::get())
.map(|| {
tracing::info!("saying goodbye...");
"So long and thanks for all the fish!"
})
// We can also provide our own custom `tracing` spans to wrap a route.
.with(warp::trace(|info| {
// Construct our own custom span for this route.
tracing::info_span!("goodbye", req.path = ?info.path())
}));
let routes = hello
.or(goodbye)
// Wrap all the routes with a filter that creates a `tracing` span for
// each request we receive, including data about the request.
.with(warp::trace::request());
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
Boxes this filter into a trait object, making it easier to name the type.
Example
use warp::Filter;
fn impl_reply() -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
warp::any()
.map(warp::reply)
.boxed()
}
fn named_i32() -> warp::filters::BoxedFilter<(i32,)> {
warp::path::param::<i32>()
.boxed()
}
fn named_and() -> warp::filters::BoxedFilter<(i32, String)> {
warp::path::param::<i32>()
.and(warp::header::<String>("host"))
.boxed()
}