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 Filters 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?
examples/returning.rs (line 8)
7
8
9
pub fn assets_filter() -> BoxedFilter<(impl Reply,)> {
    warp::path("assets").and(warp::fs::dir("./assets")).boxed()
}
More examples
Hide additional examples
examples/todos.rs (line 53)
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())
    }
src/filters/fs.rs (line 55)
 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,
            },
        )
}
src/filters/body.rs (line 175)
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 })
            })
        })
}
examples/wrapping.rs (line 16)
 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")
}
examples/file.rs (line 10)
 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?
examples/returning.rs (line 18)
17
18
19
20
async fn main() {
    let routes = index_filter().or(assets_filter());
    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
More examples
Hide additional examples
examples/todos.rs (line 43)
39
40
41
42
43
44
45
46
    pub fn todos(
        db: Db,
    ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
        todos_list(db.clone())
            .or(todos_create(db.clone()))
            .or(todos_update(db.clone()))
            .or(todos_delete(db))
    }
examples/file.rs (line 18)
 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;
}
examples/rejections.rs (line 36)
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;
}
examples/websockets_chat.rs (line 46)
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;
}
examples/compression.rs (line 21)
 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 Filters 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 ands. 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 maps 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?
src/filters/reply.rs (line 137)
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
Hide additional examples
examples/returning.rs (line 13)
12
13
14
pub fn index_filter() -> impl Filter<Extract = (&'static str,), Error = Rejection> + Clone {
    warp::path::end().map(|| "Index page")
}
examples/todos.rs (line 100)
99
100
101
    fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = std::convert::Infallible> + Clone {
        warp::any().map(move || db.clone())
    }
src/filters/body.rs (line 80)
77
78
79
80
81
pub fn stream(
) -> impl Filter<Extract = (impl Stream<Item = Result<impl Buf, crate::Error>>,), Error = Rejection> + Copy
{
    body().map(|body: Body| BodyStream { body })
}
examples/hello.rs (line 7)
5
6
7
8
9
10
async fn main() {
    // Match any request and return hello world!
    let routes = warp::any().map(|| "Hello, World!");

    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
src/filters/fs.rs (lines 51-54)
 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,
            },
        )
}

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?
examples/dyn_reply.rs (line 14)
13
14
15
16
17
async fn main() {
    let routes = warp::path::param().and_then(dyn_reply);

    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
More examples
Hide additional examples
examples/futures.rs (line 13)
9
10
11
12
13
14
15
16
async fn main() {
    // Match `/:Seconds`...
    let routes = warp::path::param()
        // and_then create a `Future` that will simply wait N seconds...
        .and_then(sleepy);

    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
examples/rejections.rs (lines 43-49)
42
43
44
45
46
47
48
49
50
fn div_by() -> impl Filter<Extract = (NonZeroU16,), Error = Rejection> + Copy {
    warp::header::<u16>("div-by").and_then(|n: u16| async move {
        if let Some(denom) = NonZeroU16::new(n) {
            Ok(denom)
        } else {
            Err(reject::custom(DivideByZero))
        }
    })
}
examples/todos.rs (line 56)
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)
    }
src/filters/fs.rs (line 56)
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)))
        })
    })
}
src/filters/host.rs (lines 26-29)
23
24
25
26
27
28
29
30
31
pub fn exact(expected: &str) -> impl Filter<Extract = (), Error = Rejection> + Clone {
    let expected = Authority::from_str(expected).expect("invalid host/authority");
    optional()
        .and_then(move |option: Option<Authority>| match option {
            Some(authority) if authority == expected => future::ok(()),
            _ => future::err(reject::not_found()),
        })
        .untuple_one()
}

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?
examples/query_string.rs (line 39)
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?
examples/wrapping.rs (line 26)
21
22
23
24
25
26
27
28
29
30
31
async fn main() {
    // Match any request and return hello world!
    let routes = warp::any()
        .map(|| "hello world")
        .boxed()
        .recover(|_err| async { Ok("recovered") })
        // wrap the filter with hello_wrapper
        .with(warp::wrap_fn(hello_wrapper));

    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
More examples
Hide additional examples
examples/rejections.rs (line 36)
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 Filters 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?
src/filters/host.rs (line 30)
23
24
25
26
27
28
29
30
31
pub fn exact(expected: &str) -> impl Filter<Extract = (), Error = Rejection> + Clone {
    let expected = Authority::from_str(expected).expect("invalid host/authority");
    optional()
        .and_then(move |option: Option<Authority>| match option {
            Some(authority) if authority == expected => future::ok(()),
            _ => future::err(reject::not_found()),
        })
        .untuple_one()
}
More examples
Hide additional examples
examples/wrapping.rs (line 15)
 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")
}
src/filters/body.rs (line 65)
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()
}
src/filters/ws.rs (line 52)
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?
examples/wrapping.rs (line 28)
21
22
23
24
25
26
27
28
29
30
31
async fn main() {
    // Match any request and return hello world!
    let routes = warp::any()
        .map(|| "hello world")
        .boxed()
        .recover(|_err| async { Ok("recovered") })
        // wrap the filter with hello_wrapper
        .with(warp::wrap_fn(hello_wrapper));

    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
More examples
Hide additional examples
examples/todos.rs (line 28)
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;
}
examples/compression.rs (line 22)
 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;
}
examples/tracing.rs (line 38)
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()
}
Examples found in repository?
examples/returning.rs (line 8)
7
8
9
pub fn assets_filter() -> BoxedFilter<(impl Reply,)> {
    warp::path("assets").and(warp::fs::dir("./assets")).boxed()
}
More examples
Hide additional examples
examples/wrapping.rs (line 25)
21
22
23
24
25
26
27
28
29
30
31
async fn main() {
    // Match any request and return hello world!
    let routes = warp::any()
        .map(|| "hello world")
        .boxed()
        .recover(|_err| async { Ok("recovered") })
        // wrap the filter with hello_wrapper
        .with(warp::wrap_fn(hello_wrapper));

    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}

Implementors