mirror of
https://github.com/Noratrieb/icefun.git
synced 2026-01-14 12:55:02 +01:00
private
This commit is contained in:
parent
25adea4103
commit
7af1274587
160 changed files with 38999 additions and 4 deletions
1
warp/.github/FUNDING.yml
vendored
Normal file
1
warp/.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
github: [seanmonstar]
|
||||
36
warp/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
warp/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Version**
|
||||
List the versions of all `warp` crates you are using. The easiest way to get
|
||||
this information is using `cargo-tree`.
|
||||
|
||||
`cargo install cargo-tree`
|
||||
(see install here: https://github.com/sfackler/cargo-tree)
|
||||
|
||||
Then:
|
||||
|
||||
`cargo tree | grep warp`
|
||||
|
||||
**Platform**
|
||||
The output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows)
|
||||
|
||||
**Description**
|
||||
Enter your issue details here.
|
||||
One way to structure the description:
|
||||
|
||||
[short summary of the bug]
|
||||
|
||||
I tried this code:
|
||||
|
||||
[code sample that causes the bug]
|
||||
|
||||
I expected to see this happen: [explanation]
|
||||
|
||||
Instead, this happened: [explanation]
|
||||
6
warp/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
6
warp/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://discord.gg/RFsPjyt
|
||||
about: 'Please post your question on the #warp discord channel. You may
|
||||
also be able to find help at https://users.rust-lang.org/.'
|
||||
20
warp/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
warp/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
97
warp/.github/workflows/ci.yml
vendored
Normal file
97
warp/.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check Style
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: cargo fmt -- --check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
test:
|
||||
name: Test
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build: [stable, beta, nightly, tls, no-default-features, compression]
|
||||
|
||||
include:
|
||||
- build: beta
|
||||
rust: beta
|
||||
- build: nightly
|
||||
rust: nightly
|
||||
benches: true
|
||||
- build: tls
|
||||
features: "--features tls"
|
||||
- build: no-default-features
|
||||
features: "--no-default-features"
|
||||
- build: compression
|
||||
features: "--features compression"
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust || 'stable' }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: ${{ matrix.features }}
|
||||
|
||||
- name: Test all benches
|
||||
if: matrix.benches
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --benches ${{ matrix.features }}
|
||||
|
||||
doc:
|
||||
name: Build docs
|
||||
needs: [style, test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: cargo doc
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: rustdoc
|
||||
args: -- -D broken_intra_doc_links
|
||||
6
warp/.gitignore
vendored
Normal file
6
warp/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
.idea/
|
||||
warp.iml
|
||||
324
warp/CHANGELOG.md
Normal file
324
warp/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
### v0.3.3 (September 27, 2022)
|
||||
|
||||
- **Fixes**:
|
||||
- Fix `fs` filters path sanitization to reject colons on Windows.
|
||||
|
||||
### v0.3.2 (November 9, 2021)
|
||||
|
||||
- **Features**:
|
||||
- Add `Filter::then()`, which is like `Filter::map()` in that it's infallible, but is async like `Filter::and_then()`.
|
||||
- Add `redirect::found()` reply helper that returns `302 Found`.
|
||||
- Add `compression-brotli` and `compression-gzip` cargo features to enable only the compression you need.
|
||||
- Allow `HEAD` requests to be served to `fs::dir()` filters.
|
||||
- Allow `path!()` with no arguments.
|
||||
- **Fixes**:
|
||||
- Update private dependencies Tungstenite and Multipart.
|
||||
- Replaces uses of `futures` with `futures-util`, which is a smaller dependency.
|
||||
|
||||
|
||||
### v0.3.1 (March 24, 2021)
|
||||
|
||||
- **Features**:
|
||||
- Add `pong` constructor to websocket messages.
|
||||
- Add `redirect::see_other` and `redirect::permanent` helpers.
|
||||
- **Fixes**:
|
||||
- Fix `fs` filters sometimes having an off-by-one error with range requests.
|
||||
- Fix CORS to allow spaces when checking `Access-Control-Request-Headers`.
|
||||
|
||||
## v0.3.0 (January 19, 2021)
|
||||
|
||||
- **Features**:
|
||||
- Add TLS client authentication support.
|
||||
- Add TLS OCSP stapling support.
|
||||
- Add `From<Reject>` for `Rejection`.
|
||||
- Add `close_frame` accessor to `ws::Message`.
|
||||
- **Changes**:
|
||||
- Update to Tokio v1.
|
||||
- Update to Bytes v1.
|
||||
- Update to hyper v0.14.
|
||||
- Rework `sse` filter to be more like `ws`, with a single `Event` type and builder.
|
||||
- Change `cookie` filter to extract a generic `FromStr` value.
|
||||
|
||||
|
||||
### v0.2.5 (August 31, 2020)
|
||||
|
||||
- **Features**:
|
||||
- Add `wrap_fn`, which can be used to create a `Wrap` from a closure. These in turn are used with `Filter::with()`.
|
||||
- Add `warp::host` filters to deal with `Host`/`:authority` headers.
|
||||
- Relax some lifetime bounds on `Server`.
|
||||
- **Fixes**:
|
||||
- Fix panic when URI doesn't have a slash (for example, `CONNECT foo.bar`).
|
||||
|
||||
### v0.2.4 (July 20, 2020)
|
||||
|
||||
- **Features**:
|
||||
- Add `tracing` internals in place of `log` (log is still emitted for backwards compatibility).
|
||||
- Add `warp::trace` module set of filters to customize `tracing` dianostics.
|
||||
- Add `path` method to `warp::fs::File` reply.
|
||||
- Add `source` implementation for `BodyDeserializeError`.
|
||||
- Make `warp::ws::MissingConnectionUpgrade` rejection public.
|
||||
|
||||
### v0.2.3 (May 19, 2020)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::compression` filters, which will compress response bodies.
|
||||
- Add `warp::header::value()` filter to get a request `HeaderValue`.
|
||||
- Add `request_headers` method to `warp::log::Info`.
|
||||
- Add `max_frame_size` to `warp::ws::Ws` builder.
|
||||
- Add `remote_addr` to `warp::test::RequestBuilder`.
|
||||
- Add `try_bind_with_graceful_shutdown` to `warp::Server` builder.
|
||||
- Add `serve_incoming_with_graceful_shutdown` to `warp::Server` builder.
|
||||
- **Fixes**:
|
||||
- Fix `warp::addr::remote` when used with `Server::tls`.
|
||||
- Fix panic in `warp::path::{peek, tail, full}` filters when the request URI is in authority-form or asterisk-form.
|
||||
|
||||
### v0.2.2 (March 3, 2020)
|
||||
|
||||
- **Features**:
|
||||
- Implement `Reply` for all `Box<T>` where `T: Reply`.
|
||||
- Add `name` methods to `MissingHeader`, `InvalidHeader`, and `MissingCookie` rejections.
|
||||
- Add `warp::ext::optional()` filter that optionally retrieves an extension from the request.
|
||||
- **Fixes**:
|
||||
- Fix the sending of pings when a user sends a `ws::Message::ping()`.
|
||||
|
||||
### v0.2.1 (January 23, 2020)
|
||||
|
||||
- **Features**:
|
||||
- Add `close` and `close_with` constructors to `warp::ws::Message`.
|
||||
- **Fixes**:
|
||||
- Fix `warp::fs` filters using a very small read buffer.
|
||||
|
||||
## v0.2.0 (January 16, 2020)
|
||||
|
||||
- **Features**:
|
||||
- Update to `std::future`, adding `async`/`await` support!
|
||||
- Add `warp::service()` to convert a `Filter` into a `tower::Service`.
|
||||
- Implement `Reply` for `Box<dyn Reply>`.
|
||||
- **Changes**:
|
||||
- Refactored Rejection system (#311).
|
||||
- Change `path!` macro to assume a `path::end()` by default, with explicit `/ ..` to allow building a prefix (#359).
|
||||
- Change `warp::path(str)` to accept any `AsRef<str>` argument.
|
||||
- Rename "2"-suffixed filters and types (`get2` to `get`, `ws2` to `ws`, etc).
|
||||
- `Filter::{or, or_else, recover}` now require `Self::Error=Rejection`. This helps catch filters that didn't make sense (like `warp::any().or(warp::get())`).
|
||||
- Change several `warp::body` filters (#345).
|
||||
- Change `warp::cors()` to return a `warp::cors::Builder` which still implements `Wrap`, but can also `build` a cheaper-to-clone wrapper.
|
||||
- Change `warp::multipart` stream API to allow for errors when streaming.
|
||||
- Change `warp::sse` to no longer return a `Filter`, adds `warp::sse::reply` to do what `Sse::reply` did.
|
||||
- Change `Server::tls()` to return a TLS server builder (#340).
|
||||
- Change internal `warp::never::Never` usage with `std::convert::Infallible`.
|
||||
- Remove `warp::ext::set()` function (#222).
|
||||
- Remove deprecated `warp::cookie::optional_value()`.
|
||||
|
||||
|
||||
### v0.1.20 (September 17, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Implement `Clone` for the `warp::cors` filter.
|
||||
- Add `into_bytes` method for `warp::ws::Message`.
|
||||
|
||||
### v0.1.19 (August 16, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Make `warp::multipart` and `wrap::ws` support optional, though enabled by default.
|
||||
- **Fixes**:
|
||||
- Fix `warp::fs::dir` filter to reject paths containing backslashes.
|
||||
|
||||
### v0.1.18 (July 25, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::multipart` support.
|
||||
|
||||
### v0.1.17 (July 8, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Export all built-in Rejection causes in the `warp::reject` module.
|
||||
- Add `Server::try_bind` as fallible bind methods.
|
||||
|
||||
### v0.1.16 (June 11, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Unseal the `Reply` trait: custom types can now implement `Reply`.
|
||||
- Add `warp::sse::keep_alive()` replacement for `warp::sse::keep()` which allows customizing keep-alive behavior.
|
||||
- Add `warp::log::Info::host()` accessor.
|
||||
- **Fixes**:
|
||||
- Fix `warp::fs` filters from sending some headers for `304` responses.
|
||||
|
||||
### v0.1.15 (April 2, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Add more accessors to `warp::log::Info` type for building custom log formats.
|
||||
- Implement `Reply` for `Cow<'static, str>`.
|
||||
|
||||
### v0.1.14 (March 19, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::header::optional` filter.
|
||||
|
||||
### v0.1.13 (February 13, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Implement `Reply` for `Vec<u8>` and `&'static [u8]`.
|
||||
- Set `content-type` header automatically for string and bytes replies.
|
||||
- Add `expose_headers` to `warp::cors` filter.
|
||||
|
||||
### v0.1.12 (January 29, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Implement `PartialEq`, `Eq`, and `Clone` for `warp::ws::Message`.
|
||||
- **Fixes**:
|
||||
- Fix panic when incoming request URI may not have a path (such as `CONNECT` requests).
|
||||
|
||||
### v0.1.11 (January 14, 2019)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::sse` filters for handling Server-Sent-Events.
|
||||
- Add `allow_headers` to `warp::cors` filter.
|
||||
- **Fixes**:
|
||||
- Fix TLS handshake to close the connection if handshake fails.
|
||||
|
||||
### v0.1.10 (December 17, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Add optional TLS support. Enable the `tls` feature, and then use `Server::tls`.
|
||||
- Add `warp::cors` filter for CORS support.
|
||||
- Add `warp::addr::remote` to access the remote address of a request.
|
||||
- Add `warp::log::custom` to support customizing of access logging.
|
||||
- Add `warp::test::ws` to improve testing Websocket filters.
|
||||
|
||||
### v0.1.9 (October 30, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::ext::get` and `warp::ext::set` to set request extensions.
|
||||
- Add `Filter::untuple_one` to unroll nested tuple layers from extractions.
|
||||
- Add `Ws2::max_send_queue` configuration method.
|
||||
- Add `ws::Message::is_ping` method, and yield pings to user code.
|
||||
- **Fixes**:
|
||||
- Fix panic in debug mode when receiving a websocket ping.
|
||||
|
||||
### v0.1.8 (October 25, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Improved flexibility of `Rejection` system.
|
||||
|
||||
The `Rejection` type can now nest and combine arbitrary rejections,
|
||||
so it is no longer bound to a small set of meanings. The ranking of
|
||||
status codes is still used to determine which rejection gets priority.
|
||||
|
||||
A different priority can be implemented by handling rejections with
|
||||
a `Filter::recover`, and searching for causes in order via
|
||||
`Rejection::find_cause`.
|
||||
- Adds `warp::reject::custom()` to create a `Rejection` with
|
||||
any `Into<Box<std::error::Error>>`. These rejections should be
|
||||
handled with an eventual `Filter::recover`. Any unhandled
|
||||
custom rejections are considered a server error.
|
||||
- Deprecates `Rejection::with`. Use custom rejections instead.
|
||||
- Deprecates `Rejection::into_cause`, as it can no longer work. Always
|
||||
returns `Err(Rejection)`.
|
||||
- Deprecates `Rejection::json`, since the format needed is too generic.
|
||||
The `errors.rs` example shows how to send custom JSON when recovering
|
||||
from rejections.
|
||||
- Deprecates `warp::reject()`, since it current signals a `400 Bad
|
||||
Request`, but in newer versions, it will signal `404 Not Found`.
|
||||
It's deprecated simply to warn that the semantics are changing,
|
||||
but the function won't actually go away.
|
||||
- Deprecates `reject::bad_request()`, `reject::forbidden()`, and
|
||||
`reject::server_error()`. Uses custom rejections instead.
|
||||
- Renamed `warp::path::index` to `warp::path::end`.
|
||||
|
||||
|
||||
### v0.1.7 (October 15, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Export the types returned from the `warp::body::stream()` filter, `BodyStream` and `StreamBuf`.
|
||||
- Deprecated `Rejection::into_cause`, since an upcoming Rejection refactor will make it impossible to support.
|
||||
|
||||
- **Fixes**:
|
||||
- Fix websocket filters to do a case-insensitive match of the `Connection` header.
|
||||
|
||||
### v0.1.6 (October 5, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Add Conditional and Range request support for `warp::fs` filters.
|
||||
- Relaxed bounds on `Rejection::with` to no longer need to be `Sized`.
|
||||
- Add `warp::path::peek()` which gets the unmatched tail without adjusting the currently matched path.
|
||||
|
||||
### v0.1.5 (October 3, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Serve `index.html` automatically with `warp::fs::dir` filter.
|
||||
- Include `last-modified` header with `warp::fs` filters.
|
||||
- Add `warp::redirect` to easily reply with redirections.
|
||||
- Add `warp::reply::{with_status, with_header}` to wrap `impl Reply`s directly with a new status code or header.
|
||||
- Add support for running a warp `Server` with a custom source of incoming connections.
|
||||
- `Server::run_incoming` to have the runtime started automatically.
|
||||
- `Server::serve_incoming` to get a future to run on existing runtime.
|
||||
- These can be used to support Unix Domain Sockets, TLS, and other transports.
|
||||
- Add `Rejection::into_cause()` to retrieve the original error of a rejection back.
|
||||
- Add `Rejection::json()` to convert a rejection into a JSON response.
|
||||
|
||||
- **Fixes**
|
||||
- Internal errors in warp that result in rendering a `500 Internal Server Error` are now also logged at the `error` level.
|
||||
|
||||
|
||||
### v0.1.4 (September 25, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::reply::with::headers(HeaderMap)` filter wrapper.
|
||||
- Add `warp::cookie::optional()` to get an optional cookie value.
|
||||
- Add `warp::path::full()` to be able to extract the full request path without affecting route matching.
|
||||
- Add graceful shutdown support to the `Server`.
|
||||
- Allow empty query strings to be treated as for `warp::query()`.
|
||||
|
||||
### v0.1.3 (August 28, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::reject::forbidden()` to represent `403 Forbidden` responses.
|
||||
- Add `Rejection::with(cause)` to customize rejection messages.
|
||||
- **Fixes**:
|
||||
- Fix `warp::body::form` to allow charsets in the `content-type` header.
|
||||
|
||||
### v0.1.2 (August 14, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Implemented `Reply` for `Response<impl Into<hyper::Body>`, allowing streaming response bodies.
|
||||
- Add `warp::body::stream()` filter to access the request body as an `impl Stream`.
|
||||
- Add `warp::ws2()` as a more flexible websocket filter.
|
||||
- This allows passing other extracted values to the upgrade callback, such as a value from a header or path.
|
||||
- Deprecates `warp::ws()`, and `ws2()` will become `ws()` in 0.2.
|
||||
- Add `warp::get2()`, `warp::post2()`, `warp::put2()`, and `warp::delete2()` as more standard method filters that are used via chaining instead of nesting.
|
||||
- `get()`, `post()`, `put()`, and `delete()` are deprecated, and the new versions will become them in 0.2.
|
||||
- Add `Filter::unify()` for when a filter returns `Either<T, T>`, converting the `Either` into the inner `T`, regardless of which variant it was.
|
||||
- This requires that both sides of the `Either` be the same type.
|
||||
- This can be useful when extracting a value that might be present in different places of the request.
|
||||
|
||||
```rust
|
||||
// Allow `MyId` to be a path parameter or a header...
|
||||
let id = warp::path::param::<MyId>()
|
||||
.or(warp::header::<MyId>())
|
||||
.unify();
|
||||
|
||||
// A way of providing default values...
|
||||
let dnt = warp::header::<bool>("dnt")
|
||||
.or(warp::any().map(|| true))
|
||||
.unify();
|
||||
```
|
||||
- Add `content-type` header automatically to replies from `file` and `dir` filters based on file extension.
|
||||
- Add `warp::head()`, `warp::options()`, and `warp::patch()` as new Method filters.
|
||||
- Try to use OS blocksize in `warp::fs` filters.
|
||||
- **Fixes**:
|
||||
- Chaining filters that try to consume the request body will log that the body is already consumed, and return a `500 Internal Server Error` rejection.
|
||||
|
||||
### v0.1.1 (August 7, 2018)
|
||||
|
||||
- **Features**:
|
||||
- Add `warp::query::raw()` filter to get query as a `String`.
|
||||
- Add `Filter::recover()` to ease customizing of rejected responses.
|
||||
- Add `warp::header::headers_clone()` filter to get a clone of request's `HeaderMap`.
|
||||
- Add `warp::path::tail()` filter to get remaining "tail" of the request path.
|
||||
- **Fixes**:
|
||||
- URL decode path segments in `warp::fs` filters.
|
||||
|
||||
|
||||
## v0.1.0 (August 1, 2018)
|
||||
|
||||
- Initial release.
|
||||
98
warp/Cargo.toml
Normal file
98
warp/Cargo.toml
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
[package]
|
||||
name = "warp"
|
||||
version = "0.3.3" # don't forget to update html_root_url
|
||||
description = "serve the web at warp speeds"
|
||||
authors = ["Sean McArthur <sean@seanmonstar.com>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/warp"
|
||||
repository = "https://github.com/seanmonstar/warp"
|
||||
categories = ["web-programming::http-server"]
|
||||
keywords = ["warp", "server", "http", "hyper"]
|
||||
autotests = true
|
||||
autoexamples = true
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
async-compression = { version = "0.3.7", features = ["tokio"], optional = true }
|
||||
bytes = "1.0"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink"] }
|
||||
futures-channel = { version = "0.3.17", features = ["sink"]}
|
||||
headers = "0.3"
|
||||
http = "0.2"
|
||||
hyper = { version = "0.14", features = ["stream", "server", "http1", "http2", "tcp", "client"] }
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
mime_guess = "2.0.0"
|
||||
multipart = { version = "0.18", default-features = false, features = ["server"], optional = true }
|
||||
scoped-tls = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
tokio = { version = "1.0", features = ["fs", "sync", "time"] }
|
||||
tokio-stream = "0.1.1"
|
||||
tokio-util = { version = "0.7", features = ["io"] }
|
||||
tracing = { version = "0.1.21", default-features = false, features = ["log", "std"] }
|
||||
tower-service = "0.3"
|
||||
tokio-tungstenite = { version = "0.17", optional = true }
|
||||
percent-encoding = "2.1"
|
||||
pin-project = "1.0"
|
||||
tokio-rustls = { version = "0.23", optional = true }
|
||||
rustls-pemfile = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_env_logger = "0.4"
|
||||
tracing-subscriber = "0.2.7"
|
||||
tracing-log = "0.1"
|
||||
serde_derive = "1.0"
|
||||
handlebars = "4.0"
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
tokio-stream = { version = "0.1.1", features = ["net"] }
|
||||
listenfd = "0.3"
|
||||
|
||||
[features]
|
||||
default = ["multipart", "websocket"]
|
||||
websocket = ["tokio-tungstenite"]
|
||||
tls = ["tokio-rustls"]
|
||||
|
||||
# Enable compression-related filters
|
||||
compression = ["compression-brotli", "compression-gzip"]
|
||||
compression-brotli = ["async-compression/brotli"]
|
||||
compression-gzip = ["async-compression/deflate", "async-compression/gzip"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
incremental = false
|
||||
|
||||
[profile.bench]
|
||||
codegen-units = 1
|
||||
incremental = false
|
||||
|
||||
[[test]]
|
||||
name = "multipart"
|
||||
required-features = ["multipart"]
|
||||
|
||||
[[test]]
|
||||
name = "ws"
|
||||
required-features = ["websocket"]
|
||||
|
||||
[[example]]
|
||||
name = "compression"
|
||||
required-features = ["compression"]
|
||||
|
||||
[[example]]
|
||||
name = "unix_socket"
|
||||
|
||||
[[example]]
|
||||
name = "websockets"
|
||||
required-features = ["websocket"]
|
||||
|
||||
[[example]]
|
||||
name = "websockets_chat"
|
||||
required-features = ["websocket"]
|
||||
|
||||
[[example]]
|
||||
name = "query_string"
|
||||
20
warp/LICENSE
Normal file
20
warp/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2018-2020 Sean McArthur
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
63
warp/README.md
Normal file
63
warp/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# warp
|
||||
|
||||
[](https://crates.io/crates/warp)
|
||||
[](https://docs.rs/warp)
|
||||
[](./LICENSE)
|
||||
[](https://github.com/seanmonstar/warp/actions?query=workflow%3ACI)
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
A super-easy, composable, web server framework for warp speeds.
|
||||
|
||||
The fundamental building block of `warp` is the `Filter`: they can be combined
|
||||
and composed to express rich requirements on requests.
|
||||
|
||||
Thanks to its `Filter` system, warp provides these out of the box:
|
||||
|
||||
* Path routing and parameter extraction
|
||||
* Header requirements and extraction
|
||||
* Query string deserialization
|
||||
* JSON and Form bodies
|
||||
* Multipart form data
|
||||
* Static Files and Directories
|
||||
* Websockets
|
||||
* Access logging
|
||||
* Gzip, Deflate, and Brotli compression
|
||||
|
||||
Since it builds on top of [hyper](https://hyper.rs), you automatically get:
|
||||
|
||||
- HTTP/1
|
||||
- HTTP/2
|
||||
- Asynchronous
|
||||
- One of the fastest HTTP implementations
|
||||
- Tested and **correct**
|
||||
|
||||
## Example
|
||||
|
||||
Add warp and Tokio to your dependencies:
|
||||
|
||||
```toml
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
warp = "0.3"
|
||||
```
|
||||
|
||||
And then get started in your `main.rs`:
|
||||
|
||||
```rust
|
||||
use warp::Filter;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// GET /hello/warp => 200 OK with body "Hello, warp!"
|
||||
let hello = warp::path!("hello" / String)
|
||||
.map(|name| format!("Hello, {}!", name));
|
||||
|
||||
warp::serve(hello)
|
||||
.run(([127, 0, 0, 1], 3030))
|
||||
.await;
|
||||
}
|
||||
```
|
||||
|
||||
For more information you can check the [docs](https://docs.rs/warp) or the [examples](https://github.com/seanmonstar/warp/tree/master/examples).
|
||||
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord
|
||||
[discord-url]: https://discord.gg/RFsPjyt
|
||||
50
warp/src/error.rs
Normal file
50
warp/src/error.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use std::convert::Infallible;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||
/// Errors that can happen inside warp.
|
||||
pub struct Error {
|
||||
inner: BoxError,
|
||||
}
|
||||
impl Error {
|
||||
pub(crate) fn new<E: Into<BoxError>>(err: E) -> Error {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for Error {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl From<Infallible> for Error {
|
||||
fn from(infallible: Infallible) -> Error {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn error_size_of() {
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn error_source() {
|
||||
loop {}
|
||||
}
|
||||
macro_rules! unit_error {
|
||||
($(#[$docs:meta])* $pub:vis $typ:ident : $display:literal) => {
|
||||
$(#[$docs])* $pub struct $typ { _p : (), } impl ::std::fmt::Debug for $typ { fn
|
||||
fmt(& self, f : & mut ::std::fmt::Formatter <'_ >) -> ::std::fmt::Result { f
|
||||
.debug_struct(stringify!($typ)).finish() } } impl ::std::fmt::Display for $typ {
|
||||
fn fmt(& self, f : & mut ::std::fmt::Formatter <'_ >) -> ::std::fmt::Result { f
|
||||
.write_str($display) } } impl ::std::error::Error for $typ {}
|
||||
};
|
||||
}
|
||||
72
warp/src/filter/and.rs
Normal file
72
warp/src/filter/and.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use pin_project::pin_project;
|
||||
use super::{Combine, Filter, FilterBase, Internal, Tuple};
|
||||
use crate::generic::CombinedTuples;
|
||||
use crate::reject::CombineRejection;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct And<T, U> {
|
||||
pub(super) first: T,
|
||||
pub(super) second: U,
|
||||
}
|
||||
impl<T, U> FilterBase for And<T, U>
|
||||
where
|
||||
T: Filter,
|
||||
T::Extract: Send,
|
||||
U: Filter + Clone + Send,
|
||||
<T::Extract as Tuple>::HList: Combine<<U::Extract as Tuple>::HList> + Send,
|
||||
CombinedTuples<T::Extract, U::Extract>: Send,
|
||||
U::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
type Extract = CombinedTuples<T::Extract, U::Extract>;
|
||||
type Error = <U::Error as CombineRejection<T::Error>>::One;
|
||||
type Future = AndFuture<T, U>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct AndFuture<T: Filter, U: Filter> {
|
||||
#[pin]
|
||||
state: State<T::Future, T::Extract, U>,
|
||||
}
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T, TE, U: Filter> {
|
||||
First(#[pin] T, U),
|
||||
Second(Option<TE>, #[pin] U::Future),
|
||||
Done,
|
||||
}
|
||||
impl<T, U> Future for AndFuture<T, U>
|
||||
where
|
||||
T: Filter,
|
||||
U: Filter,
|
||||
<T::Extract as Tuple>::HList: Combine<<U::Extract as Tuple>::HList> + Send,
|
||||
U::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
type Output = Result<
|
||||
CombinedTuples<T::Extract, U::Extract>,
|
||||
<U::Error as CombineRejection<T::Error>>::One,
|
||||
>;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T, TE, U, E> Future for State<T, TE, U>
|
||||
where
|
||||
T: Future<Output = Result<TE, E>>,
|
||||
U: Filter,
|
||||
TE: Tuple,
|
||||
TE::HList: Combine<<U::Extract as Tuple>::HList> + Send,
|
||||
U::Error: CombineRejection<E>,
|
||||
{
|
||||
type Output = Result<
|
||||
CombinedTuples<TE, U::Extract>,
|
||||
<U::Error as CombineRejection<E>>::One,
|
||||
>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
81
warp/src/filter/and_then.rs
Normal file
81
warp/src/filter/and_then.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use futures_util::{TryFuture};
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Func, Internal};
|
||||
use crate::reject::CombineRejection;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AndThen<T, F> {
|
||||
pub(super) filter: T,
|
||||
pub(super) callback: F,
|
||||
}
|
||||
impl<T, F> FilterBase for AndThen<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract> + Clone + Send,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
type Extract = (<F::Output as TryFuture>::Ok,);
|
||||
type Error = <<F::Output as TryFuture>::Error as CombineRejection<T::Error>>::One;
|
||||
type Future = AndThenFuture<T, F>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct AndThenFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract>,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T::Future, F>,
|
||||
}
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T, F>
|
||||
where
|
||||
T: TryFuture,
|
||||
F: Func<T::Ok>,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
First(#[pin] T, F),
|
||||
Second(#[pin] F::Output),
|
||||
Done,
|
||||
}
|
||||
impl<T, F> Future for AndThenFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract>,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
type Output = Result<
|
||||
(<F::Output as TryFuture>::Ok,),
|
||||
<<F::Output as TryFuture>::Error as CombineRejection<T::Error>>::One,
|
||||
>;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T, F> Future for State<T, F>
|
||||
where
|
||||
T: TryFuture,
|
||||
F: Func<T::Ok>,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
type Output = Result<
|
||||
(<F::Output as TryFuture>::Ok,),
|
||||
<<F::Output as TryFuture>::Error as CombineRejection<T::Error>>::One,
|
||||
>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
82
warp/src/filter/boxed.rs
Normal file
82
warp/src/filter/boxed.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{Filter, FilterBase, Internal, Tuple};
|
||||
use crate::reject::Rejection;
|
||||
/// A type representing a boxed `Filter` trait object.
|
||||
///
|
||||
/// The filter inside is a dynamic trait object. The purpose of this type is
|
||||
/// to ease returning `Filter`s from other functions.
|
||||
///
|
||||
/// To create one, call `Filter::boxed` on any filter.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Filter, filters::BoxedFilter, Reply};
|
||||
///
|
||||
/// pub fn assets_filter() -> BoxedFilter<(impl Reply,)> {
|
||||
/// warp::path("assets")
|
||||
/// .and(warp::fs::dir("./assets"))
|
||||
/// .boxed()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub struct BoxedFilter<T: Tuple> {
|
||||
filter: Arc<
|
||||
dyn Filter<
|
||||
Extract = T,
|
||||
Error = Rejection,
|
||||
Future = Pin<Box<dyn Future<Output = Result<T, Rejection>> + Send>>,
|
||||
> + Send + Sync,
|
||||
>,
|
||||
}
|
||||
impl<T: Tuple + Send> BoxedFilter<T> {
|
||||
pub(super) fn new<F>(filter: F) -> BoxedFilter<T>
|
||||
where
|
||||
F: Filter<Extract = T> + Send + Sync + 'static,
|
||||
F::Error: Into<Rejection>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: Tuple> Clone for BoxedFilter<T> {
|
||||
fn clone(&self) -> BoxedFilter<T> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: Tuple> fmt::Debug for BoxedFilter<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
fn _assert_send() {
|
||||
loop {}
|
||||
}
|
||||
impl<T: Tuple + Send> FilterBase for BoxedFilter<T> {
|
||||
type Extract = T;
|
||||
type Error = Rejection;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<T, Rejection>> + Send>>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
struct BoxingFilter<F> {
|
||||
filter: F,
|
||||
}
|
||||
impl<F> FilterBase for BoxingFilter<F>
|
||||
where
|
||||
F: Filter,
|
||||
F::Future: Send + 'static,
|
||||
{
|
||||
type Extract = F::Extract;
|
||||
type Error = F::Error;
|
||||
type Future = Pin<
|
||||
Box<dyn Future<Output = Result<Self::Extract, Self::Error>> + Send>,
|
||||
>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
42
warp/src/filter/map.rs
Normal file
42
warp/src/filter/map.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Func, Internal};
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Map<T, F> {
|
||||
pub(super) filter: T,
|
||||
pub(super) callback: F,
|
||||
}
|
||||
impl<T, F> FilterBase for Map<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract> + Clone + Send,
|
||||
{
|
||||
type Extract = (F::Output,);
|
||||
type Error = T::Error;
|
||||
type Future = MapFuture<T, F>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct MapFuture<T: Filter, F> {
|
||||
#[pin]
|
||||
extract: T::Future,
|
||||
callback: F,
|
||||
}
|
||||
impl<T, F> Future for MapFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract>,
|
||||
{
|
||||
type Output = Result<(F::Output,), T::Error>;
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
44
warp/src/filter/map_err.rs
Normal file
44
warp/src/filter/map_err.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Internal};
|
||||
use crate::reject::IsReject;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MapErr<T, F> {
|
||||
pub(super) filter: T,
|
||||
pub(super) callback: F,
|
||||
}
|
||||
impl<T, F, E> FilterBase for MapErr<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Fn(T::Error) -> E + Clone + Send,
|
||||
E: IsReject,
|
||||
{
|
||||
type Extract = T::Extract;
|
||||
type Error = E;
|
||||
type Future = MapErrFuture<T, F>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct MapErrFuture<T: Filter, F> {
|
||||
#[pin]
|
||||
extract: T::Future,
|
||||
callback: F,
|
||||
}
|
||||
impl<T, F, E> Future for MapErrFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Fn(T::Error) -> E,
|
||||
{
|
||||
type Output = Result<T::Extract, E>;
|
||||
#[inline]
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
425
warp/src/filter/mod.rs
Normal file
425
warp/src/filter/mod.rs
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
mod and;
|
||||
mod and_then;
|
||||
mod boxed;
|
||||
mod map;
|
||||
mod map_err;
|
||||
mod or;
|
||||
mod or_else;
|
||||
mod recover;
|
||||
pub(crate) mod service;
|
||||
mod then;
|
||||
mod unify;
|
||||
mod untuple_one;
|
||||
mod wrap;
|
||||
use std::future::Future;
|
||||
use futures_util::{future, TryFuture, TryFutureExt};
|
||||
pub(crate) use crate::generic::{one, Combine, Either, Func, One, Tuple};
|
||||
use crate::reject::{CombineRejection, IsReject, Rejection};
|
||||
use crate::route::{Route};
|
||||
pub(crate) use self::and::And;
|
||||
use self::and_then::AndThen;
|
||||
pub use self::boxed::BoxedFilter;
|
||||
pub(crate) use self::map::Map;
|
||||
pub(crate) use self::map_err::MapErr;
|
||||
pub(crate) use self::or::Or;
|
||||
use self::or_else::OrElse;
|
||||
use self::recover::Recover;
|
||||
use self::then::Then;
|
||||
use self::unify::Unify;
|
||||
use self::untuple_one::UntupleOne;
|
||||
pub use self::wrap::wrap_fn;
|
||||
pub(crate) use self::wrap::{Wrap, WrapSealed};
|
||||
pub trait FilterBase {
|
||||
type Extract: Tuple;
|
||||
type Error: IsReject;
|
||||
type Future: Future<Output = Result<Self::Extract, Self::Error>> + Send;
|
||||
fn filter(&self, internal: Internal) -> Self::Future;
|
||||
fn map_err<F, E>(self, _internal: Internal, fun: F) -> MapErr<Self, F>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(Self::Error) -> E + Clone,
|
||||
E: ::std::fmt::Debug + Send,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Internal;
|
||||
/// 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.
|
||||
pub trait Filter: FilterBase {
|
||||
/// 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>());
|
||||
/// ```
|
||||
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>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// 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>());
|
||||
/// ```
|
||||
fn or<F>(self, other: F) -> Or<Self, F>
|
||||
where
|
||||
Self: Filter<Error = Rejection> + Sized,
|
||||
F: Filter,
|
||||
F::Error: CombineRejection<Self::Error>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// 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.
|
||||
fn map<F>(self, fun: F) -> Map<Self, F>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Func<Self::Extract> + Clone,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Composes this `Filter` with an async function receiving
|
||||
/// the extracted value.
|
||||
///
|
||||
/// The function should return some `Future` type.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Map `/:id`
|
||||
/// warp::path::param().then(|id: u64| async move {
|
||||
/// format!("Hello #{}", id)
|
||||
/// });
|
||||
/// ```
|
||||
fn then<F>(self, fun: F) -> Then<Self, F>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Func<Self::Extract> + Clone,
|
||||
F::Output: Future + Send,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Composes this `Filter` with a fallible async 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.
|
||||
///
|
||||
/// Rejections are meant to say "this filter didn't accept the request,
|
||||
/// maybe another can". So for application-level errors, consider using
|
||||
/// [`Filter::then`] instead.
|
||||
///
|
||||
/// # 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())
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
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>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Compose this `Filter` with a function receiving an error.
|
||||
///
|
||||
/// The function should return some `TryFuture` type yielding the
|
||||
/// same item and error types.
|
||||
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,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// 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][ex].
|
||||
///
|
||||
/// [ex]: https://github.com/seanmonstar/warp/blob/master/examples/rejections.rs
|
||||
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,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// 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
|
||||
///
|
||||
/// ```rust
|
||||
/// 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.
|
||||
/// });
|
||||
/// ```
|
||||
fn unify<T>(self) -> Unify<Self>
|
||||
where
|
||||
Self: Filter<Extract = (Either<T, T>,)> + Sized,
|
||||
T: Tuple,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// 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);
|
||||
/// });
|
||||
/// ```
|
||||
fn untuple_one<T>(self) -> UntupleOne<Self>
|
||||
where
|
||||
Self: Filter<Extract = (T,)> + Sized,
|
||||
T: Tuple,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// 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"));
|
||||
/// ```
|
||||
fn with<W>(self, wrapper: W) -> W::Wrapped
|
||||
where
|
||||
Self: Sized,
|
||||
W: Wrap<Self>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// 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()
|
||||
/// }
|
||||
/// ```
|
||||
fn boxed(self) -> BoxedFilter<Self::Extract>
|
||||
where
|
||||
Self: Sized + Send + Sync + 'static,
|
||||
Self::Extract: Send,
|
||||
Self::Error: Into<Rejection>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: FilterBase> Filter for T {}
|
||||
pub trait FilterClone: Filter + Clone {}
|
||||
impl<T: Filter + Clone> FilterClone for T {}
|
||||
fn _assert_object_safe() {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn filter_fn<F, U>(func: F) -> FilterFn<F>
|
||||
where
|
||||
F: Fn(&mut Route) -> U,
|
||||
U: TryFuture,
|
||||
U::Ok: Tuple,
|
||||
U::Error: IsReject,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn filter_fn_one<F, U>(
|
||||
func: F,
|
||||
) -> impl Filter<Extract = (U::Ok,), Error = U::Error> + Copy
|
||||
where
|
||||
F: Fn(&mut Route) -> U + Copy,
|
||||
U: TryFuture + Send + 'static,
|
||||
U::Ok: Send,
|
||||
U::Error: IsReject,
|
||||
{
|
||||
filter_fn(move |route| func(route).map_ok(|item| (item,)))
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub(crate) struct FilterFn<F> {
|
||||
func: F,
|
||||
}
|
||||
impl<F, U> FilterBase for FilterFn<F>
|
||||
where
|
||||
F: Fn(&mut Route) -> U,
|
||||
U: TryFuture + Send + 'static,
|
||||
U::Ok: Tuple + Send,
|
||||
U::Error: IsReject,
|
||||
{
|
||||
type Extract = U::Ok;
|
||||
type Error = U::Error;
|
||||
type Future = future::IntoFuture<U>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
62
warp/src/filter/or.rs
Normal file
62
warp/src/filter/or.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Internal};
|
||||
use crate::generic::Either;
|
||||
use crate::reject::CombineRejection;
|
||||
|
||||
type Combined<E1, E2> = <E1 as CombineRejection<E2>>::Combined;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Or<T, U> {
|
||||
pub(super) first: T,
|
||||
pub(super) second: U,
|
||||
}
|
||||
impl<T, U> FilterBase for Or<T, U>
|
||||
where
|
||||
T: Filter,
|
||||
U: Filter + Clone + Send,
|
||||
U::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
type Extract = (Either<T::Extract, U::Extract>,);
|
||||
type Error = Combined<U::Error, T::Error>;
|
||||
type Future = EitherFuture<T, U>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct EitherFuture<T: Filter, U: Filter> {
|
||||
#[pin]
|
||||
state: State<T, U>,
|
||||
original_path_index: PathIndex,
|
||||
}
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T: Filter, U: Filter> {
|
||||
First(#[pin] T::Future, U),
|
||||
Second(Option<T::Error>, #[pin] U::Future),
|
||||
Done,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
struct PathIndex(usize);
|
||||
impl PathIndex {
|
||||
fn reset_path(&self) {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T, U> Future for EitherFuture<T, U>
|
||||
where
|
||||
T: Filter,
|
||||
U: Filter,
|
||||
U::Error: CombineRejection<T::Error>,
|
||||
{
|
||||
type Output = Result<
|
||||
(Either<T::Extract, U::Extract>,),
|
||||
Combined<U::Error, T::Error>,
|
||||
>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
69
warp/src/filter/or_else.rs
Normal file
69
warp/src/filter/or_else.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use futures_util::{TryFuture};
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Func, Internal};
|
||||
use crate::reject::IsReject;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct OrElse<T, F> {
|
||||
pub(super) filter: T,
|
||||
pub(super) callback: F,
|
||||
}
|
||||
impl<T, F> FilterBase for OrElse<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error> + Clone + Send,
|
||||
F::Output: TryFuture<Ok = T::Extract> + Send,
|
||||
<F::Output as TryFuture>::Error: IsReject,
|
||||
{
|
||||
type Extract = <F::Output as TryFuture>::Ok;
|
||||
type Error = <F::Output as TryFuture>::Error;
|
||||
type Future = OrElseFuture<T, F>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct OrElseFuture<T: Filter, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error>,
|
||||
F::Output: TryFuture<Ok = T::Extract> + Send,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T, F>,
|
||||
original_path_index: PathIndex,
|
||||
}
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error>,
|
||||
F::Output: TryFuture<Ok = T::Extract> + Send,
|
||||
{
|
||||
First(#[pin] T::Future, F),
|
||||
Second(#[pin] F::Output),
|
||||
Done,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
struct PathIndex(usize);
|
||||
impl PathIndex {
|
||||
fn reset_path(&self) {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T, F> Future for OrElseFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error>,
|
||||
F::Output: TryFuture<Ok = T::Extract> + Send,
|
||||
{
|
||||
type Output = Result<<F::Output as TryFuture>::Ok, <F::Output as TryFuture>::Error>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
76
warp/src/filter/recover.rs
Normal file
76
warp/src/filter/recover.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use futures_util::{TryFuture};
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Func, Internal};
|
||||
use crate::generic::Either;
|
||||
use crate::reject::IsReject;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Recover<T, F> {
|
||||
pub(super) filter: T,
|
||||
pub(super) callback: F,
|
||||
}
|
||||
impl<T, F> FilterBase for Recover<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error> + Clone + Send,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: IsReject,
|
||||
{
|
||||
type Extract = (Either<T::Extract, (<F::Output as TryFuture>::Ok,)>,);
|
||||
type Error = <F::Output as TryFuture>::Error;
|
||||
type Future = RecoverFuture<T, F>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct RecoverFuture<T: Filter, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error>,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: IsReject,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T, F>,
|
||||
original_path_index: PathIndex,
|
||||
}
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error>,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: IsReject,
|
||||
{
|
||||
First(#[pin] T::Future, F),
|
||||
Second(#[pin] F::Output),
|
||||
Done,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
struct PathIndex(usize);
|
||||
impl PathIndex {
|
||||
fn reset_path(&self) {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T, F> Future for RecoverFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Error>,
|
||||
F::Output: TryFuture + Send,
|
||||
<F::Output as TryFuture>::Error: IsReject,
|
||||
{
|
||||
type Output = Result<
|
||||
(Either<T::Extract, (<F::Output as TryFuture>::Ok,)>,),
|
||||
<F::Output as TryFuture>::Error,
|
||||
>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
111
warp/src/filter/service.rs
Normal file
111
warp/src/filter/service.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use std::convert::Infallible;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use futures_util::future::TryFuture;
|
||||
use hyper::service::Service;
|
||||
use pin_project::pin_project;
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::{Reply, Response};
|
||||
use crate::route::{Route};
|
||||
use crate::{Filter, Request};
|
||||
/// Convert a `Filter` into a `Service`.
|
||||
///
|
||||
/// Filters are normally what APIs are built on in warp. However, it can be
|
||||
/// useful to convert a `Filter` into a [`Service`][Service], such as if
|
||||
/// further customizing a `hyper::Service`, or if wanting to make use of
|
||||
/// the greater [Tower][tower] set of middleware.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Running a `warp::Filter` on a regular `hyper::Server`:
|
||||
///
|
||||
/// ```
|
||||
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use std::convert::Infallible;
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Our Filter...
|
||||
/// let route = warp::any().map(|| "Hello From Warp!");
|
||||
///
|
||||
/// // Convert it into a `Service`...
|
||||
/// let svc = warp::service(route);
|
||||
///
|
||||
/// // Typical hyper setup...
|
||||
/// let make_svc = hyper::service::make_service_fn(move |_| async move {
|
||||
/// Ok::<_, Infallible>(svc)
|
||||
/// });
|
||||
///
|
||||
/// hyper::Server::bind(&([127, 0, 0, 1], 3030).into())
|
||||
/// .serve(make_svc)
|
||||
/// .await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [Service]: https://docs.rs/hyper/0.13.*/hyper/service/trait.Service.html
|
||||
/// [tower]: https://docs.rs/tower
|
||||
pub fn service<F>(filter: F) -> FilteredService<F>
|
||||
where
|
||||
F: Filter,
|
||||
<F::Future as TryFuture>::Ok: Reply,
|
||||
<F::Future as TryFuture>::Error: IsReject,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct FilteredService<F> {
|
||||
filter: F,
|
||||
}
|
||||
impl<F> FilteredService<F>
|
||||
where
|
||||
F: Filter,
|
||||
<F::Future as TryFuture>::Ok: Reply,
|
||||
<F::Future as TryFuture>::Error: IsReject,
|
||||
{
|
||||
#[inline]
|
||||
pub(crate) fn call_with_addr(
|
||||
&self,
|
||||
req: Request,
|
||||
remote_addr: Option<SocketAddr>,
|
||||
) -> FilteredFuture<F::Future> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<F> Service<Request> for FilteredService<F>
|
||||
where
|
||||
F: Filter,
|
||||
<F::Future as TryFuture>::Ok: Reply,
|
||||
<F::Future as TryFuture>::Error: IsReject,
|
||||
{
|
||||
type Response = Response;
|
||||
type Error = Infallible;
|
||||
type Future = FilteredFuture<F::Future>;
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
fn call(&mut self, req: Request) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct FilteredFuture<F> {
|
||||
#[pin]
|
||||
future: F,
|
||||
route: ::std::cell::RefCell<Route>,
|
||||
}
|
||||
impl<F> Future for FilteredFuture<F>
|
||||
where
|
||||
F: TryFuture,
|
||||
F::Ok: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Output = Result<Response, Infallible>;
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
69
warp/src/filter/then.rs
Normal file
69
warp/src/filter/then.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use futures_util::{TryFuture};
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Func, Internal};
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Then<T, F> {
|
||||
pub(super) filter: T,
|
||||
pub(super) callback: F,
|
||||
}
|
||||
impl<T, F> FilterBase for Then<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract> + Clone + Send,
|
||||
F::Output: Future + Send,
|
||||
{
|
||||
type Extract = (<F::Output as Future>::Output,);
|
||||
type Error = T::Error;
|
||||
type Future = ThenFuture<T, F>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct ThenFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract>,
|
||||
F::Output: Future + Send,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T::Future, F>,
|
||||
}
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T, F>
|
||||
where
|
||||
T: TryFuture,
|
||||
F: Func<T::Ok>,
|
||||
F::Output: Future + Send,
|
||||
{
|
||||
First(#[pin] T, F),
|
||||
Second(#[pin] F::Output),
|
||||
Done,
|
||||
}
|
||||
impl<T, F> Future for ThenFuture<T, F>
|
||||
where
|
||||
T: Filter,
|
||||
F: Func<T::Extract>,
|
||||
F::Output: Future + Send,
|
||||
{
|
||||
type Output = Result<(<F::Output as Future>::Output,), T::Error>;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T, F> Future for State<T, F>
|
||||
where
|
||||
T: TryFuture,
|
||||
F: Func<T::Ok>,
|
||||
F::Output: Future + Send,
|
||||
{
|
||||
type Output = Result<(<F::Output as Future>::Output,), T::Error>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
39
warp/src/filter/unify.rs
Normal file
39
warp/src/filter/unify.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use futures_util::{TryFuture};
|
||||
use pin_project::pin_project;
|
||||
use super::{Either, Filter, FilterBase, Internal, Tuple};
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Unify<F> {
|
||||
pub(super) filter: F,
|
||||
}
|
||||
impl<F, T> FilterBase for Unify<F>
|
||||
where
|
||||
F: Filter<Extract = (Either<T, T>,)>,
|
||||
T: Tuple,
|
||||
{
|
||||
type Extract = T;
|
||||
type Error = F::Error;
|
||||
type Future = UnifyFuture<F::Future>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct UnifyFuture<F> {
|
||||
#[pin]
|
||||
inner: F,
|
||||
}
|
||||
impl<F, T> Future for UnifyFuture<F>
|
||||
where
|
||||
F: TryFuture<Ok = (Either<T, T>,)>,
|
||||
{
|
||||
type Output = Result<T, F::Error>;
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
40
warp/src/filter/untuple_one.rs
Normal file
40
warp/src/filter/untuple_one.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use pin_project::pin_project;
|
||||
use super::{Filter, FilterBase, Internal, Tuple};
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UntupleOne<F> {
|
||||
pub(super) filter: F,
|
||||
}
|
||||
impl<F, T> FilterBase for UntupleOne<F>
|
||||
where
|
||||
F: Filter<Extract = (T,)>,
|
||||
T: Tuple,
|
||||
{
|
||||
type Extract = T;
|
||||
type Error = F::Error;
|
||||
type Future = UntupleOneFuture<F>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct UntupleOneFuture<F: Filter> {
|
||||
#[pin]
|
||||
extract: F::Future,
|
||||
}
|
||||
impl<F, T> Future for UntupleOneFuture<F>
|
||||
where
|
||||
F: Filter<Extract = (T,)>,
|
||||
T: Tuple,
|
||||
{
|
||||
type Output = Result<T, F::Error>;
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
57
warp/src/filter/wrap.rs
Normal file
57
warp/src/filter/wrap.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use super::Filter;
|
||||
pub trait WrapSealed<F: Filter> {
|
||||
type Wrapped: Filter;
|
||||
fn wrap(&self, filter: F) -> Self::Wrapped;
|
||||
}
|
||||
impl<'a, T, F> WrapSealed<F> for &'a T
|
||||
where
|
||||
T: WrapSealed<F>,
|
||||
F: Filter,
|
||||
{
|
||||
type Wrapped = T::Wrapped;
|
||||
fn wrap(&self, filter: F) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
pub trait Wrap<F: Filter>: WrapSealed<F> {}
|
||||
impl<T, F> Wrap<F> for T
|
||||
where
|
||||
T: WrapSealed<F>,
|
||||
F: Filter,
|
||||
{}
|
||||
/// Combines received filter with pre and after filters
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use crate::warp::Filter;
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(|| "hello world")
|
||||
/// .with(warp::wrap_fn(|filter| filter));
|
||||
/// ```
|
||||
///
|
||||
/// You can find the full example in the [usage example](https://github.com/seanmonstar/warp/blob/master/examples/wrapping.rs).
|
||||
pub fn wrap_fn<F, T, U>(func: F) -> WrapFn<F>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
T: Filter,
|
||||
U: Filter,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct WrapFn<F> {
|
||||
func: F,
|
||||
}
|
||||
impl<F, T, U> WrapSealed<T> for WrapFn<F>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
T: Filter,
|
||||
U: Filter,
|
||||
{
|
||||
type Wrapped = U;
|
||||
fn wrap(&self, filter: T) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
26
warp/src/filters/addr.rs
Normal file
26
warp/src/filters/addr.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
//! Socket Address filters.
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::filter::{filter_fn_one, Filter};
|
||||
|
||||
/// Creates a `Filter` to get the remote address of the connection.
|
||||
///
|
||||
/// If the underlying transport doesn't use socket addresses, this will yield
|
||||
/// `None`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::SocketAddr;
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::addr::remote()
|
||||
/// .map(|addr: Option<SocketAddr>| {
|
||||
/// println!("remote address = {:?}", addr);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn remote() -> impl Filter<Extract = (Option<SocketAddr>,), Error = Infallible> + Copy {
|
||||
filter_fn_one(|route| futures_util::future::ok(route.remote_addr()))
|
||||
}
|
||||
68
warp/src/filters/any.rs
Normal file
68
warp/src/filters/any.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
//! A filter that matches any route.
|
||||
use std::convert::Infallible;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use crate::filter::{Filter, FilterBase, Internal};
|
||||
/// A filter that matches any route.
|
||||
///
|
||||
/// This can be a useful building block to build new filters from,
|
||||
/// since [`Filter`](crate::Filter) is otherwise a sealed trait.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(|| {
|
||||
/// "I always return this string!"
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// This could allow creating a single `impl Filter` returning a specific
|
||||
/// reply, that can then be used as the end of several different filter
|
||||
/// chains.
|
||||
///
|
||||
/// Another use case is turning some clone-able resource into a `Filter`,
|
||||
/// thus allowing to easily `and` it together with others.
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::Arc;
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let state = Arc::new(vec![33, 41]);
|
||||
/// let with_state = warp::any().map(move || state.clone());
|
||||
///
|
||||
/// // Now we could `and` with any other filter:
|
||||
///
|
||||
/// let route = warp::path::param()
|
||||
/// .and(with_state)
|
||||
/// .map(|param_id: u32, db: Arc<Vec<u32>>| {
|
||||
/// db.contains(¶m_id)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn any() -> impl Filter<Extract = (), Error = Infallible> + Copy {
|
||||
Any
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
struct Any;
|
||||
impl FilterBase for Any {
|
||||
type Extract = ();
|
||||
type Error = Infallible;
|
||||
type Future = AnyFut;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
struct AnyFut;
|
||||
impl Future for AnyFut {
|
||||
type Output = Result<(), Infallible>;
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
320
warp/src/filters/body.rs
Normal file
320
warp/src/filters/body.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
//! Body filters
|
||||
//!
|
||||
//! Filters that extract a body for a route.
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use bytes::{Buf, Bytes};
|
||||
use futures_util::{future, Stream, TryFutureExt};
|
||||
use headers::ContentLength;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use hyper::Body;
|
||||
use mime;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
|
||||
use crate::filter::{filter_fn, filter_fn_one, Filter, FilterBase};
|
||||
use crate::reject::{self, Rejection};
|
||||
type BoxError = Box<dyn StdError + Send + Sync>;
|
||||
pub(crate) fn body() -> impl Filter<Extract = (Body,), Error = Rejection> + Copy {
|
||||
filter_fn_one(|route| {
|
||||
future::ready(
|
||||
route
|
||||
.take_body()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("request body already taken in previous filter");
|
||||
reject::known(BodyConsumedMultipleTimes {
|
||||
_p: (),
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Require a `content-length` header to have a value no greater than some limit.
|
||||
///
|
||||
/// Rejects if `content-length` header is missing, is invalid, or has a number
|
||||
/// larger than the limit provided.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Limit the upload to 4kb...
|
||||
/// let upload = warp::body::content_length_limit(4096)
|
||||
/// .and(warp::body::aggregate());
|
||||
/// ```
|
||||
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()
|
||||
}
|
||||
/// Create a `Filter` that extracts the request body as a `futures::Stream`.
|
||||
///
|
||||
/// If other filters have already extracted the body, this filter will reject
|
||||
/// with a `500 Internal Server Error`.
|
||||
///
|
||||
/// # 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.
|
||||
pub fn stream() -> impl Filter<
|
||||
Extract = (impl Stream<Item = Result<impl Buf, crate::Error>>,),
|
||||
Error = Rejection,
|
||||
> + Copy {
|
||||
body().map(|body: Body| BodyStream { body })
|
||||
}
|
||||
/// Returns a `Filter` that matches any request and extracts a `Future` of a
|
||||
/// concatenated body.
|
||||
///
|
||||
/// The contents of the body will be flattened into a single contiguous
|
||||
/// `Bytes`, which may require memory copies. If you don't require a
|
||||
/// contiguous buffer, using `aggregate` can be give better performance.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Buf, Filter};
|
||||
///
|
||||
/// let route = warp::body::content_length_limit(1024 * 32)
|
||||
/// .and(warp::body::bytes())
|
||||
/// .map(|bytes: bytes::Bytes| {
|
||||
/// println!("bytes = {:?}", bytes);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn bytes() -> impl Filter<Extract = (Bytes,), Error = Rejection> + Copy {
|
||||
body()
|
||||
.and_then(|body: hyper::Body| {
|
||||
hyper::body::to_bytes(body)
|
||||
.map_err(|err| {
|
||||
tracing::debug!("to_bytes error: {}", err);
|
||||
reject::known(BodyReadError(err))
|
||||
})
|
||||
})
|
||||
}
|
||||
/// Returns a `Filter` that matches any request and extracts a `Future` of an
|
||||
/// aggregated body.
|
||||
///
|
||||
/// The `Buf` may contain multiple, non-contiguous buffers. This can be more
|
||||
/// performant (by reducing copies) when receiving large bodies.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Buf, Filter};
|
||||
///
|
||||
/// fn full_body(mut body: impl Buf) {
|
||||
/// // It could have several non-contiguous slices of memory...
|
||||
/// while body.has_remaining() {
|
||||
/// println!("slice = {:?}", body.chunk());
|
||||
/// let cnt = body.chunk().len();
|
||||
/// body.advance(cnt);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let route = warp::body::content_length_limit(1024 * 32)
|
||||
/// .and(warp::body::aggregate())
|
||||
/// .map(full_body);
|
||||
/// ```
|
||||
pub fn aggregate() -> impl Filter<Extract = (impl Buf,), Error = Rejection> + Copy {
|
||||
body()
|
||||
.and_then(|body: ::hyper::Body| {
|
||||
hyper::body::aggregate(body)
|
||||
.map_err(|err| {
|
||||
tracing::debug!("aggregate error: {}", err);
|
||||
reject::known(BodyReadError(err))
|
||||
})
|
||||
})
|
||||
}
|
||||
/// Returns a `Filter` that matches any request and extracts a `Future` of a
|
||||
/// JSON-decoded body.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::collections::HashMap;
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::body::content_length_limit(1024 * 32)
|
||||
/// .and(warp::body::json())
|
||||
/// .map(|simple_map: HashMap<String, String>| {
|
||||
/// "Got a JSON body!"
|
||||
/// });
|
||||
/// ```
|
||||
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 })
|
||||
})
|
||||
})
|
||||
}
|
||||
trait Decode {
|
||||
const MIME: (mime::Name<'static>, mime::Name<'static>);
|
||||
const WITH_NO_CONTENT_TYPE: bool;
|
||||
fn decode<B: Buf, T: DeserializeOwned>(buf: B) -> Result<T, BoxError>;
|
||||
}
|
||||
struct Json;
|
||||
impl Decode for Json {
|
||||
const MIME: (mime::Name<'static>, mime::Name<'static>) = (
|
||||
mime::APPLICATION,
|
||||
mime::JSON,
|
||||
);
|
||||
const WITH_NO_CONTENT_TYPE: bool = true;
|
||||
fn decode<B: Buf, T: DeserializeOwned>(mut buf: B) -> Result<T, BoxError> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
struct Form;
|
||||
impl Decode for Form {
|
||||
const MIME: (mime::Name<'static>, mime::Name<'static>) = (
|
||||
mime::APPLICATION,
|
||||
mime::WWW_FORM_URLENCODED,
|
||||
);
|
||||
const WITH_NO_CONTENT_TYPE: bool = true;
|
||||
fn decode<B: Buf, T: DeserializeOwned>(buf: B) -> Result<T, BoxError> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
fn is_content_type<D: Decode>() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
filter_fn(move |route| {
|
||||
let (type_, subtype) = D::MIME;
|
||||
if let Some(value) = route.headers().get(CONTENT_TYPE) {
|
||||
tracing::trace!("is_content_type {}/{}? {:?}", type_, subtype, value);
|
||||
let ct = value.to_str().ok().and_then(|s| s.parse::<mime::Mime>().ok());
|
||||
if let Some(ct) = ct {
|
||||
if ct.type_() == type_ && ct.subtype() == subtype {
|
||||
future::ok(())
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"content-type {:?} doesn't match {}/{}", value, type_, subtype
|
||||
);
|
||||
future::err(reject::unsupported_media_type())
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("content-type {:?} couldn't be parsed", value);
|
||||
future::err(reject::unsupported_media_type())
|
||||
}
|
||||
} else if D::WITH_NO_CONTENT_TYPE {
|
||||
tracing::trace!("no content-type header, assuming {}/{}", type_, subtype);
|
||||
future::ok(())
|
||||
} else {
|
||||
tracing::debug!("no content-type found");
|
||||
future::err(reject::unsupported_media_type())
|
||||
}
|
||||
})
|
||||
}
|
||||
struct BodyStream {
|
||||
body: Body,
|
||||
}
|
||||
impl Stream for BodyStream {
|
||||
type Item = Result<Bytes, crate::Error>;
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// An error used in rejections when deserializing a request body fails.
|
||||
#[derive(Debug)]
|
||||
pub struct BodyDeserializeError {
|
||||
cause: BoxError,
|
||||
}
|
||||
impl fmt::Display for BodyDeserializeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for BodyDeserializeError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BodyReadError(::hyper::Error);
|
||||
impl fmt::Display for BodyReadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for BodyReadError {}
|
||||
unit_error! {
|
||||
pub (crate) BodyConsumedMultipleTimes : "Request body consumed multiple times"
|
||||
}
|
||||
200
warp/src/filters/compression.rs
Normal file
200
warp/src/filters/compression.rs
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
//! Compression Filters
|
||||
//!
|
||||
//! Filters that compress the body of a response.
|
||||
#[cfg(feature = "compression-brotli")]
|
||||
use async_compression::tokio::bufread::BrotliEncoder;
|
||||
#[cfg(feature = "compression-gzip")]
|
||||
use async_compression::tokio::bufread::{DeflateEncoder, GzipEncoder};
|
||||
use http::header::HeaderValue;
|
||||
use hyper::{
|
||||
header::{CONTENT_ENCODING, CONTENT_LENGTH},
|
||||
Body,
|
||||
};
|
||||
use tokio_util::io::{ReaderStream, StreamReader};
|
||||
use crate::filter::{Filter, WrapSealed};
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::{Reply, Response};
|
||||
use self::internal::{CompressionProps, WithCompression};
|
||||
enum CompressionAlgo {
|
||||
#[cfg(feature = "compression-brotli")]
|
||||
BR,
|
||||
#[cfg(feature = "compression-gzip")]
|
||||
DEFLATE,
|
||||
#[cfg(feature = "compression-gzip")]
|
||||
GZIP,
|
||||
}
|
||||
impl From<CompressionAlgo> for HeaderValue {
|
||||
#[inline]
|
||||
fn from(algo: CompressionAlgo) -> Self {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Compression
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Compression<F> {
|
||||
func: F,
|
||||
}
|
||||
/// Create a wrapping filter that compresses the Body of a [`Response`](crate::reply::Response)
|
||||
/// using gzip, adding `content-encoding: gzip` to the Response's [`HeaderMap`](hyper::HeaderMap)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::get()
|
||||
/// .and(warp::path::end())
|
||||
/// .and(warp::fs::file("./README.md"))
|
||||
/// .with(warp::compression::gzip());
|
||||
/// ```
|
||||
#[cfg(feature = "compression-gzip")]
|
||||
pub fn gzip() -> Compression<impl Fn(CompressionProps) -> Response + Copy> {
|
||||
loop {}
|
||||
}
|
||||
/// Create a wrapping filter that compresses the Body of a [`Response`](crate::reply::Response)
|
||||
/// using deflate, adding `content-encoding: deflate` to the Response's [`HeaderMap`](hyper::HeaderMap)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::get()
|
||||
/// .and(warp::path::end())
|
||||
/// .and(warp::fs::file("./README.md"))
|
||||
/// .with(warp::compression::deflate());
|
||||
/// ```
|
||||
#[cfg(feature = "compression-gzip")]
|
||||
pub fn deflate() -> Compression<impl Fn(CompressionProps) -> Response + Copy> {
|
||||
loop {}
|
||||
}
|
||||
/// Create a wrapping filter that compresses the Body of a [`Response`](crate::reply::Response)
|
||||
/// using brotli, adding `content-encoding: br` to the Response's [`HeaderMap`](hyper::HeaderMap)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::get()
|
||||
/// .and(warp::path::end())
|
||||
/// .and(warp::fs::file("./README.md"))
|
||||
/// .with(warp::compression::brotli());
|
||||
/// ```
|
||||
#[cfg(feature = "compression-brotli")]
|
||||
pub fn brotli() -> Compression<impl Fn(CompressionProps) -> Response + Copy> {
|
||||
loop {}
|
||||
}
|
||||
impl<FN, F> WrapSealed<F> for Compression<FN>
|
||||
where
|
||||
FN: Fn(CompressionProps) -> Response + Clone + Send,
|
||||
F: Filter + Clone + Send,
|
||||
F::Extract: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Wrapped = WithCompression<FN, F>;
|
||||
fn wrap(&self, filter: F) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
mod internal {
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use bytes::Bytes;
|
||||
use futures_util::{ready, Stream, TryFuture};
|
||||
use hyper::Body;
|
||||
use pin_project::pin_project;
|
||||
use crate::filter::{Filter, FilterBase, Internal};
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::{Reply, Response};
|
||||
use super::Compression;
|
||||
/// A wrapper around any type that implements [`Stream`](futures::Stream) to be
|
||||
/// compatible with async_compression's Stream based encoders
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct CompressableBody<S, E>
|
||||
where
|
||||
E: std::error::Error,
|
||||
S: Stream<Item = Result<Bytes, E>>,
|
||||
{
|
||||
#[pin]
|
||||
body: S,
|
||||
}
|
||||
impl<S, E> Stream for CompressableBody<S, E>
|
||||
where
|
||||
E: std::error::Error,
|
||||
S: Stream<Item = Result<Bytes, E>>,
|
||||
{
|
||||
type Item = std::io::Result<Bytes>;
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl From<Body> for CompressableBody<Body, hyper::Error> {
|
||||
fn from(body: Body) -> Self {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Compression Props
|
||||
#[derive(Debug)]
|
||||
pub struct CompressionProps {
|
||||
pub(super) body: CompressableBody<Body, hyper::Error>,
|
||||
pub(super) head: http::response::Parts,
|
||||
}
|
||||
impl From<http::Response<Body>> for CompressionProps {
|
||||
fn from(resp: http::Response<Body>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Compressed(pub(super) Response);
|
||||
impl Reply for Compressed {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct WithCompression<FN, F> {
|
||||
pub(super) compress: Compression<FN>,
|
||||
pub(super) filter: F,
|
||||
}
|
||||
impl<FN, F> FilterBase for WithCompression<FN, F>
|
||||
where
|
||||
FN: Fn(CompressionProps) -> Response + Clone + Send,
|
||||
F: Filter + Clone + Send,
|
||||
F::Extract: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Extract = (Compressed,);
|
||||
type Error = F::Error;
|
||||
type Future = WithCompressionFuture<FN, F::Future>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct WithCompressionFuture<FN, F> {
|
||||
compress: Compression<FN>,
|
||||
#[pin]
|
||||
future: F,
|
||||
}
|
||||
impl<FN, F> Future for WithCompressionFuture<FN, F>
|
||||
where
|
||||
FN: Fn(CompressionProps) -> Response,
|
||||
F: TryFuture,
|
||||
F::Ok: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Output = Result<(Compressed,), F::Error>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
warp/src/filters/cookie.rs
Normal file
46
warp/src/filters/cookie.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//! Cookie Filters
|
||||
|
||||
use futures_util::future;
|
||||
use headers::Cookie;
|
||||
|
||||
use super::header;
|
||||
use crate::filter::{Filter, One};
|
||||
use crate::reject::Rejection;
|
||||
use std::convert::Infallible;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Creates a `Filter` that requires a cookie by name.
|
||||
///
|
||||
/// If found, extracts the value of the cookie, otherwise rejects.
|
||||
pub fn cookie<T>(name: &'static str) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy
|
||||
where
|
||||
T: FromStr + Send + 'static,
|
||||
{
|
||||
header::header2().and_then(move |cookie: Cookie| {
|
||||
let cookie = cookie
|
||||
.get(name)
|
||||
.ok_or_else(|| crate::reject::missing_cookie(name))
|
||||
.and_then(|s| T::from_str(s).map_err(|_| crate::reject::missing_cookie(name)));
|
||||
future::ready(cookie)
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a `Filter` that looks for an optional cookie by name.
|
||||
///
|
||||
/// If found, extracts the value of the cookie, otherwise continues
|
||||
/// the request, extracting `None`.
|
||||
pub fn optional<T>(
|
||||
name: &'static str,
|
||||
) -> impl Filter<Extract = One<Option<T>>, Error = Infallible> + Copy
|
||||
where
|
||||
T: FromStr + Send + 'static,
|
||||
{
|
||||
header::optional2().map(move |opt: Option<Cookie>| {
|
||||
let cookie = opt.and_then(|cookie| cookie.get(name).map(|x| T::from_str(x)));
|
||||
match cookie {
|
||||
Some(Ok(t)) => Some(t),
|
||||
Some(Err(_)) => None,
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
350
warp/src/filters/cors.rs
Normal file
350
warp/src/filters/cors.rs
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
//! CORS Filters
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use headers::{
|
||||
AccessControlAllowHeaders, AccessControlAllowMethods, AccessControlExposeHeaders,
|
||||
};
|
||||
use http::{self, header::{HeaderName, HeaderValue}};
|
||||
use crate::filter::{Filter, WrapSealed};
|
||||
use crate::reject::{CombineRejection, Rejection};
|
||||
use crate::reply::Reply;
|
||||
use self::internal::{CorsFilter, IntoOrigin, Seconds};
|
||||
/// Create a wrapping filter that exposes [CORS][] behavior for a wrapped
|
||||
/// filter.
|
||||
///
|
||||
/// [CORS]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let cors = warp::cors()
|
||||
/// .allow_origin("https://hyper.rs")
|
||||
/// .allow_methods(vec!["GET", "POST", "DELETE"]);
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(cors);
|
||||
/// ```
|
||||
/// If you want to allow any route:
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
/// let cors = warp::cors()
|
||||
/// .allow_any_origin();
|
||||
/// ```
|
||||
/// You can find more usage examples [here](https://github.com/seanmonstar/warp/blob/7fa54eaecd0fe12687137372791ff22fc7995766/tests/cors.rs).
|
||||
pub fn cors() -> Builder {
|
||||
loop {}
|
||||
}
|
||||
/// A wrapping filter constructed via `warp::cors()`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Cors {
|
||||
config: Arc<Configured>,
|
||||
}
|
||||
/// A constructed via `warp::cors()`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Builder {
|
||||
credentials: bool,
|
||||
allowed_headers: HashSet<HeaderName>,
|
||||
exposed_headers: HashSet<HeaderName>,
|
||||
max_age: Option<u64>,
|
||||
methods: HashSet<http::Method>,
|
||||
origins: Option<HashSet<HeaderValue>>,
|
||||
}
|
||||
impl Builder {
|
||||
/// Sets whether to add the `Access-Control-Allow-Credentials` header.
|
||||
pub fn allow_credentials(mut self, allow: bool) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Adds a method to the existing list of allowed request methods.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided argument is not a valid `http::Method`.
|
||||
pub fn allow_method<M>(mut self, method: M) -> Self
|
||||
where
|
||||
http::Method: TryFrom<M>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Adds multiple methods to the existing list of allowed request methods.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided argument is not a valid `http::Method`.
|
||||
pub fn allow_methods<I>(mut self, methods: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
http::Method: TryFrom<I::Item>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Adds a header to the list of allowed request headers.
|
||||
///
|
||||
/// **Note**: These should match the values the browser sends via `Access-Control-Request-Headers`, e.g. `content-type`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided argument is not a valid `http::header::HeaderName`.
|
||||
pub fn allow_header<H>(mut self, header: H) -> Self
|
||||
where
|
||||
HeaderName: TryFrom<H>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Adds multiple headers to the list of allowed request headers.
|
||||
///
|
||||
/// **Note**: These should match the values the browser sends via `Access-Control-Request-Headers`, e.g.`content-type`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if any of the headers are not a valid `http::header::HeaderName`.
|
||||
pub fn allow_headers<I>(mut self, headers: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
HeaderName: TryFrom<I::Item>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Adds a header to the list of exposed headers.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided argument is not a valid `http::header::HeaderName`.
|
||||
pub fn expose_header<H>(mut self, header: H) -> Self
|
||||
where
|
||||
HeaderName: TryFrom<H>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Adds multiple headers to the list of exposed headers.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if any of the headers are not a valid `http::header::HeaderName`.
|
||||
pub fn expose_headers<I>(mut self, headers: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
HeaderName: TryFrom<I::Item>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Sets that *any* `Origin` header is allowed.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This can allow websites you didn't intend to access this resource,
|
||||
/// it is usually better to set an explicit list.
|
||||
pub fn allow_any_origin(mut self) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Add an origin to the existing list of allowed `Origin`s.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided argument is not a valid `Origin`.
|
||||
pub fn allow_origin(self, origin: impl IntoOrigin) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Add multiple origins to the existing list of allowed `Origin`s.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided argument is not a valid `Origin`.
|
||||
pub fn allow_origins<I>(mut self, origins: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: IntoOrigin,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Sets the `Access-Control-Max-Age` header.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// use std::time::Duration;
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let cors = warp::cors()
|
||||
/// .max_age(30) // 30u32 seconds
|
||||
/// .max_age(Duration::from_secs(30)); // or a Duration
|
||||
/// ```
|
||||
pub fn max_age(mut self, seconds: impl Seconds) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Builds the `Cors` wrapper from the configured settings.
|
||||
///
|
||||
/// This step isn't *required*, as the `Builder` itself can be passed
|
||||
/// to `Filter::with`. This just allows constructing once, thus not needing
|
||||
/// to pay the cost of "building" every time.
|
||||
pub fn build(self) -> Cors {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<F> WrapSealed<F> for Builder
|
||||
where
|
||||
F: Filter + Clone + Send + Sync + 'static,
|
||||
F::Extract: Reply,
|
||||
F::Error: CombineRejection<Rejection>,
|
||||
<F::Error as CombineRejection<Rejection>>::One: CombineRejection<Rejection>,
|
||||
{
|
||||
type Wrapped = CorsFilter<F>;
|
||||
fn wrap(&self, inner: F) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<F> WrapSealed<F> for Cors
|
||||
where
|
||||
F: Filter + Clone + Send + Sync + 'static,
|
||||
F::Extract: Reply,
|
||||
F::Error: CombineRejection<Rejection>,
|
||||
<F::Error as CombineRejection<Rejection>>::One: CombineRejection<Rejection>,
|
||||
{
|
||||
type Wrapped = CorsFilter<F>;
|
||||
fn wrap(&self, inner: F) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// An error used to reject requests that are forbidden by a `cors` filter.
|
||||
pub struct CorsForbidden {
|
||||
kind: Forbidden,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
enum Forbidden {
|
||||
OriginNotAllowed,
|
||||
MethodNotAllowed,
|
||||
HeaderNotAllowed,
|
||||
}
|
||||
impl fmt::Debug for CorsForbidden {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for CorsForbidden {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for CorsForbidden {}
|
||||
#[derive(Clone, Debug)]
|
||||
struct Configured {
|
||||
cors: Builder,
|
||||
allowed_headers_header: AccessControlAllowHeaders,
|
||||
expose_headers_header: Option<AccessControlExposeHeaders>,
|
||||
methods_header: AccessControlAllowMethods,
|
||||
}
|
||||
enum Validated {
|
||||
Preflight(HeaderValue),
|
||||
Simple(HeaderValue),
|
||||
NotCors,
|
||||
}
|
||||
impl Configured {}
|
||||
mod internal {
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use futures_util::{future, TryFuture};
|
||||
use headers::Origin;
|
||||
use http::header;
|
||||
use pin_project::pin_project;
|
||||
use super::Configured;
|
||||
use crate::filter::{Filter, FilterBase, Internal, One};
|
||||
use crate::generic::Either;
|
||||
use crate::reject::{CombineRejection, Rejection};
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CorsFilter<F> {
|
||||
pub(super) config: Arc<Configured>,
|
||||
pub(super) inner: F,
|
||||
}
|
||||
impl<F> FilterBase for CorsFilter<F>
|
||||
where
|
||||
F: Filter,
|
||||
F::Extract: Send,
|
||||
F::Future: Future,
|
||||
F::Error: CombineRejection<Rejection>,
|
||||
{
|
||||
type Extract = One<
|
||||
Either<One<Preflight>, One<Either<One<Wrapped<F::Extract>>, F::Extract>>>,
|
||||
>;
|
||||
type Error = <F::Error as CombineRejection<Rejection>>::One;
|
||||
type Future = future::Either<
|
||||
future::Ready<Result<Self::Extract, Self::Error>>,
|
||||
WrappedFuture<F::Future>,
|
||||
>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Preflight {
|
||||
config: Arc<Configured>,
|
||||
origin: header::HeaderValue,
|
||||
}
|
||||
impl crate::reply::Reply for Preflight {
|
||||
fn into_response(self) -> crate::reply::Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Wrapped<R> {
|
||||
config: Arc<Configured>,
|
||||
inner: R,
|
||||
origin: header::HeaderValue,
|
||||
}
|
||||
impl<R> crate::reply::Reply for Wrapped<R>
|
||||
where
|
||||
R: crate::reply::Reply,
|
||||
{
|
||||
fn into_response(self) -> crate::reply::Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct WrappedFuture<F> {
|
||||
#[pin]
|
||||
inner: F,
|
||||
wrapped: Option<(Arc<Configured>, header::HeaderValue)>,
|
||||
}
|
||||
impl<F> Future for WrappedFuture<F>
|
||||
where
|
||||
F: TryFuture,
|
||||
F::Error: CombineRejection<Rejection>,
|
||||
{
|
||||
type Output = Result<
|
||||
One<Either<One<Preflight>, One<Either<One<Wrapped<F::Ok>>, F::Ok>>>>,
|
||||
<F::Error as CombineRejection<Rejection>>::One,
|
||||
>;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
pub trait Seconds {
|
||||
fn seconds(self) -> u64;
|
||||
}
|
||||
impl Seconds for u32 {
|
||||
fn seconds(self) -> u64 {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Seconds for ::std::time::Duration {
|
||||
fn seconds(self) -> u64 {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
pub trait IntoOrigin {
|
||||
fn into_origin(self) -> Origin;
|
||||
}
|
||||
impl<'a> IntoOrigin for &'a str {
|
||||
fn into_origin(self) -> Origin {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
warp/src/filters/ext.rs
Normal file
36
warp/src/filters/ext.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//! Request Extensions
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use futures_util::future;
|
||||
|
||||
use crate::filter::{filter_fn_one, Filter};
|
||||
use crate::reject::{self, Rejection};
|
||||
|
||||
/// Get a previously set extension of the current route.
|
||||
///
|
||||
/// If the extension doesn't exist, this rejects with a `MissingExtension`.
|
||||
pub fn get<T: Clone + Send + Sync + 'static>(
|
||||
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
|
||||
filter_fn_one(|route| {
|
||||
let route = route
|
||||
.extensions()
|
||||
.get::<T>()
|
||||
.cloned()
|
||||
.ok_or_else(|| reject::known(MissingExtension { _p: () }));
|
||||
future::ready(route)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a previously set extension of the current route.
|
||||
///
|
||||
/// If the extension doesn't exist, it yields `None`.
|
||||
pub fn optional<T: Clone + Send + Sync + 'static>(
|
||||
) -> impl Filter<Extract = (Option<T>,), Error = Infallible> + Copy {
|
||||
filter_fn_one(|route| future::ok(route.extensions().get::<T>().cloned()))
|
||||
}
|
||||
|
||||
unit_error! {
|
||||
/// An error used to reject if `get` cannot find the extension.
|
||||
pub MissingExtension: "Missing request extension"
|
||||
}
|
||||
358
warp/src/filters/fs.rs
Normal file
358
warp/src/filters/fs.rs
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
//! File System Filters
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::fs::Metadata;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_util::future::Either;
|
||||
use futures_util::{future, ready, stream, FutureExt, Stream, StreamExt, TryFutureExt};
|
||||
use headers::{
|
||||
AcceptRanges, ContentLength, ContentRange, ContentType, HeaderMapExt,
|
||||
IfModifiedSince, IfRange, IfUnmodifiedSince, LastModified, Range,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use hyper::Body;
|
||||
use mime_guess;
|
||||
|
||||
use tokio::fs::File as TkFile;
|
||||
use tokio::io::AsyncSeekExt;
|
||||
use tokio_util::io::poll_read_buf;
|
||||
use crate::filter::{Filter, FilterClone, One};
|
||||
use crate::reject::{self, Rejection};
|
||||
use crate::reply::{Reply, Response};
|
||||
/// Creates a `Filter` that serves a File at the `path`.
|
||||
///
|
||||
/// Does not filter out based on any information of the request. Always serves
|
||||
/// the file at the exact `path` provided. Thus, this can be used to serve a
|
||||
/// single file with `GET`s, but could also be used in combination with other
|
||||
/// filters, such as after validating in `POST` request, wanting to return a
|
||||
/// specific file as the body.
|
||||
///
|
||||
/// For serving a directory, see [dir](dir).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // Always serves this file from the file system.
|
||||
/// let route = warp::fs::file("/www/static/app.js");
|
||||
/// ```
|
||||
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(file_reply)
|
||||
}
|
||||
/// 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()
|
||||
.or(crate::head())
|
||||
.unify()
|
||||
.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> {
|
||||
loop {}
|
||||
}
|
||||
#[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 {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
/// A file response.
|
||||
#[derive(Debug)]
|
||||
pub struct File {
|
||||
resp: Response,
|
||||
path: ArcPath,
|
||||
}
|
||||
impl File {
|
||||
/// Extract the `&Path` of the file this `Response` delivers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The example below changes the Content-Type response header for every file called `video.mp4`.
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Filter, reply::Reply};
|
||||
///
|
||||
/// let route = warp::path("static")
|
||||
/// .and(warp::fs::dir("/www/static"))
|
||||
/// .map(|reply: warp::filters::fs::File| {
|
||||
/// if reply.path().ends_with("video.mp4") {
|
||||
/// warp::reply::with_header(reply, "Content-Type", "video/mp4").into_response()
|
||||
/// } else {
|
||||
/// reply.into_response()
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
pub fn path(&self) -> &Path {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
struct ArcPath(Arc<PathBuf>);
|
||||
impl AsRef<Path> for ArcPath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for File {
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
fn file_reply(
|
||||
path: ArcPath,
|
||||
conditionals: Conditionals,
|
||||
) -> impl Future<Output = Result<File, Rejection>> + Send {
|
||||
TkFile::open(path.clone())
|
||||
.then(move |res| match res {
|
||||
Ok(f) => Either::Left(file_conditional(f, path, conditionals)),
|
||||
Err(err) => {
|
||||
let rej = match err.kind() {
|
||||
io::ErrorKind::NotFound => {
|
||||
tracing::debug!("file not found: {:?}", path.as_ref().display());
|
||||
reject::not_found()
|
||||
}
|
||||
io::ErrorKind::PermissionDenied => {
|
||||
tracing::warn!(
|
||||
"file permission denied: {:?}", path.as_ref().display()
|
||||
);
|
||||
reject::known(FilePermissionError { _p: () })
|
||||
}
|
||||
_ => {
|
||||
tracing::error!(
|
||||
"file open error (path={:?}): {} ", path.as_ref().display(),
|
||||
err
|
||||
);
|
||||
reject::known(FileOpenError { _p: () })
|
||||
}
|
||||
};
|
||||
Either::Right(future::err(rej))
|
||||
}
|
||||
})
|
||||
}
|
||||
async fn file_metadata(f: TkFile) -> Result<(TkFile, Metadata), Rejection> {
|
||||
loop {}
|
||||
}
|
||||
fn file_conditional(
|
||||
f: TkFile,
|
||||
path: ArcPath,
|
||||
conditionals: Conditionals,
|
||||
) -> impl Future<Output = Result<File, Rejection>> + Send {
|
||||
file_metadata(f)
|
||||
.map_ok(move |(file, meta)| {
|
||||
let mut len = meta.len();
|
||||
let modified = meta.modified().ok().map(LastModified::from);
|
||||
let resp = match conditionals.check(modified) {
|
||||
Cond::NoBody(resp) => resp,
|
||||
Cond::WithBody(range) => {
|
||||
bytes_range(range, len)
|
||||
.map(|(start, end)| {
|
||||
let sub_len = end - start;
|
||||
let buf_size = optimal_buf_size(&meta);
|
||||
let stream = file_stream(file, buf_size, (start, end));
|
||||
let body = Body::wrap_stream(stream);
|
||||
let mut resp = Response::new(body);
|
||||
if sub_len != len {
|
||||
*resp.status_mut() = StatusCode::PARTIAL_CONTENT;
|
||||
resp.headers_mut()
|
||||
.typed_insert(
|
||||
ContentRange::bytes(start..end, len)
|
||||
.expect("valid ContentRange"),
|
||||
);
|
||||
len = sub_len;
|
||||
}
|
||||
let mime = mime_guess::from_path(path.as_ref())
|
||||
.first_or_octet_stream();
|
||||
resp.headers_mut().typed_insert(ContentLength(len));
|
||||
resp.headers_mut().typed_insert(ContentType::from(mime));
|
||||
resp.headers_mut().typed_insert(AcceptRanges::bytes());
|
||||
if let Some(last_modified) = modified {
|
||||
resp.headers_mut().typed_insert(last_modified);
|
||||
}
|
||||
resp
|
||||
})
|
||||
.unwrap_or_else(|BadRange| {
|
||||
let mut resp = Response::new(Body::empty());
|
||||
*resp.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
|
||||
resp.headers_mut()
|
||||
.typed_insert(ContentRange::unsatisfied_bytes(len));
|
||||
resp
|
||||
})
|
||||
}
|
||||
};
|
||||
File { resp, path }
|
||||
})
|
||||
}
|
||||
struct BadRange;
|
||||
fn bytes_range(range: Option<Range>, max_len: u64) -> Result<(u64, u64), BadRange> {
|
||||
loop {}
|
||||
}
|
||||
fn file_stream(
|
||||
mut file: TkFile,
|
||||
buf_size: usize,
|
||||
(start, end): (u64, u64),
|
||||
) -> impl Stream<Item = Result<Bytes, io::Error>> + Send {
|
||||
use std::io::SeekFrom;
|
||||
let seek = async move {
|
||||
if start != 0 {
|
||||
file.seek(SeekFrom::Start(start)).await?;
|
||||
}
|
||||
Ok(file)
|
||||
};
|
||||
seek.into_stream()
|
||||
.map(move |result| {
|
||||
let mut buf = BytesMut::new();
|
||||
let mut len = end - start;
|
||||
let mut f = match result {
|
||||
Ok(f) => f,
|
||||
Err(f) => return Either::Left(stream::once(future::err(f))),
|
||||
};
|
||||
Either::Right(
|
||||
stream::poll_fn(move |cx| {
|
||||
if len == 0 {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
reserve_at_least(&mut buf, buf_size);
|
||||
let n = match ready!(
|
||||
poll_read_buf(Pin::new(& mut f), cx, & mut buf)
|
||||
) {
|
||||
Ok(n) => n as u64,
|
||||
Err(err) => {
|
||||
tracing::debug!("file read error: {}", err);
|
||||
return Poll::Ready(Some(Err(err)));
|
||||
}
|
||||
};
|
||||
if n == 0 {
|
||||
tracing::debug!("file read found EOF before expected length");
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
let mut chunk = buf.split().freeze();
|
||||
if n > len {
|
||||
chunk = chunk.split_to(len as usize);
|
||||
len = 0;
|
||||
} else {
|
||||
len -= n;
|
||||
}
|
||||
Poll::Ready(Some(Ok(chunk)))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
fn reserve_at_least(buf: &mut BytesMut, cap: usize) {
|
||||
loop {}
|
||||
}
|
||||
const DEFAULT_READ_BUF_SIZE: usize = 8_192;
|
||||
fn optimal_buf_size(metadata: &Metadata) -> usize {
|
||||
loop {}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
fn get_block_size(metadata: &Metadata) -> usize {
|
||||
loop {}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
fn get_block_size(_metadata: &Metadata) -> usize {
|
||||
loop {}
|
||||
}
|
||||
unit_error! {
|
||||
pub (crate) FileOpenError : "file open error"
|
||||
}
|
||||
unit_error! {
|
||||
pub (crate) FilePermissionError : "file perimission error"
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::sanitize_path;
|
||||
use bytes::BytesMut;
|
||||
#[test]
|
||||
fn test_sanitize_path() {
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn test_reserve_at_least() {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
230
warp/src/filters/header.rs
Normal file
230
warp/src/filters/header.rs
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
//! Header Filters
|
||||
//!
|
||||
//! These filters are used to interact with the Request HTTP headers. Some
|
||||
//! of them, like `exact` and `exact_ignore_case`, are just predicates,
|
||||
//! they don't extract any values. The `header` filter allows parsing
|
||||
//! a type from any header.
|
||||
use std::convert::Infallible;
|
||||
use std::str::FromStr;
|
||||
|
||||
use futures_util::future;
|
||||
use headers::{Header, HeaderMapExt};
|
||||
use http::header::HeaderValue;
|
||||
use http::HeaderMap;
|
||||
|
||||
use crate::filter::{filter_fn, filter_fn_one, Filter, One};
|
||||
use crate::reject::{self, Rejection};
|
||||
|
||||
/// Create a `Filter` that tries to parse the specified header.
|
||||
///
|
||||
/// This `Filter` will look for a header with supplied name, and try to
|
||||
/// parse to a `T`, otherwise rejects the request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// // Parse `content-length: 100` as a `u64`
|
||||
/// let content_length = warp::header::<u64>("content-length");
|
||||
///
|
||||
/// // Parse `host: 127.0.0.1:8080` as a `SocketAddr
|
||||
/// let local_host = warp::header::<SocketAddr>("host");
|
||||
///
|
||||
/// // Parse `foo: bar` into a `String`
|
||||
/// let foo = warp::header::<String>("foo");
|
||||
/// ```
|
||||
pub fn header<T: FromStr + Send + 'static>(
|
||||
name: &'static str,
|
||||
) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
|
||||
filter_fn_one(move |route| {
|
||||
tracing::trace!("header({:?})", name);
|
||||
let route = route
|
||||
.headers()
|
||||
.get(name)
|
||||
.ok_or_else(|| reject::missing_header(name))
|
||||
.and_then(|value| value.to_str().map_err(|_| reject::invalid_header(name)))
|
||||
.and_then(|s| T::from_str(s).map_err(|_| reject::invalid_header(name)));
|
||||
future::ready(route)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn header2<T: Header + Send + 'static>(
|
||||
) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
|
||||
filter_fn_one(move |route| {
|
||||
tracing::trace!("header2({:?})", T::name());
|
||||
let route = route
|
||||
.headers()
|
||||
.typed_get()
|
||||
.ok_or_else(|| reject::invalid_header(T::name().as_str()));
|
||||
future::ready(route)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a `Filter` that tries to parse the specified header, if it exists.
|
||||
///
|
||||
/// If the header does not exist, it yields `None`. Otherwise, it will try to
|
||||
/// parse as a `T`, and if it fails, a invalid header rejection is return. If
|
||||
/// successful, the filter yields `Some(T)`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // Grab the `authorization` header if it exists.
|
||||
/// let opt_auth = warp::header::optional::<String>("authorization");
|
||||
/// ```
|
||||
pub fn optional<T>(
|
||||
name: &'static str,
|
||||
) -> impl Filter<Extract = One<Option<T>>, Error = Rejection> + Copy
|
||||
where
|
||||
T: FromStr + Send + 'static,
|
||||
{
|
||||
filter_fn_one(move |route| {
|
||||
tracing::trace!("optional({:?})", name);
|
||||
let result = route.headers().get(name).map(|value| {
|
||||
value
|
||||
.to_str()
|
||||
.map_err(|_| reject::invalid_header(name))?
|
||||
.parse::<T>()
|
||||
.map_err(|_| reject::invalid_header(name))
|
||||
});
|
||||
|
||||
match result {
|
||||
Some(Ok(t)) => future::ok(Some(t)),
|
||||
Some(Err(e)) => future::err(e),
|
||||
None => future::ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn optional2<T>() -> impl Filter<Extract = One<Option<T>>, Error = Infallible> + Copy
|
||||
where
|
||||
T: Header + Send + 'static,
|
||||
{
|
||||
filter_fn_one(move |route| future::ready(Ok(route.headers().typed_get())))
|
||||
}
|
||||
|
||||
/* TODO
|
||||
pub fn exact2<T>(header: T) -> impl FilterClone<Extract=(), Error=Rejection>
|
||||
where
|
||||
T: Header + PartialEq + Clone + Send,
|
||||
{
|
||||
filter_fn(move |route| {
|
||||
tracing::trace!("exact2({:?})", T::NAME);
|
||||
route.headers()
|
||||
.typed_get::<T>()
|
||||
.and_then(|val| if val == header {
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.ok_or_else(|| reject::bad_request())
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
/// Create a `Filter` that requires a header to match the value exactly.
|
||||
///
|
||||
/// This `Filter` will look for a header with supplied name and the exact
|
||||
/// value, otherwise rejects the request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // Require `dnt: 1` header to be set.
|
||||
/// let must_dnt = warp::header::exact("dnt", "1");
|
||||
/// ```
|
||||
pub fn exact(
|
||||
name: &'static str,
|
||||
value: &'static str,
|
||||
) -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
filter_fn(move |route| {
|
||||
tracing::trace!("exact?({:?}, {:?})", name, value);
|
||||
let route = route
|
||||
.headers()
|
||||
.get(name)
|
||||
.ok_or_else(|| reject::missing_header(name))
|
||||
.and_then(|val| {
|
||||
if val == value {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(reject::invalid_header(name))
|
||||
}
|
||||
});
|
||||
future::ready(route)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a `Filter` that requires a header to match the value exactly.
|
||||
///
|
||||
/// This `Filter` will look for a header with supplied name and the exact
|
||||
/// value, ignoring ASCII case, otherwise rejects the request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // Require `connection: keep-alive` header to be set.
|
||||
/// let keep_alive = warp::header::exact_ignore_case("connection", "keep-alive");
|
||||
/// ```
|
||||
pub fn exact_ignore_case(
|
||||
name: &'static str,
|
||||
value: &'static str,
|
||||
) -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
filter_fn(move |route| {
|
||||
tracing::trace!("exact_ignore_case({:?}, {:?})", name, value);
|
||||
let route = route
|
||||
.headers()
|
||||
.get(name)
|
||||
.ok_or_else(|| reject::missing_header(name))
|
||||
.and_then(|val| {
|
||||
if val.as_bytes().eq_ignore_ascii_case(value.as_bytes()) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(reject::invalid_header(name))
|
||||
}
|
||||
});
|
||||
future::ready(route)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a `Filter` that gets a `HeaderValue` for the name.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Filter, http::header::HeaderValue};
|
||||
///
|
||||
/// let filter = warp::header::value("x-token")
|
||||
/// .map(|value: HeaderValue| {
|
||||
/// format!("header value bytes: {:?}", value)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn value(
|
||||
name: &'static str,
|
||||
) -> impl Filter<Extract = One<HeaderValue>, Error = Rejection> + Copy {
|
||||
filter_fn_one(move |route| {
|
||||
tracing::trace!("value({:?})", name);
|
||||
let route = route
|
||||
.headers()
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or_else(|| reject::missing_header(name));
|
||||
future::ready(route)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a `Filter` that returns a clone of the request's `HeaderMap`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Filter, http::HeaderMap};
|
||||
///
|
||||
/// let headers = warp::header::headers_cloned()
|
||||
/// .map(|headers: HeaderMap| {
|
||||
/// format!("header count: {}", headers.len())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn headers_cloned() -> impl Filter<Extract = One<HeaderMap>, Error = Infallible> + Copy {
|
||||
filter_fn_one(|route| future::ok(route.headers().clone()))
|
||||
}
|
||||
96
warp/src/filters/host.rs
Normal file
96
warp/src/filters/host.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//! Host ("authority") filter
|
||||
//!
|
||||
use crate::filter::{filter_fn_one, Filter, One};
|
||||
use crate::reject::{self, Rejection};
|
||||
use futures_util::future;
|
||||
pub use http::uri::Authority;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Creates a `Filter` that requires a specific authority (target server's
|
||||
/// host and port) in the request.
|
||||
///
|
||||
/// Authority is specified either in the `Host` header or in the target URI.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let multihost =
|
||||
/// warp::host::exact("foo.com").map(|| "you've reached foo.com")
|
||||
/// .or(warp::host::exact("bar.com").map(|| "you've reached bar.com"));
|
||||
/// ```
|
||||
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()
|
||||
}
|
||||
|
||||
/// Creates a `Filter` that looks for an authority (target server's host
|
||||
/// and port) in the request.
|
||||
///
|
||||
/// Authority is specified either in the `Host` header or in the target URI.
|
||||
///
|
||||
/// If found, extracts the `Authority`, otherwise continues the request,
|
||||
/// extracting `None`.
|
||||
///
|
||||
/// Rejects with `400 Bad Request` if the `Host` header is malformed or if there
|
||||
/// is a mismatch between the `Host` header and the target URI.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Filter, host::Authority};
|
||||
///
|
||||
/// let host = warp::host::optional()
|
||||
/// .map(|authority: Option<Authority>| {
|
||||
/// if let Some(a) = authority {
|
||||
/// format!("{} is currently not at home", a.host())
|
||||
/// } else {
|
||||
/// "please state who you're trying to reach".to_owned()
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
pub fn optional() -> impl Filter<Extract = One<Option<Authority>>, Error = Rejection> + Copy {
|
||||
filter_fn_one(move |route| {
|
||||
// The authority can be sent by clients in various ways:
|
||||
//
|
||||
// 1) in the "target URI"
|
||||
// a) serialized in the start line (HTTP/1.1 proxy requests)
|
||||
// b) serialized in `:authority` pseudo-header (HTTP/2 generated - "SHOULD")
|
||||
// 2) in the `Host` header (HTTP/1.1 origin requests, HTTP/2 converted)
|
||||
//
|
||||
// Hyper transparently handles 1a/1b, but not 2, so we must look at both.
|
||||
|
||||
let from_uri = route.uri().authority();
|
||||
|
||||
let name = "host";
|
||||
let from_header = route.headers()
|
||||
.get(name)
|
||||
.map(|value|
|
||||
// Header present, parse it
|
||||
value.to_str().map_err(|_| reject::invalid_header(name))
|
||||
.and_then(|value| Authority::from_str(value).map_err(|_| reject::invalid_header(name)))
|
||||
);
|
||||
|
||||
future::ready(match (from_uri, from_header) {
|
||||
// no authority in the request (HTTP/1.0 or non-conforming)
|
||||
(None, None) => Ok(None),
|
||||
|
||||
// authority specified in either or both matching
|
||||
(Some(a), None) => Ok(Some(a.clone())),
|
||||
(None, Some(Ok(a))) => Ok(Some(a)),
|
||||
(Some(a), Some(Ok(b))) if *a == b => Ok(Some(b)),
|
||||
|
||||
// mismatch
|
||||
(Some(_), Some(Ok(_))) => Err(reject::invalid_header(name)),
|
||||
|
||||
// parse error
|
||||
(_, Some(Err(r))) => Err(r),
|
||||
})
|
||||
})
|
||||
}
|
||||
197
warp/src/filters/log.rs
Normal file
197
warp/src/filters/log.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
//! Logger Filters
|
||||
use std::fmt;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::{Duration, Instant};
|
||||
use http::{self, StatusCode};
|
||||
use crate::filter::{Filter, WrapSealed};
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::Reply;
|
||||
use crate::route::Route;
|
||||
use self::internal::WithLog;
|
||||
/// Create a wrapping filter with the specified `name` as the `target`.
|
||||
///
|
||||
/// This uses the default access logging format, and log records produced
|
||||
/// will have their `target` set to `name`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // If using something like `pretty_env_logger`,
|
||||
/// // view logs by setting `RUST_LOG=example::api`.
|
||||
/// let log = warp::log("example::api");
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(log);
|
||||
/// ```
|
||||
pub fn log(name: &'static str) -> Log<impl Fn(Info<'_>) + Copy> {
|
||||
let func = move |info: Info<'_>| {
|
||||
log::info!(
|
||||
target : name, "{} \"{} {} {:?}\" {} \"{}\" \"{}\" {:?}", OptFmt(info.route
|
||||
.remote_addr()), info.method(), info.path(), info.route.version(), info
|
||||
.status().as_u16(), OptFmt(info.referer()), OptFmt(info.user_agent()), info
|
||||
.elapsed(),
|
||||
);
|
||||
};
|
||||
Log { func }
|
||||
}
|
||||
/// Create a wrapping filter that receives `warp::log::Info`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let log = warp::log::custom(|info| {
|
||||
/// // Use a log macro, or slog, or println, or whatever!
|
||||
/// eprintln!(
|
||||
/// "{} {} {}",
|
||||
/// info.method(),
|
||||
/// info.path(),
|
||||
/// info.status(),
|
||||
/// );
|
||||
/// });
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(log);
|
||||
/// ```
|
||||
pub fn custom<F>(func: F) -> Log<F>
|
||||
where
|
||||
F: Fn(Info<'_>),
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Decorates a [`Filter`](crate::Filter) to log requests and responses.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Log<F> {
|
||||
func: F,
|
||||
}
|
||||
/// Information about the request/response that can be used to prepare log lines.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Info<'a> {
|
||||
route: &'a Route,
|
||||
start: Instant,
|
||||
status: StatusCode,
|
||||
}
|
||||
impl<FN, F> WrapSealed<F> for Log<FN>
|
||||
where
|
||||
FN: Fn(Info<'_>) + Clone + Send,
|
||||
F: Filter + Clone + Send,
|
||||
F::Extract: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Wrapped = WithLog<FN, F>;
|
||||
fn wrap(&self, filter: F) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<'a> Info<'a> {
|
||||
/// View the remote `SocketAddr` of the request.
|
||||
pub fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
loop {}
|
||||
}
|
||||
/// View the `http::Method` of the request.
|
||||
pub fn method(&self) -> &http::Method {
|
||||
loop {}
|
||||
}
|
||||
/// View the URI path of the request.
|
||||
pub fn path(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
/// View the `http::Version` of the request.
|
||||
pub fn version(&self) -> http::Version {
|
||||
loop {}
|
||||
}
|
||||
/// View the `http::StatusCode` of the response.
|
||||
pub fn status(&self) -> http::StatusCode {
|
||||
loop {}
|
||||
}
|
||||
/// View the referer of the request.
|
||||
pub fn referer(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// View the user agent of the request.
|
||||
pub fn user_agent(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// View the `Duration` that elapsed for the request.
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
loop {}
|
||||
}
|
||||
/// View the host of the request
|
||||
pub fn host(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// Access the full headers of the request
|
||||
pub fn request_headers(&self) -> &http::HeaderMap {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
struct OptFmt<T>(Option<T>);
|
||||
impl<T: fmt::Display> fmt::Display for OptFmt<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
mod internal {
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Instant;
|
||||
use futures_util::{TryFuture};
|
||||
use pin_project::pin_project;
|
||||
use super::{Info, Log};
|
||||
use crate::filter::{Filter, FilterBase, Internal};
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::{Reply, Response};
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Logged(pub(super) Response);
|
||||
impl Reply for Logged {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct WithLog<FN, F> {
|
||||
pub(super) filter: F,
|
||||
pub(super) log: Log<FN>,
|
||||
}
|
||||
impl<FN, F> FilterBase for WithLog<FN, F>
|
||||
where
|
||||
FN: Fn(Info<'_>) + Clone + Send,
|
||||
F: Filter + Clone + Send,
|
||||
F::Extract: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Extract = (Logged,);
|
||||
type Error = F::Error;
|
||||
type Future = WithLogFuture<FN, F::Future>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
pub struct WithLogFuture<FN, F> {
|
||||
log: Log<FN>,
|
||||
#[pin]
|
||||
future: F,
|
||||
started: Instant,
|
||||
}
|
||||
impl<FN, F> Future for WithLogFuture<FN, F>
|
||||
where
|
||||
FN: Fn(Info<'_>),
|
||||
F: TryFuture,
|
||||
F::Ok: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Output = Result<(Logged,), F::Error>;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
warp/src/filters/method.rs
Normal file
135
warp/src/filters/method.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
//! HTTP Method filters.
|
||||
//!
|
||||
//! The filters deal with the HTTP Method part of a request. Several here will
|
||||
//! match the request `Method`, and if not matched, will reject the request
|
||||
//! with a `405 Method Not Allowed`.
|
||||
//!
|
||||
//! There is also [`warp::method()`](method), which never rejects
|
||||
//! a request, and just extracts the method to be used in your filter chains.
|
||||
use futures_util::future;
|
||||
use http::Method;
|
||||
use crate::filter::{filter_fn, filter_fn_one, Filter, One};
|
||||
use crate::reject::Rejection;
|
||||
use std::convert::Infallible;
|
||||
/// Create a `Filter` that requires the request method to be `GET`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let get_only = warp::get().map(warp::reply);
|
||||
/// ```
|
||||
pub fn get() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
method_is(|| &Method::GET)
|
||||
}
|
||||
/// Create a `Filter` that requires the request method to be `POST`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let post_only = warp::post().map(warp::reply);
|
||||
/// ```
|
||||
pub fn post() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
method_is(|| &Method::POST)
|
||||
}
|
||||
/// Create a `Filter` that requires the request method to be `PUT`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let put_only = warp::put().map(warp::reply);
|
||||
/// ```
|
||||
pub fn put() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
method_is(|| &Method::PUT)
|
||||
}
|
||||
/// Create a `Filter` that requires the request method to be `DELETE`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let delete_only = warp::delete().map(warp::reply);
|
||||
/// ```
|
||||
pub fn delete() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
method_is(|| &Method::DELETE)
|
||||
}
|
||||
/// Create a `Filter` that requires the request method to be `HEAD`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let head_only = warp::head().map(warp::reply);
|
||||
/// ```
|
||||
pub fn head() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
method_is(|| &Method::HEAD)
|
||||
}
|
||||
/// Create a `Filter` that requires the request method to be `OPTIONS`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let options_only = warp::options().map(warp::reply);
|
||||
/// ```
|
||||
pub fn options() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
method_is(|| &Method::OPTIONS)
|
||||
}
|
||||
/// Create a `Filter` that requires the request method to be `PATCH`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let patch_only = warp::patch().map(warp::reply);
|
||||
/// ```
|
||||
pub fn patch() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
method_is(|| &Method::PATCH)
|
||||
}
|
||||
/// Extract the `Method` from the request.
|
||||
///
|
||||
/// This never rejects a request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::method()
|
||||
/// .map(|method| {
|
||||
/// format!("You sent a {} request!", method)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn method() -> impl Filter<Extract = One<Method>, Error = Infallible> + Copy {
|
||||
filter_fn_one(|route| future::ok::<_, Infallible>(route.method().clone()))
|
||||
}
|
||||
fn method_is<F>(func: F) -> impl Filter<Extract = (), Error = Rejection> + Copy
|
||||
where
|
||||
F: Fn() -> &'static Method + Copy,
|
||||
{
|
||||
filter_fn(move |route| {
|
||||
let method = func();
|
||||
tracing::trace!("method::{:?}?: {:?}", method, route.method());
|
||||
if route.method() == method {
|
||||
future::ok(())
|
||||
} else {
|
||||
future::err(crate::reject::method_not_allowed())
|
||||
}
|
||||
})
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn method_size_of() {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
29
warp/src/filters/mod.rs
Normal file
29
warp/src/filters/mod.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//! Built-in Filters
|
||||
//!
|
||||
//! This module mostly serves as documentation to group together the list of
|
||||
//! built-in filters. Most of these are available at more convenient paths.
|
||||
|
||||
pub mod addr;
|
||||
pub mod any;
|
||||
pub mod body;
|
||||
#[cfg(any(feature = "compression-brotli", feature = "compression-gzip"))]
|
||||
pub mod compression;
|
||||
pub mod cookie;
|
||||
pub mod cors;
|
||||
pub mod ext;
|
||||
pub mod fs;
|
||||
pub mod header;
|
||||
pub mod host;
|
||||
pub mod log;
|
||||
pub mod method;
|
||||
#[cfg(feature = "multipart")]
|
||||
pub mod multipart;
|
||||
pub mod path;
|
||||
pub mod query;
|
||||
pub mod reply;
|
||||
pub mod sse;
|
||||
pub mod trace;
|
||||
#[cfg(feature = "websocket")]
|
||||
pub mod ws;
|
||||
|
||||
pub use crate::filter::BoxedFilter;
|
||||
116
warp/src/filters/multipart.rs
Normal file
116
warp/src/filters/multipart.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
//! Multipart body filters
|
||||
//!
|
||||
//! Filters that extract a multipart body for a route.
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use bytes::{Buf, Bytes};
|
||||
use futures_util::{future, Stream};
|
||||
use headers::ContentType;
|
||||
use mime::Mime;
|
||||
use multipart::server::Multipart;
|
||||
use crate::filter::{Filter, FilterBase, Internal};
|
||||
use crate::reject::{self, Rejection};
|
||||
const DEFAULT_FORM_DATA_MAX_LENGTH: u64 = 1024 * 1024 * 2;
|
||||
/// A `Filter` to extract a `multipart/form-data` body from a request.
|
||||
///
|
||||
/// Create with the `warp::multipart::form()` function.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FormOptions {
|
||||
max_length: u64,
|
||||
}
|
||||
/// A `Stream` of multipart/form-data `Part`s.
|
||||
///
|
||||
/// Extracted with a `warp::multipart::form` filter.
|
||||
pub struct FormData {
|
||||
inner: Multipart<Cursor<::bytes::Bytes>>,
|
||||
}
|
||||
/// A single "part" of a multipart/form-data body.
|
||||
///
|
||||
/// Yielded from the `FormData` stream.
|
||||
pub struct Part {
|
||||
name: String,
|
||||
filename: Option<String>,
|
||||
content_type: Option<String>,
|
||||
data: Option<Vec<u8>>,
|
||||
}
|
||||
/// Create a `Filter` to extract a `multipart/form-data` body from a request.
|
||||
///
|
||||
/// The extracted `FormData` type is a `Stream` of `Part`s, and each `Part`
|
||||
/// in turn is a `Stream` of bytes.
|
||||
pub fn form() -> FormOptions {
|
||||
loop {}
|
||||
}
|
||||
impl FormOptions {
|
||||
/// Set the maximum byte length allowed for this body.
|
||||
///
|
||||
/// Defaults to 2MB.
|
||||
pub fn max_length(mut self, max: u64) -> Self {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
type FormFut = Pin<Box<dyn Future<Output = Result<(FormData,), Rejection>> + Send>>;
|
||||
impl FilterBase for FormOptions {
|
||||
type Extract = (FormData,);
|
||||
type Error = Rejection;
|
||||
type Future = FormFut;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for FormData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Stream for FormData {
|
||||
type Item = Result<Part, crate::Error>;
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Part {
|
||||
/// Get the name of this part.
|
||||
pub fn name(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
/// Get the filename of this part, if present.
|
||||
pub fn filename(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// Get the content-type of this part, if present.
|
||||
pub fn content_type(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// Asynchronously get some of the data for this `Part`.
|
||||
pub async fn data(&mut self) -> Option<Result<impl Buf, crate::Error>> {
|
||||
loop {}
|
||||
}
|
||||
/// Convert this `Part` into a `Stream` of `Buf`s.
|
||||
pub fn stream(self) -> impl Stream<Item = Result<impl Buf, crate::Error>> {
|
||||
loop {}
|
||||
}
|
||||
fn take_data(&mut self) -> Option<Result<Bytes, crate::Error>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Part {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
struct PartStream(Part);
|
||||
impl Stream for PartStream {
|
||||
type Item = Result<Bytes, crate::Error>;
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
527
warp/src/filters/path.rs
Normal file
527
warp/src/filters/path.rs
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
//! Path Filters
|
||||
//!
|
||||
//! The filters here work on the "path" of requests.
|
||||
//!
|
||||
//! - [`path`](./fn.path.html) matches a specific segment, like `/foo`.
|
||||
//! - [`param`](./fn.param.html) tries to parse a segment into a type, like `/:u16`.
|
||||
//! - [`end`](./fn.end.html) matches when the path end is found.
|
||||
//! - [`path!`](../../macro.path.html) eases combining multiple `path` and `param` filters.
|
||||
//!
|
||||
//! # Routing
|
||||
//!
|
||||
//! Routing in warp is simple yet powerful.
|
||||
//!
|
||||
//! First up, matching a single segment:
|
||||
//!
|
||||
//! ```
|
||||
//! use warp::Filter;
|
||||
//!
|
||||
//! // GET /hi
|
||||
//! let hi = warp::path("hi").map(|| {
|
||||
//! "Hello, World!"
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! How about multiple segments? It's easiest with the `path!` macro:
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! // GET /hello/from/warp
|
||||
//! let hello_from_warp = warp::path!("hello" / "from" / "warp").map(|| {
|
||||
//! "Hello from warp!"
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! Neat! But how do I handle **parameters** in paths?
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! // GET /sum/:u32/:u32
|
||||
//! let sum = warp::path!("sum" / u32 / u32).map(|a, b| {
|
||||
//! format!("{} + {} = {}", a, b, a + b)
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! In fact, any type that implements `FromStr` can be used, in any order:
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! // GET /:u16/times/:u16
|
||||
//! let times = warp::path!(u16 / "times" / u16).map(|a, b| {
|
||||
//! format!("{} times {} = {}", a, b, a * b)
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! Oh shoot, those math routes should be **mounted** at a different path,
|
||||
//! is that possible? Yep!
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! # let sum = warp::any().map(warp::reply);
|
||||
//! # let times = sum.clone();
|
||||
//! // GET /math/sum/:u32/:u32
|
||||
//! // GET /math/:u16/times/:u16
|
||||
//! let math = warp::path("math");
|
||||
//! let math_sum = math.and(sum);
|
||||
//! let math_times = math.and(times);
|
||||
//! ```
|
||||
//!
|
||||
//! What! `and`? What's that do?
|
||||
//!
|
||||
//! It combines the filters in a sort of "this and then that" order. In fact,
|
||||
//! it's exactly what the `path!` macro has been doing internally.
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! // GET /bye/:string
|
||||
//! let bye = warp::path("bye")
|
||||
//! .and(warp::path::param())
|
||||
//! .map(|name: String| {
|
||||
//! format!("Good bye, {}!", name)
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! Ah, so, can filters do things besides `and`?
|
||||
//!
|
||||
//! Why, yes they can! They can also `or`! As you might expect, `or` creates a
|
||||
//! "this or else that" chain of filters. If the first doesn't succeed, then
|
||||
//! it tries the other.
|
||||
//!
|
||||
//! So, those `math` routes could have been **mounted** all as one, with `or`.
|
||||
//!
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! # let sum = warp::path("sum");
|
||||
//! # let times = warp::path("times");
|
||||
//! // GET /math/sum/:u32/:u32
|
||||
//! // GET /math/:u16/times/:u16
|
||||
//! let math = warp::path("math")
|
||||
//! .and(sum.or(times));
|
||||
//! ```
|
||||
//!
|
||||
//! It turns out, using `or` is how you combine everything together into a
|
||||
//! single API.
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! # let hi = warp::path("hi");
|
||||
//! # let hello_from_warp = hi.clone();
|
||||
//! # let bye = hi.clone();
|
||||
//! # let math = hi.clone();
|
||||
//! // GET /hi
|
||||
//! // GET /hello/from/warp
|
||||
//! // GET /bye/:string
|
||||
//! // GET /math/sum/:u32/:u32
|
||||
//! // GET /math/:u16/times/:u16
|
||||
//! let routes = hi
|
||||
//! .or(hello_from_warp)
|
||||
//! .or(bye)
|
||||
//! .or(math);
|
||||
//! ```
|
||||
//!
|
||||
//! Note that you will generally want path filters to come **before** other filters
|
||||
//! like `body` or `headers`. If a different type of filter comes first, a request
|
||||
//! with an invalid body for route `/right-path-wrong-body` may try matching against `/wrong-path`
|
||||
//! and return the error from `/wrong-path` instead of the correct body-related error.
|
||||
use std::convert::Infallible;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use futures_util::future;
|
||||
use http::uri::PathAndQuery;
|
||||
use self::internal::Opaque;
|
||||
use crate::filter::{filter_fn, one, Filter, FilterBase, Internal, One, Tuple};
|
||||
use crate::reject::{self, Rejection};
|
||||
use crate::route::Route;
|
||||
/// Create an exact match path segment `Filter`.
|
||||
///
|
||||
/// This will try to match exactly to the current request path segment.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// - [`end()`](./fn.end.html) should be used to match the end of a path to avoid having
|
||||
/// filters for shorter paths like `/math` unintentionally match a longer
|
||||
/// path such as `/math/sum`
|
||||
/// - Path-related filters should generally come **before** other types of filters, such
|
||||
/// as those checking headers or body types. Including those other filters before
|
||||
/// the path checks may result in strange errors being returned because a given request
|
||||
/// does not match the parameters for a completely separate route.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Exact path filters cannot be empty, or contain slashes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Matches '/hello'
|
||||
/// let hello = warp::path("hello")
|
||||
/// .map(|| "Hello, World!");
|
||||
/// ```
|
||||
pub fn path<P>(p: P) -> Exact<Opaque<P>>
|
||||
where
|
||||
P: AsRef<str>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// A `Filter` matching an exact path segment.
|
||||
///
|
||||
/// Constructed from `path()` or `path!()`.
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Exact<P>(P);
|
||||
impl<P> FilterBase for Exact<P>
|
||||
where
|
||||
P: AsRef<str>,
|
||||
{
|
||||
type Extract = ();
|
||||
type Error = Rejection;
|
||||
type Future = future::Ready<Result<Self::Extract, Self::Error>>;
|
||||
#[inline]
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Matches the end of a route.
|
||||
///
|
||||
/// Note that _not_ including `end()` may result in shorter paths like
|
||||
/// `/math` unintentionally matching `/math/sum`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Matches '/'
|
||||
/// let hello = warp::path::end()
|
||||
/// .map(|| "Hello, World!");
|
||||
/// ```
|
||||
pub fn end() -> impl Filter<Extract = (), Error = Rejection> + Copy {
|
||||
filter_fn(move |route| {
|
||||
if route.path().is_empty() {
|
||||
future::ok(())
|
||||
} else {
|
||||
future::err(reject::not_found())
|
||||
}
|
||||
})
|
||||
}
|
||||
/// Extract a parameter from a path segment.
|
||||
///
|
||||
/// This will try to parse a value from the current request path
|
||||
/// segment, and if successful, the value is returned as the `Filter`'s
|
||||
/// "extracted" value.
|
||||
///
|
||||
/// If the value could not be parsed, rejects with a `404 Not Found`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::path::param()
|
||||
/// .map(|id: u32| {
|
||||
/// format!("You asked for /{}", id)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn param<T: FromStr + Send + 'static>() -> impl Filter<
|
||||
Extract = One<T>,
|
||||
Error = Rejection,
|
||||
> + Copy {
|
||||
filter_segment(|seg| {
|
||||
tracing::trace!("param?: {:?}", seg);
|
||||
if seg.is_empty() {
|
||||
return Err(reject::not_found());
|
||||
}
|
||||
T::from_str(seg).map(one).map_err(|_| reject::not_found())
|
||||
})
|
||||
}
|
||||
/// Extract the unmatched tail of the path.
|
||||
///
|
||||
/// This will return a `Tail`, which allows access to the rest of the path
|
||||
/// that previous filters have not already matched.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::path("foo")
|
||||
/// .and(warp::path::tail())
|
||||
/// .map(|tail| {
|
||||
/// // GET /foo/bar/baz would return "bar/baz".
|
||||
/// format!("The tail after foo is {:?}", tail)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn tail() -> impl Filter<Extract = One<Tail>, Error = Infallible> + Copy {
|
||||
filter_fn(move |route| {
|
||||
let path = path_and_query(route);
|
||||
let idx = route.matched_path_index();
|
||||
let end = path.path().len() - idx;
|
||||
route.set_unmatched_path(end);
|
||||
future::ok(one(Tail { path, start_index: idx }))
|
||||
})
|
||||
}
|
||||
/// Represents the tail part of a request path, returned by the [`tail()`] filter.
|
||||
pub struct Tail {
|
||||
path: PathAndQuery,
|
||||
start_index: usize,
|
||||
}
|
||||
impl Tail {
|
||||
/// Get the `&str` representation of the remaining path.
|
||||
pub fn as_str(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Tail {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Peek at the unmatched tail of the path, without affecting the matched path.
|
||||
///
|
||||
/// This will return a `Peek`, which allows access to the rest of the path
|
||||
/// that previous filters have not already matched. This differs from `tail`
|
||||
/// in that `peek` will **not** set the entire path as matched.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::path("foo")
|
||||
/// .and(warp::path::peek())
|
||||
/// .map(|peek| {
|
||||
/// // GET /foo/bar/baz would return "bar/baz".
|
||||
/// format!("The path after foo is {:?}", peek)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn peek() -> impl Filter<Extract = One<Peek>, Error = Infallible> + Copy {
|
||||
filter_fn(move |route| {
|
||||
let path = path_and_query(route);
|
||||
let idx = route.matched_path_index();
|
||||
future::ok(one(Peek { path, start_index: idx }))
|
||||
})
|
||||
}
|
||||
/// Represents the tail part of a request path, returned by the [`peek()`] filter.
|
||||
pub struct Peek {
|
||||
path: PathAndQuery,
|
||||
start_index: usize,
|
||||
}
|
||||
impl Peek {
|
||||
/// Get the `&str` representation of the remaining path.
|
||||
pub fn as_str(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
/// Get an iterator over the segments of the peeked path.
|
||||
pub fn segments(&self) -> impl Iterator<Item = &str> {
|
||||
self.as_str().split('/').filter(|seg| !seg.is_empty())
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Peek {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Returns the full request path, irrespective of other filters.
|
||||
///
|
||||
/// This will return a `FullPath`, which can be stringified to return the
|
||||
/// full path of the request.
|
||||
///
|
||||
/// This is more useful in generic pre/post-processing filters, and should
|
||||
/// probably not be used for request matching/routing.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Filter, path::FullPath};
|
||||
/// use std::{collections::HashMap, sync::{Arc, Mutex}};
|
||||
///
|
||||
/// let counts = Arc::new(Mutex::new(HashMap::new()));
|
||||
/// let access_counter = warp::path::full()
|
||||
/// .map(move |path: FullPath| {
|
||||
/// let mut counts = counts.lock().unwrap();
|
||||
///
|
||||
/// *counts.entry(path.as_str().to_string())
|
||||
/// .and_modify(|c| *c += 1)
|
||||
/// .or_insert(0)
|
||||
/// });
|
||||
///
|
||||
/// let route = warp::path("foo")
|
||||
/// .and(warp::path("bar"))
|
||||
/// .and(access_counter)
|
||||
/// .map(|count| {
|
||||
/// format!("This is the {}th visit to this URL!", count)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn full() -> impl Filter<Extract = One<FullPath>, Error = Infallible> + Copy {
|
||||
filter_fn(move |route| future::ok(one(FullPath(path_and_query(route)))))
|
||||
}
|
||||
/// Represents the full request path, returned by the [`full()`] filter.
|
||||
pub struct FullPath(PathAndQuery);
|
||||
impl FullPath {
|
||||
/// Get the `&str` representation of the request path.
|
||||
pub fn as_str(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for FullPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
fn filter_segment<F, U>(func: F) -> impl Filter<Extract = U, Error = Rejection> + Copy
|
||||
where
|
||||
F: Fn(&str) -> Result<U, Rejection> + Copy,
|
||||
U: Tuple + Send + 'static,
|
||||
{
|
||||
filter_fn(move |route| future::ready(with_segment(route, func)))
|
||||
}
|
||||
fn with_segment<F, U>(route: &mut Route, func: F) -> Result<U, Rejection>
|
||||
where
|
||||
F: Fn(&str) -> Result<U, Rejection>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
fn path_and_query(route: &Route) -> PathAndQuery {
|
||||
loop {}
|
||||
}
|
||||
/// Convenient way to chain multiple path filters together.
|
||||
///
|
||||
/// Any number of either type identifiers or string expressions can be passed,
|
||||
/// each separated by a forward slash (`/`). Strings will be used to match
|
||||
/// path segments exactly, and type identifiers are used just like
|
||||
/// [`param`](crate::path::param) filters.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Match `/sum/:a/:b`
|
||||
/// let route = warp::path!("sum" / u32 / u32)
|
||||
/// .map(|a, b| {
|
||||
/// format!("{} + {} = {}", a, b, a + b)
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The equivalent filter chain without using the `path!` macro looks this:
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::path("sum")
|
||||
/// .and(warp::path::param::<u32>())
|
||||
/// .and(warp::path::param::<u32>())
|
||||
/// .and(warp::path::end())
|
||||
/// .map(|a, b| {
|
||||
/// format!("{} + {} = {}", a, b, a + b)
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// # Path Prefixes
|
||||
///
|
||||
/// The `path!` macro automatically assumes the path should include an `end()`
|
||||
/// filter. To build up a path filter *prefix*, such that the `end()` isn't
|
||||
/// included, use the `/ ..` syntax.
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let prefix = warp::path!("math" / "sum" / ..);
|
||||
///
|
||||
/// let sum = warp::path!(u32 / u32)
|
||||
/// .map(|a, b| {
|
||||
/// format!("{} + {} = {}", a, b, a + b)
|
||||
/// });
|
||||
///
|
||||
/// let help = warp::path::end()
|
||||
/// .map(|| "This API returns the sum of two u32's");
|
||||
///
|
||||
/// let api = prefix.and(sum.or(help));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! path {
|
||||
($($pieces:tt)*) => {
|
||||
{ $crate ::__internal_path!(@ start $($pieces)*) }
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __internal_path {
|
||||
(@ start) => {
|
||||
$crate ::path::end()
|
||||
};
|
||||
(@ start ..) => {
|
||||
{ compile_error!("'..' cannot be the only segment") }
|
||||
};
|
||||
(@ start $first:tt $(/ $tail:tt)*) => {
|
||||
{ $crate ::__internal_path!(@ munch $crate ::any(); [$first] [$(/ $tail)*]) }
|
||||
};
|
||||
(@ munch $sum:expr; [$cur:tt] [/ $next:tt $(/ $tail:tt)*]) => {
|
||||
{ $crate ::__internal_path!(@ munch $crate ::Filter::and($sum, $crate
|
||||
::__internal_path!(@ segment $cur)); [$next] [$(/ $tail)*]) }
|
||||
};
|
||||
(@ munch $sum:expr; [$cur:tt] []) => {
|
||||
{ $crate ::__internal_path!(@ last $sum; $cur) }
|
||||
};
|
||||
(@ last $sum:expr; ..) => {
|
||||
$sum
|
||||
};
|
||||
(@ last $sum:expr; $end:tt) => {
|
||||
$crate ::Filter::and($crate ::Filter::and($sum, $crate ::__internal_path!(@
|
||||
segment $end)), $crate ::path::end())
|
||||
};
|
||||
(@ segment ..) => {
|
||||
compile_error!("'..' must be the last segment")
|
||||
};
|
||||
(@ segment $param:ty) => {
|
||||
$crate ::path::param::<$param > ()
|
||||
};
|
||||
(@ segment $s:literal) => {
|
||||
{ #[derive(Clone, Copy)] struct __StaticPath; impl ::std::convert::AsRef < str >
|
||||
for __StaticPath { fn as_ref(& self) -> & str { static S : & str = $s; S } }
|
||||
$crate ::path(__StaticPath) }
|
||||
};
|
||||
}
|
||||
/// ```compile_fail
|
||||
/// warp::path!("foo" / .. / "bar");
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// warp::path!(.. / "bar");
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// warp::path!("foo" ..);
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// warp::path!("foo" / .. /);
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// warp::path!(..);
|
||||
/// ```
|
||||
fn _path_macro_compile_fail() {}
|
||||
mod internal {
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Opaque<T>(pub(super) T);
|
||||
impl<T: AsRef<str>> AsRef<str> for Opaque<T> {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_path_exact_size() {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
91
warp/src/filters/query.rs
Normal file
91
warp/src/filters/query.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
//! Query Filters
|
||||
|
||||
use futures_util::future;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_urlencoded;
|
||||
|
||||
use crate::filter::{filter_fn_one, Filter, One};
|
||||
use crate::reject::{self, Rejection};
|
||||
|
||||
/// Creates a `Filter` that decodes query parameters to the type `T`.
|
||||
///
|
||||
/// If cannot decode into a `T`, the request is rejected with a `400 Bad Request`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::collections::HashMap;
|
||||
/// use warp::{
|
||||
/// http::Response,
|
||||
/// Filter,
|
||||
/// };
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .and(warp::query::<HashMap<String, String>>())
|
||||
/// .map(|map: HashMap<String, String>| {
|
||||
/// let mut response: Vec<String> = Vec::new();
|
||||
/// for (key, value) in map.into_iter() {
|
||||
/// response.push(format!("{}={}", key, value))
|
||||
/// }
|
||||
/// Response::builder().body(response.join(";"))
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// You can define your custom query object and deserialize with [Serde][Serde]. Ensure to include
|
||||
/// the crate in your dependencies before usage.
|
||||
///
|
||||
/// ```
|
||||
/// use serde_derive::{Deserialize, Serialize};
|
||||
/// use std::collections::HashMap;
|
||||
/// use warp::{
|
||||
/// http::Response,
|
||||
/// Filter,
|
||||
/// };
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct FooQuery {
|
||||
/// foo: Option<String>,
|
||||
/// bar: u8,
|
||||
/// }
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .and(warp::query::<FooQuery>())
|
||||
/// .map(|q: FooQuery| {
|
||||
/// if let Some(foo) = q.foo {
|
||||
/// Response::builder().body(format!("foo={}", foo))
|
||||
/// } else {
|
||||
/// Response::builder().body(format!("bar={}", q.bar))
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// For more examples, please take a look at [examples/query_string.rs](https://github.com/seanmonstar/warp/blob/master/examples/query_string.rs).
|
||||
///
|
||||
/// [Serde]: https://docs.rs/serde
|
||||
pub fn query<T: DeserializeOwned + Send + 'static>(
|
||||
) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
|
||||
filter_fn_one(|route| {
|
||||
let query_string = route.query().unwrap_or_else(|| {
|
||||
tracing::debug!("route was called without a query string, defaulting to empty");
|
||||
""
|
||||
});
|
||||
|
||||
let query_encoded = serde_urlencoded::from_str(query_string).map_err(|e| {
|
||||
tracing::debug!("failed to decode query string '{}': {:?}", query_string, e);
|
||||
reject::invalid_query()
|
||||
});
|
||||
future::ready(query_encoded)
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a `Filter` that returns the raw query string as type String.
|
||||
pub fn raw() -> impl Filter<Extract = One<String>, Error = Rejection> + Copy {
|
||||
filter_fn_one(|route| {
|
||||
let route = route
|
||||
.query()
|
||||
.map(|q| q.to_owned())
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| Err(reject::invalid_query()));
|
||||
future::ready(route)
|
||||
})
|
||||
}
|
||||
201
warp/src/filters/reply.rs
Normal file
201
warp/src/filters/reply.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
//! Reply Filters
|
||||
//!
|
||||
//! These "filters" behave a little differently than the rest. Instead of
|
||||
//! being used directly on requests, these filters "wrap" other filters.
|
||||
//!
|
||||
//!
|
||||
//! ## Wrapping a `Filter` (`with`)
|
||||
//!
|
||||
//! ```
|
||||
//! use warp::Filter;
|
||||
//!
|
||||
//! let with_server = warp::reply::with::header("server", "warp");
|
||||
//!
|
||||
//! let route = warp::any()
|
||||
//! .map(warp::reply)
|
||||
//! .with(with_server);
|
||||
//! ```
|
||||
//!
|
||||
//! Wrapping allows adding in conditional logic *before* the request enters
|
||||
//! the inner filter (though the `with::header` wrapper does not).
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use http::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use self::sealed::{WithDefaultHeader_, WithHeader_, WithHeaders_};
|
||||
use crate::filter::{Filter, Map, WrapSealed};
|
||||
use crate::reply::Reply;
|
||||
/// Wrap a [`Filter`](crate::Filter) that adds a header to the reply.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This **only** adds a header if the underlying filter is successful, and
|
||||
/// returns a [`Reply`](Reply). If the underlying filter was rejected, the
|
||||
/// header is not added.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Always set `foo: bar` header.
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(warp::reply::with::header("foo", "bar"));
|
||||
/// ```
|
||||
pub fn header<K, V>(name: K, value: V) -> WithHeader
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
||||
HeaderValue: TryFrom<V>,
|
||||
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Wrap a [`Filter`](crate::Filter) that adds multiple headers to the reply.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This **only** adds a header if the underlying filter is successful, and
|
||||
/// returns a [`Reply`](Reply). If the underlying filter was rejected, the
|
||||
/// header is not added.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::http::header::{HeaderMap, HeaderValue};
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let mut headers = HeaderMap::new();
|
||||
/// headers.insert("server", HeaderValue::from_static("wee/0"));
|
||||
/// headers.insert("foo", HeaderValue::from_static("bar"));
|
||||
///
|
||||
/// // Always set `server: wee/0` and `foo: bar` headers.
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(warp::reply::with::headers(headers));
|
||||
/// ```
|
||||
pub fn headers(headers: HeaderMap) -> WithHeaders {
|
||||
loop {}
|
||||
}
|
||||
/// Wrap a [`Filter`](crate::Filter) that adds a header to the reply, if they
|
||||
/// aren't already set.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This **only** adds a header if the underlying filter is successful, and
|
||||
/// returns a [`Reply`](Reply). If the underlying filter was rejected, the
|
||||
/// header is not added.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // Set `server: warp` if not already set.
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(warp::reply::with::default_header("server", "warp"));
|
||||
/// ```
|
||||
pub fn default_header<K, V>(name: K, value: V) -> WithDefaultHeader
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
||||
HeaderValue: TryFrom<V>,
|
||||
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Wrap a `Filter` to always set a header.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WithHeader {
|
||||
name: HeaderName,
|
||||
value: HeaderValue,
|
||||
}
|
||||
impl<F, R> WrapSealed<F> for WithHeader
|
||||
where
|
||||
F: Filter<Extract = (R,)>,
|
||||
R: Reply,
|
||||
{
|
||||
type Wrapped = Map<F, WithHeader_>;
|
||||
fn wrap(&self, filter: F) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// 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 {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// 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 {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
fn assert_name_and_value<K, V>(name: K, value: V) -> (HeaderName, HeaderValue)
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
||||
HeaderValue: TryFrom<V>,
|
||||
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
mod sealed {
|
||||
use super::{WithDefaultHeader, WithHeader, WithHeaders};
|
||||
use crate::generic::{Func, One};
|
||||
use crate::reply::{Reply, Reply_};
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct WithHeader_ {
|
||||
pub(super) with: WithHeader,
|
||||
}
|
||||
impl<R: Reply> Func<One<R>> for WithHeader_ {
|
||||
type Output = Reply_;
|
||||
fn call(&self, args: One<R>) -> Self::Output {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct WithHeaders_ {
|
||||
pub(super) with: WithHeaders,
|
||||
}
|
||||
impl<R: Reply> Func<One<R>> for WithHeaders_ {
|
||||
type Output = Reply_;
|
||||
fn call(&self, args: One<R>) -> Self::Output {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct WithDefaultHeader_ {
|
||||
pub(super) with: WithDefaultHeader,
|
||||
}
|
||||
impl<R: Reply> Func<One<R>> for WithDefaultHeader_ {
|
||||
type Output = Reply_;
|
||||
fn call(&self, args: One<R>) -> Self::Output {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
380
warp/src/filters/sse.rs
Normal file
380
warp/src/filters/sse.rs
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
//! Server-Sent Events (SSE)
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! use std::time::Duration;
|
||||
//! use std::convert::Infallible;
|
||||
//! use warp::{Filter, sse::Event};
|
||||
//! use futures_util::{stream::iter, Stream};
|
||||
//!
|
||||
//! fn sse_events() -> impl Stream<Item = Result<Event, Infallible>> {
|
||||
//! iter(vec![
|
||||
//! Ok(Event::default().data("unnamed event")),
|
||||
//! Ok(
|
||||
//! Event::default().event("chat")
|
||||
//! .data("chat message")
|
||||
//! ),
|
||||
//! Ok(
|
||||
//! Event::default().id(13.to_string())
|
||||
//! .event("chat")
|
||||
//! .data("other chat message\nwith next line")
|
||||
//! .retry(Duration::from_millis(5000))
|
||||
//! )
|
||||
//! ])
|
||||
//! }
|
||||
//!
|
||||
//! let app = warp::path("push-notifications")
|
||||
//! .and(warp::get())
|
||||
//! .map(|| {
|
||||
//! warp::sse::reply(warp::sse::keep_alive().stream(sse_events()))
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! Each field already is event which can be sent to client.
|
||||
//! The events with multiple fields can be created by combining fields using tuples.
|
||||
//!
|
||||
//! See also the [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) API,
|
||||
//! which specifies the expected behavior of Server Sent Events.
|
||||
//!
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
use futures_util::{Stream, TryStream};
|
||||
|
||||
|
||||
use pin_project::pin_project;
|
||||
use serde_json::{self, Error};
|
||||
use tokio::time::{self, Sleep};
|
||||
use self::sealed::SseError;
|
||||
use super::header;
|
||||
use crate::filter::One;
|
||||
use crate::reply::Response;
|
||||
use crate::{Filter, Rejection, Reply};
|
||||
#[derive(Debug)]
|
||||
enum DataType {
|
||||
Text(String),
|
||||
Json(String),
|
||||
}
|
||||
/// Server-sent event
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Event {
|
||||
id: Option<String>,
|
||||
data: Option<DataType>,
|
||||
event: Option<String>,
|
||||
comment: Option<String>,
|
||||
retry: Option<Duration>,
|
||||
}
|
||||
impl Event {
|
||||
/// Set Server-sent event data
|
||||
/// data field(s) ("data:<content>")
|
||||
pub fn data<T: Into<String>>(mut self, data: T) -> Event {
|
||||
loop {}
|
||||
}
|
||||
/// Set Server-sent event data
|
||||
/// data field(s) ("data:<content>")
|
||||
pub fn json_data<T: Serialize>(mut self, data: T) -> Result<Event, Error> {
|
||||
loop {}
|
||||
}
|
||||
/// Set Server-sent event comment
|
||||
/// Comment field (":<comment-text>")
|
||||
pub fn comment<T: Into<String>>(mut self, comment: T) -> Event {
|
||||
loop {}
|
||||
}
|
||||
/// Set Server-sent event event
|
||||
/// Event name field ("event:<event-name>")
|
||||
pub fn event<T: Into<String>>(mut self, event: T) -> Event {
|
||||
loop {}
|
||||
}
|
||||
/// Set Server-sent event retry
|
||||
/// Retry timeout field ("retry:<timeout>")
|
||||
pub fn retry(mut self, duration: Duration) -> Event {
|
||||
loop {}
|
||||
}
|
||||
/// Set Server-sent event id
|
||||
/// Identifier field ("id:<identifier>")
|
||||
pub fn id<T: Into<String>>(mut self, id: T) -> Event {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Event {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Gets the optional last event id from request.
|
||||
/// Typically this identifier represented as number or string.
|
||||
///
|
||||
/// ```
|
||||
/// let app = warp::sse::last_event_id::<u32>();
|
||||
///
|
||||
/// // The identifier is present
|
||||
/// async {
|
||||
/// assert_eq!(
|
||||
/// warp::test::request()
|
||||
/// .header("Last-Event-ID", "12")
|
||||
/// .filter(&app)
|
||||
/// .await
|
||||
/// .unwrap(),
|
||||
/// Some(12)
|
||||
/// );
|
||||
///
|
||||
/// // The identifier is missing
|
||||
/// assert_eq!(
|
||||
/// warp::test::request()
|
||||
/// .filter(&app)
|
||||
/// .await
|
||||
/// .unwrap(),
|
||||
/// None
|
||||
/// );
|
||||
///
|
||||
/// // The identifier is not a valid
|
||||
/// assert!(
|
||||
/// warp::test::request()
|
||||
/// .header("Last-Event-ID", "abc")
|
||||
/// .filter(&app)
|
||||
/// .await
|
||||
/// .is_err(),
|
||||
/// );
|
||||
///};
|
||||
/// ```
|
||||
pub fn last_event_id<T>() -> impl Filter<
|
||||
Extract = One<Option<T>>,
|
||||
Error = Rejection,
|
||||
> + Copy
|
||||
where
|
||||
T: FromStr + Send + Sync + 'static,
|
||||
{
|
||||
header::optional("last-event-id")
|
||||
}
|
||||
/// Server-sent events reply
|
||||
///
|
||||
/// This function converts stream of server events into a `Reply` with:
|
||||
///
|
||||
/// - Status of `200 OK`
|
||||
/// - Header `content-type: text/event-stream`
|
||||
/// - Header `cache-control: no-cache`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
/// use futures_util::Stream;
|
||||
/// use futures_util::stream::iter;
|
||||
/// use std::convert::Infallible;
|
||||
/// use warp::{Filter, sse::Event};
|
||||
/// use serde_derive::Serialize;
|
||||
///
|
||||
/// #[derive(Serialize)]
|
||||
/// struct Msg {
|
||||
/// from: u32,
|
||||
/// text: String,
|
||||
/// }
|
||||
///
|
||||
/// fn event_stream() -> impl Stream<Item = Result<Event, Infallible>> {
|
||||
/// iter(vec![
|
||||
/// // Unnamed event with data only
|
||||
/// Ok(Event::default().data("payload")),
|
||||
/// // Named event with ID and retry timeout
|
||||
/// Ok(
|
||||
/// Event::default().data("other message\nwith next line")
|
||||
/// .event("chat")
|
||||
/// .id(1.to_string())
|
||||
/// .retry(Duration::from_millis(15000))
|
||||
/// ),
|
||||
/// // Event with JSON data
|
||||
/// Ok(
|
||||
/// Event::default().id(2.to_string())
|
||||
/// .json_data(Msg {
|
||||
/// from: 2,
|
||||
/// text: "hello".into(),
|
||||
/// }).unwrap(),
|
||||
/// )
|
||||
/// ])
|
||||
/// }
|
||||
///
|
||||
/// async {
|
||||
/// let app = warp::path("sse").and(warp::get()).map(|| {
|
||||
/// warp::sse::reply(event_stream())
|
||||
/// });
|
||||
///
|
||||
/// let res = warp::test::request()
|
||||
/// .method("GET")
|
||||
/// .header("Connection", "Keep-Alive")
|
||||
/// .path("/sse")
|
||||
/// .reply(&app)
|
||||
/// .await
|
||||
/// .into_body();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// res,
|
||||
/// r#"data:payload
|
||||
///
|
||||
/// event:chat
|
||||
/// data:other message
|
||||
/// data:with next line
|
||||
/// id:1
|
||||
/// retry:15000
|
||||
///
|
||||
/// data:{"from":2,"text":"hello"}
|
||||
/// id:2
|
||||
///
|
||||
/// "#
|
||||
/// );
|
||||
/// };
|
||||
/// ```
|
||||
pub fn reply<S>(event_stream: S) -> impl Reply
|
||||
where
|
||||
S: TryStream<Ok = Event> + Send + 'static,
|
||||
S::Error: StdError + Send + Sync + 'static,
|
||||
{
|
||||
SseReply { event_stream }
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
struct SseReply<S> {
|
||||
event_stream: S,
|
||||
}
|
||||
impl<S> Reply for SseReply<S>
|
||||
where
|
||||
S: TryStream<Ok = Event> + Send + 'static,
|
||||
S::Error: StdError + Send + Sync + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Configure the interval between keep-alive messages, the content
|
||||
/// of each message, and the associated stream.
|
||||
#[derive(Debug)]
|
||||
pub struct KeepAlive {
|
||||
comment_text: Cow<'static, str>,
|
||||
max_interval: Duration,
|
||||
}
|
||||
impl KeepAlive {
|
||||
/// Customize the interval between keep-alive messages.
|
||||
///
|
||||
/// Default is 15 seconds.
|
||||
pub fn interval(mut self, time: Duration) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Customize the text of the keep-alive message.
|
||||
///
|
||||
/// Default is an empty comment.
|
||||
pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Wrap an event stream with keep-alive functionality.
|
||||
///
|
||||
/// See [`keep_alive`](keep_alive) for more.
|
||||
pub fn stream<S>(
|
||||
self,
|
||||
event_stream: S,
|
||||
) -> impl TryStream<
|
||||
Ok = Event,
|
||||
Error = impl StdError + Send + Sync + 'static,
|
||||
> + Send + 'static
|
||||
where
|
||||
S: TryStream<Ok = Event> + Send + 'static,
|
||||
S::Error: StdError + Send + Sync + 'static,
|
||||
{
|
||||
let alive_timer = time::sleep(self.max_interval);
|
||||
SseKeepAlive {
|
||||
event_stream,
|
||||
comment_text: self.comment_text,
|
||||
max_interval: self.max_interval,
|
||||
alive_timer,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[pin_project]
|
||||
struct SseKeepAlive<S> {
|
||||
#[pin]
|
||||
event_stream: S,
|
||||
comment_text: Cow<'static, str>,
|
||||
max_interval: Duration,
|
||||
#[pin]
|
||||
alive_timer: Sleep,
|
||||
}
|
||||
/// Keeps event source connection alive when no events sent over a some time.
|
||||
///
|
||||
/// Some proxy servers may drop HTTP connection after a some timeout of inactivity.
|
||||
/// This function helps to prevent such behavior by sending comment events every
|
||||
/// `keep_interval` of inactivity.
|
||||
///
|
||||
/// By default the comment is `:` (an empty comment) and the time interval between
|
||||
/// events is 15 seconds. Both may be customized using the builder pattern
|
||||
/// as shown below.
|
||||
///
|
||||
/// ```
|
||||
/// use std::time::Duration;
|
||||
/// use std::convert::Infallible;
|
||||
/// use futures_util::StreamExt;
|
||||
/// use tokio::time::interval;
|
||||
/// use tokio_stream::wrappers::IntervalStream;
|
||||
/// use warp::{Filter, Stream, sse::Event};
|
||||
///
|
||||
/// // create server-sent event
|
||||
/// fn sse_counter(counter: u64) -> Result<Event, Infallible> {
|
||||
/// Ok(Event::default().data(counter.to_string()))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let routes = warp::path("ticks")
|
||||
/// .and(warp::get())
|
||||
/// .map(|| {
|
||||
/// let mut counter: u64 = 0;
|
||||
/// let interval = interval(Duration::from_secs(15));
|
||||
/// let stream = IntervalStream::new(interval);
|
||||
/// let event_stream = stream.map(move |_| {
|
||||
/// counter += 1;
|
||||
/// sse_counter(counter)
|
||||
/// });
|
||||
/// // reply using server-sent events
|
||||
/// let stream = warp::sse::keep_alive()
|
||||
/// .interval(Duration::from_secs(5))
|
||||
/// .text("thump".to_string())
|
||||
/// .stream(event_stream);
|
||||
/// warp::sse::reply(stream)
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// See [notes](https://www.w3.org/TR/2009/WD-eventsource-20090421/#notes).
|
||||
pub fn keep_alive() -> KeepAlive {
|
||||
loop {}
|
||||
}
|
||||
impl<S> Stream for SseKeepAlive<S>
|
||||
where
|
||||
S: TryStream<Ok = Event> + Send + 'static,
|
||||
S::Error: StdError + Send + Sync + 'static,
|
||||
{
|
||||
type Item = Result<Event, SseError>;
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
mod sealed {
|
||||
use super::*;
|
||||
/// SSE error type
|
||||
#[derive(Debug)]
|
||||
pub struct SseError;
|
||||
impl fmt::Display for SseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for SseError {}
|
||||
}
|
||||
206
warp/src/filters/trace.rs
Normal file
206
warp/src/filters/trace.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
//! [`tracing`] filters.
|
||||
//!
|
||||
//! [`tracing`] is a framework for instrumenting Rust programs to
|
||||
//! collect scoped, structured, and async-aware diagnostics. This module
|
||||
//! provides a set of filters for instrumenting Warp applications with `tracing`
|
||||
//! spans. [`Spans`] can be used to associate individual events with a request,
|
||||
//! and track contexts through the application.
|
||||
//!
|
||||
//! [`tracing`]: https://crates.io/crates/tracing
|
||||
//! [`Spans`]: https://docs.rs/tracing/latest/tracing/#spans
|
||||
use tracing::Span;
|
||||
use std::net::SocketAddr;
|
||||
use http::{self};
|
||||
use crate::filter::{Filter, WrapSealed};
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::Reply;
|
||||
use crate::route::Route;
|
||||
use self::internal::WithTrace;
|
||||
/// Create a wrapping filter that instruments every request with a `tracing`
|
||||
/// [`Span`] at the [`INFO`] level, containing a summary of the request.
|
||||
/// Additionally, if the [`DEBUG`] level is enabled, the span will contain an
|
||||
/// event recording the request's headers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(warp::trace::request());
|
||||
/// ```
|
||||
///
|
||||
/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
|
||||
/// [`INFO`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.INFO
|
||||
/// [`DEBUG`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.DEBUG
|
||||
pub fn request() -> Trace<impl Fn(Info<'_>) -> Span + Clone> {
|
||||
trace(|info: Info<'_>| {
|
||||
loop {}
|
||||
})
|
||||
}
|
||||
/// Create a wrapping filter that instruments every request with a custom
|
||||
/// `tracing` [`Span`] provided by a function.
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .with(warp::trace(|info| {
|
||||
/// // Create a span using tracing macros
|
||||
/// tracing::info_span!(
|
||||
/// "request",
|
||||
/// method = %info.method(),
|
||||
/// path = %info.path(),
|
||||
/// )
|
||||
/// }));
|
||||
/// ```
|
||||
///
|
||||
/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
|
||||
pub fn trace<F>(func: F) -> Trace<F>
|
||||
where
|
||||
F: Fn(Info<'_>) -> Span + Clone,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Create a wrapping filter that instruments every request with a `tracing`
|
||||
/// [`Span`] at the [`DEBUG`] level representing a named context.
|
||||
///
|
||||
/// This can be used to instrument multiple routes with their own sub-spans in a
|
||||
/// per-request trace.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let hello = warp::path("hello")
|
||||
/// .map(warp::reply)
|
||||
/// .with(warp::trace::named("hello"));
|
||||
///
|
||||
/// let goodbye = warp::path("goodbye")
|
||||
/// .map(warp::reply)
|
||||
/// .with(warp::trace::named("goodbye"));
|
||||
///
|
||||
/// let routes = hello.or(goodbye);
|
||||
/// ```
|
||||
///
|
||||
/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
|
||||
/// [`DEBUG`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.DEBUG
|
||||
pub fn named(name: &'static str) -> Trace<impl Fn(Info<'_>) -> Span + Copy> {
|
||||
trace(move |_| tracing::debug_span!("context", "{}", name,))
|
||||
}
|
||||
/// Decorates a [`Filter`](crate::Filter) to create a [`tracing`] [span] for
|
||||
/// requests and responses.
|
||||
///
|
||||
/// [`tracing`]: https://crates.io/crates/tracing
|
||||
/// [span]: https://docs.rs/tracing/latest/tracing/#spans
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Trace<F> {
|
||||
func: F,
|
||||
}
|
||||
/// Information about the request/response that can be used to prepare log lines.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Info<'a> {
|
||||
route: &'a Route,
|
||||
}
|
||||
impl<FN, F> WrapSealed<F> for Trace<FN>
|
||||
where
|
||||
FN: Fn(Info<'_>) -> Span + Clone + Send,
|
||||
F: Filter + Clone + Send,
|
||||
F::Extract: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Wrapped = WithTrace<FN, F>;
|
||||
fn wrap(&self, filter: F) -> Self::Wrapped {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<'a> Info<'a> {
|
||||
/// View the remote `SocketAddr` of the request.
|
||||
pub fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
loop {}
|
||||
}
|
||||
/// View the `http::Method` of the request.
|
||||
pub fn method(&self) -> &http::Method {
|
||||
loop {}
|
||||
}
|
||||
/// View the URI path of the request.
|
||||
pub fn path(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
/// View the `http::Version` of the request.
|
||||
pub fn version(&self) -> http::Version {
|
||||
loop {}
|
||||
}
|
||||
/// View the referer of the request.
|
||||
pub fn referer(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// View the user agent of the request.
|
||||
pub fn user_agent(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// View the host of the request
|
||||
pub fn host(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
/// View the request headers.
|
||||
pub fn request_headers(&self) -> &http::HeaderMap {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
mod internal {
|
||||
use futures_util::{future::Inspect, future::MapOk};
|
||||
use super::{Info, Trace};
|
||||
use crate::filter::{Filter, FilterBase, Internal};
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::Reply;
|
||||
use crate::reply::Response;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Traced(pub(super) Response);
|
||||
impl Reply for Traced {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct WithTrace<FN, F> {
|
||||
pub(super) filter: F,
|
||||
pub(super) trace: Trace<FN>,
|
||||
}
|
||||
use tracing::instrument::{Instrumented};
|
||||
use tracing::Span;
|
||||
fn finished_logger<E: IsReject>(reply: &Result<(Traced,), E>) {
|
||||
loop {}
|
||||
}
|
||||
fn convert_reply<R: Reply>(reply: R) -> (Traced,) {
|
||||
loop {}
|
||||
}
|
||||
impl<FN, F> FilterBase for WithTrace<FN, F>
|
||||
where
|
||||
FN: Fn(Info<'_>) -> Span + Clone + Send,
|
||||
F: Filter + Clone + Send,
|
||||
F::Extract: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
type Extract = (Traced,);
|
||||
type Error = F::Error;
|
||||
type Future = Instrumented<
|
||||
Inspect<
|
||||
MapOk<F::Future, fn(F::Extract) -> Self::Extract>,
|
||||
fn(&Result<Self::Extract, F::Error>),
|
||||
>,
|
||||
>;
|
||||
fn filter(&self, _: Internal) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
255
warp/src/filters/ws.rs
Normal file
255
warp/src/filters/ws.rs
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
//! Websockets Filters
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use super::header;
|
||||
use crate::filter::{filter_fn_one, Filter, One};
|
||||
use crate::reject::Rejection;
|
||||
use crate::reply::{Reply, Response};
|
||||
use futures_util::{future, ready, FutureExt, Sink, Stream, TryFutureExt};
|
||||
use headers::{Connection, HeaderMapExt, SecWebsocketAccept, SecWebsocketKey, Upgrade};
|
||||
use http;
|
||||
use hyper::upgrade::OnUpgrade;
|
||||
use tokio_tungstenite::{
|
||||
tungstenite::protocol::{self, WebSocketConfig},
|
||||
WebSocketStream,
|
||||
};
|
||||
/// Creates a Websocket Filter.
|
||||
///
|
||||
/// The yielded `Ws` is used to finish the websocket upgrade.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This filter combines multiple filters internally, so you don't need them:
|
||||
///
|
||||
/// - Method must be `GET`
|
||||
/// - Header `connection` must be `upgrade`
|
||||
/// - Header `upgrade` must be `websocket`
|
||||
/// - Header `sec-websocket-version` must be `13`
|
||||
/// - Header `sec-websocket-key` must be set.
|
||||
///
|
||||
/// If the filters are met, yields a `Ws`. Calling `Ws::on_upgrade` will
|
||||
/// return a reply with:
|
||||
///
|
||||
/// - Status of `101 Switching Protocols`
|
||||
/// - Header `connection: upgrade`
|
||||
/// - Header `upgrade: websocket`
|
||||
/// - Header `sec-websocket-accept` with the hash value of the received key.
|
||||
pub fn ws() -> impl Filter<Extract = One<Ws>, Error = Rejection> + Copy {
|
||||
loop {}
|
||||
}
|
||||
/// Extracted by the [`ws`](ws) filter, and used to finish an upgrade.
|
||||
pub struct Ws {
|
||||
config: Option<WebSocketConfig>,
|
||||
key: SecWebsocketKey,
|
||||
on_upgrade: Option<OnUpgrade>,
|
||||
}
|
||||
impl Ws {
|
||||
/// Finish the upgrade, passing a function to handle the `WebSocket`.
|
||||
///
|
||||
/// The passed function must return a `Future`.
|
||||
pub fn on_upgrade<F, U>(self, func: F) -> impl Reply
|
||||
where
|
||||
F: FnOnce(WebSocket) -> U + Send + 'static,
|
||||
U: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Set the size of the internal message send queue.
|
||||
pub fn max_send_queue(mut self, max: usize) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Set the maximum message size (defaults to 64 megabytes)
|
||||
pub fn max_message_size(mut self, max: usize) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Set the maximum frame size (defaults to 16 megabytes)
|
||||
pub fn max_frame_size(mut self, max: usize) -> Self {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Ws {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
struct WsReply<F> {
|
||||
ws: Ws,
|
||||
on_upgrade: F,
|
||||
}
|
||||
impl<F, U> Reply for WsReply<F>
|
||||
where
|
||||
F: FnOnce(WebSocket) -> U + Send + 'static,
|
||||
U: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
fn on_upgrade() -> impl Filter<
|
||||
Extract = (Option<OnUpgrade>,),
|
||||
Error = Rejection,
|
||||
> + Copy {
|
||||
loop {}
|
||||
}
|
||||
/// A websocket `Stream` and `Sink`, provided to `ws` filters.
|
||||
///
|
||||
/// Ping messages sent from the client will be handled internally by replying with a Pong message.
|
||||
/// Close messages need to be handled explicitly: usually by closing the `Sink` end of the
|
||||
/// `WebSocket`.
|
||||
///
|
||||
/// **Note!**
|
||||
/// Due to rust futures nature, pings won't be handled until read part of `WebSocket` is polled
|
||||
pub struct WebSocket {
|
||||
inner: WebSocketStream<hyper::upgrade::Upgraded>,
|
||||
}
|
||||
impl WebSocket {
|
||||
pub(crate) async fn from_raw_socket(
|
||||
upgraded: hyper::upgrade::Upgraded,
|
||||
role: protocol::Role,
|
||||
config: Option<protocol::WebSocketConfig>,
|
||||
) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Gracefully close this websocket.
|
||||
pub async fn close(mut self) -> Result<(), crate::Error> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Stream for WebSocket {
|
||||
type Item = Result<Message, crate::Error>;
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Sink<Message> for WebSocket {
|
||||
type Error = crate::Error;
|
||||
fn poll_ready(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_close(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for WebSocket {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// A WebSocket message.
|
||||
///
|
||||
/// This will likely become a `non-exhaustive` enum in the future, once that
|
||||
/// language feature has stabilized.
|
||||
#[derive(Eq, PartialEq, Clone)]
|
||||
pub struct Message {
|
||||
inner: protocol::Message,
|
||||
}
|
||||
impl Message {
|
||||
/// Construct a new Text `Message`.
|
||||
pub fn text<S: Into<String>>(s: S) -> Message {
|
||||
loop {}
|
||||
}
|
||||
/// Construct a new Binary `Message`.
|
||||
pub fn binary<V: Into<Vec<u8>>>(v: V) -> Message {
|
||||
loop {}
|
||||
}
|
||||
/// Construct a new Ping `Message`.
|
||||
pub fn ping<V: Into<Vec<u8>>>(v: V) -> Message {
|
||||
loop {}
|
||||
}
|
||||
/// Construct a new Pong `Message`.
|
||||
///
|
||||
/// Note that one rarely needs to manually construct a Pong message because the underlying tungstenite socket
|
||||
/// automatically responds to the Ping messages it receives. Manual construction might still be useful in some cases
|
||||
/// like in tests or to send unidirectional heartbeats.
|
||||
pub fn pong<V: Into<Vec<u8>>>(v: V) -> Message {
|
||||
loop {}
|
||||
}
|
||||
/// Construct the default Close `Message`.
|
||||
pub fn close() -> Message {
|
||||
loop {}
|
||||
}
|
||||
/// Construct a Close `Message` with a code and reason.
|
||||
pub fn close_with(
|
||||
code: impl Into<u16>,
|
||||
reason: impl Into<Cow<'static, str>>,
|
||||
) -> Message {
|
||||
loop {}
|
||||
}
|
||||
/// Returns true if this message is a Text message.
|
||||
pub fn is_text(&self) -> bool {
|
||||
loop {}
|
||||
}
|
||||
/// Returns true if this message is a Binary message.
|
||||
pub fn is_binary(&self) -> bool {
|
||||
loop {}
|
||||
}
|
||||
/// Returns true if this message a is a Close message.
|
||||
pub fn is_close(&self) -> bool {
|
||||
loop {}
|
||||
}
|
||||
/// Returns true if this message is a Ping message.
|
||||
pub fn is_ping(&self) -> bool {
|
||||
loop {}
|
||||
}
|
||||
/// Returns true if this message is a Pong message.
|
||||
pub fn is_pong(&self) -> bool {
|
||||
loop {}
|
||||
}
|
||||
/// Try to get the close frame (close code and reason)
|
||||
pub fn close_frame(&self) -> Option<(u16, &str)> {
|
||||
loop {}
|
||||
}
|
||||
/// Try to get a reference to the string text, if this is a Text message.
|
||||
pub fn to_str(&self) -> Result<&str, ()> {
|
||||
loop {}
|
||||
}
|
||||
/// Return the bytes of this message, if the message can contain data.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
loop {}
|
||||
}
|
||||
/// Destructure this message into binary data.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Message {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl From<Message> for Vec<u8> {
|
||||
fn from(m: Message) -> Self {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Connection header did not include 'upgrade'
|
||||
#[derive(Debug)]
|
||||
pub struct MissingConnectionUpgrade;
|
||||
impl fmt::Display for MissingConnectionUpgrade {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl ::std::error::Error for MissingConnectionUpgrade {}
|
||||
144
warp/src/generic.rs
Normal file
144
warp/src/generic.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#[derive(Debug)]
|
||||
pub struct Product<H, T: HList>(pub(crate) H, pub(crate) T);
|
||||
pub type One<T> = (T,);
|
||||
#[inline]
|
||||
pub(crate) fn one<T>(val: T) -> One<T> {
|
||||
loop {}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Either<T, U> {
|
||||
A(T),
|
||||
B(U),
|
||||
}
|
||||
pub trait HList: Sized {
|
||||
type Tuple: Tuple<HList = Self>;
|
||||
fn flatten(self) -> Self::Tuple;
|
||||
}
|
||||
pub trait Tuple: Sized {
|
||||
type HList: HList<Tuple = Self>;
|
||||
fn hlist(self) -> Self::HList;
|
||||
#[inline]
|
||||
fn combine<T>(self, other: T) -> CombinedTuples<Self, T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: Tuple,
|
||||
Self::HList: Combine<T::HList>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
pub type CombinedTuples<T, U> = <<<T as Tuple>::HList as Combine<
|
||||
<U as Tuple>::HList,
|
||||
>>::Output as HList>::Tuple;
|
||||
pub trait Combine<T: HList> {
|
||||
type Output: HList;
|
||||
fn combine(self, other: T) -> Self::Output;
|
||||
}
|
||||
pub trait Func<Args> {
|
||||
type Output;
|
||||
fn call(&self, args: Args) -> Self::Output;
|
||||
}
|
||||
impl<T: HList> Combine<T> for () {
|
||||
type Output = T;
|
||||
#[inline]
|
||||
fn combine(self, other: T) -> Self::Output {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<H, T: HList, U: HList> Combine<U> for Product<H, T>
|
||||
where
|
||||
T: Combine<U>,
|
||||
Product<H, <T as Combine<U>>::Output>: HList,
|
||||
{
|
||||
type Output = Product<H, <T as Combine<U>>::Output>;
|
||||
#[inline]
|
||||
fn combine(self, other: U) -> Self::Output {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl HList for () {
|
||||
type Tuple = ();
|
||||
#[inline]
|
||||
fn flatten(self) -> Self::Tuple {}
|
||||
}
|
||||
impl Tuple for () {
|
||||
type HList = ();
|
||||
#[inline]
|
||||
fn hlist(self) -> Self::HList {}
|
||||
}
|
||||
impl<F, R> Func<()> for F
|
||||
where
|
||||
F: Fn() -> R,
|
||||
{
|
||||
type Output = R;
|
||||
#[inline]
|
||||
fn call(&self, _args: ()) -> Self::Output {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<F, R> Func<crate::Rejection> for F
|
||||
where
|
||||
F: Fn(crate::Rejection) -> R,
|
||||
{
|
||||
type Output = R;
|
||||
#[inline]
|
||||
fn call(&self, arg: crate::Rejection) -> Self::Output {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
macro_rules! product {
|
||||
($H:expr) => {
|
||||
Product($H, ())
|
||||
};
|
||||
($H:expr, $($T:expr),*) => {
|
||||
Product($H, product!($($T),*))
|
||||
};
|
||||
}
|
||||
macro_rules! Product {
|
||||
($H:ty) => {
|
||||
Product <$H, () >
|
||||
};
|
||||
($H:ty, $($T:ty),*) => {
|
||||
Product <$H, Product!($($T),*) >
|
||||
};
|
||||
}
|
||||
macro_rules! product_pat {
|
||||
($H:pat) => {
|
||||
Product($H, ())
|
||||
};
|
||||
($H:pat, $($T:pat),*) => {
|
||||
Product($H, product_pat!($($T),*))
|
||||
};
|
||||
}
|
||||
macro_rules! generics {
|
||||
($type:ident) => {
|
||||
impl <$type > HList for Product!($type) { type Tuple = ($type,); #[inline] fn
|
||||
flatten(self) -> Self::Tuple { (self.0,) } } impl <$type > Tuple for ($type,) {
|
||||
type HList = Product!($type); #[inline] fn hlist(self) -> Self::HList {
|
||||
product!(self.0) } } impl < F, R, $type > Func < Product!($type) > for F where F
|
||||
: Fn($type) -> R, { type Output = R; #[inline] fn call(& self, args :
|
||||
Product!($type)) -> Self::Output { (* self) (args.0) } } impl < F, R, $type >
|
||||
Func < ($type,) > for F where F : Fn($type) -> R, { type Output = R; #[inline] fn
|
||||
call(& self, args : ($type,)) -> Self::Output { (* self) (args.0) } }
|
||||
};
|
||||
($type1:ident, $($type:ident),*) => {
|
||||
generics!($($type),*); impl <$type1, $($type),*> HList for Product!($type1,
|
||||
$($type),*) { type Tuple = ($type1, $($type),*); #[inline] fn flatten(self) ->
|
||||
Self::Tuple { #[allow(non_snake_case)] let product_pat!($type1, $($type),*) =
|
||||
self; ($type1, $($type),*) } } impl <$type1, $($type),*> Tuple for ($type1,
|
||||
$($type),*) { type HList = Product!($type1, $($type),*); #[inline] fn hlist(self)
|
||||
-> Self::HList { #[allow(non_snake_case)] let ($type1, $($type),*) = self;
|
||||
product!($type1, $($type),*) } } impl < F, R, $type1, $($type),*> Func <
|
||||
Product!($type1, $($type),*) > for F where F : Fn($type1, $($type),*) -> R, {
|
||||
type Output = R; #[inline] fn call(& self, args : Product!($type1, $($type),*))
|
||||
-> Self::Output { #[allow(non_snake_case)] let product_pat!($type1, $($type),*) =
|
||||
args; (* self) ($type1, $($type),*) } } impl < F, R, $type1, $($type),*> Func <
|
||||
($type1, $($type),*) > for F where F : Fn($type1, $($type),*) -> R, { type Output
|
||||
= R; #[inline] fn call(& self, args : ($type1, $($type),*)) -> Self::Output {
|
||||
#[allow(non_snake_case)] let ($type1, $($type),*) = args; (* self) ($type1,
|
||||
$($type),*) } }
|
||||
};
|
||||
}
|
||||
generics! {
|
||||
T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
|
||||
}
|
||||
179
warp/src/lib.rs
Normal file
179
warp/src/lib.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#![doc(html_root_url = "https://docs.rs/warp/0.3.3")]
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
|
||||
//! # warp
|
||||
//!
|
||||
//! warp is a super-easy, composable, web server framework for warp speeds.
|
||||
//!
|
||||
//! Thanks to its [`Filter`][Filter] system, warp provides these out of the box:
|
||||
//!
|
||||
//! - Path routing and parameter extraction
|
||||
//! - Header requirements and extraction
|
||||
//! - Query string deserialization
|
||||
//! - JSON and Form bodies
|
||||
//! - Multipart form data
|
||||
//! - Static Files and Directories
|
||||
//! - Websockets
|
||||
//! - Access logging
|
||||
//! - Etc
|
||||
//!
|
||||
//! Since it builds on top of [hyper](https://hyper.rs), you automatically get:
|
||||
//!
|
||||
//! - HTTP/1
|
||||
//! - HTTP/2
|
||||
//! - Asynchronous
|
||||
//! - One of the fastest HTTP implementations
|
||||
//! - Tested and **correct**
|
||||
//!
|
||||
//! ## Filters
|
||||
//!
|
||||
//! The main concept in warp is the [`Filter`][Filter], which allows composition
|
||||
//! to describe various endpoints in your web service. Besides this powerful
|
||||
//! trait, warp comes with several built in [filters](filters/index.html), which
|
||||
//! can be combined for your specific needs.
|
||||
//!
|
||||
//! As a small example, consider an endpoint that has path and header requirements:
|
||||
//!
|
||||
//! ```
|
||||
//! use warp::Filter;
|
||||
//!
|
||||
//! let hi = warp::path("hello")
|
||||
//! .and(warp::path::param())
|
||||
//! .and(warp::header("user-agent"))
|
||||
//! .map(|param: String, agent: String| {
|
||||
//! format!("Hello {}, whose agent is {}", param, agent)
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! This example composes several [`Filter`s][Filter] together using `and`:
|
||||
//!
|
||||
//! - A path prefix of "hello"
|
||||
//! - A path parameter of a `String`
|
||||
//! - The `user-agent` header parsed as a `String`
|
||||
//!
|
||||
//! These specific filters will [`reject`][reject] requests that don't match
|
||||
//! their requirements.
|
||||
//!
|
||||
//! This ends up matching requests like:
|
||||
//!
|
||||
//! ```notrust
|
||||
//! GET /hello/sean HTTP/1.1
|
||||
//! Host: hyper.rs
|
||||
//! User-Agent: reqwest/v0.8.6
|
||||
//!
|
||||
//! ```
|
||||
//! And it returns a response similar to this:
|
||||
//!
|
||||
//! ```notrust
|
||||
//! HTTP/1.1 200 OK
|
||||
//! Content-Length: 41
|
||||
//! Date: ...
|
||||
//!
|
||||
//! Hello sean, whose agent is reqwest/v0.8.6
|
||||
//! ```
|
||||
//!
|
||||
//! Take a look at the full list of [`filters`](filters/index.html) to see what
|
||||
//! you can build.
|
||||
//!
|
||||
//! ## Testing
|
||||
//!
|
||||
//! Testing your web services easily is extremely important, and warp provides
|
||||
//! a [`test`](self::test) module to help send mocked requests through your service.
|
||||
//!
|
||||
//! [Filter]: trait.Filter.html
|
||||
//! [reject]: reject/index.html
|
||||
|
||||
#[macro_use]
|
||||
mod error;
|
||||
mod filter;
|
||||
pub mod filters;
|
||||
mod generic;
|
||||
pub mod redirect;
|
||||
pub mod reject;
|
||||
pub mod reply;
|
||||
mod route;
|
||||
mod server;
|
||||
mod service;
|
||||
pub mod test;
|
||||
#[cfg(feature = "tls")]
|
||||
mod tls;
|
||||
mod transport;
|
||||
|
||||
pub use self::error::Error;
|
||||
pub use self::filter::Filter;
|
||||
// This otherwise shows a big dump of re-exports in the doc homepage,
|
||||
// with zero context, so just hide it from the docs. Doc examples
|
||||
// on each can show that a convenient import exists.
|
||||
#[cfg(feature = "compression")]
|
||||
#[doc(hidden)]
|
||||
pub use self::filters::compression;
|
||||
#[cfg(feature = "multipart")]
|
||||
#[doc(hidden)]
|
||||
pub use self::filters::multipart;
|
||||
#[cfg(feature = "websocket")]
|
||||
#[doc(hidden)]
|
||||
pub use self::filters::ws;
|
||||
#[doc(hidden)]
|
||||
pub use self::filters::{
|
||||
addr,
|
||||
// any() function
|
||||
any::any,
|
||||
body,
|
||||
cookie,
|
||||
// cookie() function
|
||||
cookie::cookie,
|
||||
cors,
|
||||
// cors() function
|
||||
cors::cors,
|
||||
ext,
|
||||
fs,
|
||||
header,
|
||||
// header() function
|
||||
header::header,
|
||||
host,
|
||||
log,
|
||||
// log() function
|
||||
log::log,
|
||||
method::{delete, get, head, method, options, patch, post, put},
|
||||
path,
|
||||
// path() function and macro
|
||||
path::path,
|
||||
query,
|
||||
// query() function
|
||||
query::query,
|
||||
sse,
|
||||
trace,
|
||||
// trace() function
|
||||
trace::trace,
|
||||
};
|
||||
// ws() function
|
||||
pub use self::filter::wrap_fn;
|
||||
#[cfg(feature = "websocket")]
|
||||
#[doc(hidden)]
|
||||
pub use self::filters::ws::ws;
|
||||
#[doc(hidden)]
|
||||
pub use self::redirect::redirect;
|
||||
#[doc(hidden)]
|
||||
#[allow(deprecated)]
|
||||
pub use self::reject::{reject, Rejection};
|
||||
#[doc(hidden)]
|
||||
pub use self::reply::{reply, Reply};
|
||||
#[cfg(feature = "tls")]
|
||||
pub use self::server::TlsServer;
|
||||
pub use self::server::{serve, Server};
|
||||
pub use self::service::service;
|
||||
#[doc(hidden)]
|
||||
pub use http;
|
||||
#[doc(hidden)]
|
||||
pub use hyper;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use bytes::Buf;
|
||||
#[doc(hidden)]
|
||||
pub use futures_util::{Future, Sink, Stream};
|
||||
#[doc(hidden)]
|
||||
|
||||
pub(crate) type Request = http::Request<hyper::Body>;
|
||||
120
warp/src/redirect.rs
Normal file
120
warp/src/redirect.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
//! Redirect requests to a new location.
|
||||
//!
|
||||
//! The types in this module are helpers that implement [`Reply`](Reply), and easy
|
||||
//! to use in order to setup redirects.
|
||||
use http::{header, StatusCode};
|
||||
pub use self::sealed::AsLocation;
|
||||
use crate::reply::{self, Reply};
|
||||
/// A simple `301` permanent redirect to a different location.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{http::Uri, Filter};
|
||||
///
|
||||
/// let route = warp::path("v1")
|
||||
/// .map(|| {
|
||||
/// warp::redirect(Uri::from_static("/v2"))
|
||||
/// });
|
||||
/// ```
|
||||
pub fn redirect(uri: impl AsLocation) -> impl Reply {
|
||||
reply::with_header(
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
header::LOCATION,
|
||||
uri.header_value(),
|
||||
)
|
||||
}
|
||||
/// A simple `302` found redirect to a different location
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{http::Uri, Filter};
|
||||
///
|
||||
/// let route = warp::path("v1")
|
||||
/// .map(|| {
|
||||
/// warp::redirect::found(Uri::from_static("/v2"))
|
||||
/// });
|
||||
/// ```
|
||||
pub fn found(uri: impl AsLocation) -> impl Reply {
|
||||
reply::with_header(StatusCode::FOUND, header::LOCATION, uri.header_value())
|
||||
}
|
||||
/// A simple `303` redirect to a different location.
|
||||
///
|
||||
/// The HTTP method of the request to the new location will always be `GET`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{http::Uri, Filter};
|
||||
///
|
||||
/// let route = warp::path("v1")
|
||||
/// .map(|| {
|
||||
/// warp::redirect::see_other(Uri::from_static("/v2"))
|
||||
/// });
|
||||
/// ```
|
||||
pub fn see_other(uri: impl AsLocation) -> impl Reply {
|
||||
reply::with_header(StatusCode::SEE_OTHER, header::LOCATION, uri.header_value())
|
||||
}
|
||||
/// A simple `307` temporary redirect to a different location.
|
||||
///
|
||||
/// This is similar to [`see_other`](fn@see_other) but the HTTP method and the body of the request
|
||||
/// to the new location will be the same as the method and body of the current request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{http::Uri, Filter};
|
||||
///
|
||||
/// let route = warp::path("v1")
|
||||
/// .map(|| {
|
||||
/// warp::redirect::temporary(Uri::from_static("/v2"))
|
||||
/// });
|
||||
/// ```
|
||||
pub fn temporary(uri: impl AsLocation) -> impl Reply {
|
||||
reply::with_header(
|
||||
StatusCode::TEMPORARY_REDIRECT,
|
||||
header::LOCATION,
|
||||
uri.header_value(),
|
||||
)
|
||||
}
|
||||
/// A simple `308` permanent redirect to a different location.
|
||||
///
|
||||
/// This is similar to [`redirect`](fn@redirect) but the HTTP method of the request to the new
|
||||
/// location will be the same as the method of the current request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{http::Uri, Filter};
|
||||
///
|
||||
/// let route = warp::path("v1")
|
||||
/// .map(|| {
|
||||
/// warp::redirect::permanent(Uri::from_static("/v2"))
|
||||
/// });
|
||||
/// ```
|
||||
pub fn permanent(uri: impl AsLocation) -> impl Reply {
|
||||
reply::with_header(
|
||||
StatusCode::PERMANENT_REDIRECT,
|
||||
header::LOCATION,
|
||||
uri.header_value(),
|
||||
)
|
||||
}
|
||||
mod sealed {
|
||||
|
||||
use http::{header::HeaderValue, Uri};
|
||||
/// Trait for redirect locations. Currently only a `Uri` can be used in
|
||||
/// redirect.
|
||||
/// This sealed trait exists to allow adding possibly new impls so other
|
||||
/// arguments could be accepted, like maybe just `warp::redirect("/v2")`.
|
||||
pub trait AsLocation: Sealed {}
|
||||
pub trait Sealed {
|
||||
fn header_value(self) -> HeaderValue;
|
||||
}
|
||||
impl AsLocation for Uri {}
|
||||
impl Sealed for Uri {
|
||||
fn header_value(self) -> HeaderValue {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
452
warp/src/reject.rs
Normal file
452
warp/src/reject.rs
Normal file
|
|
@ -0,0 +1,452 @@
|
|||
//! Rejections
|
||||
//!
|
||||
//! Part of the power of the [`Filter`](../trait.Filter.html) system is being able to
|
||||
//! reject a request from a filter chain. This allows for filters to be
|
||||
//! combined with `or`, so that if one side of the chain finds that a request
|
||||
//! doesn't fulfill its requirements, the other side can try to process
|
||||
//! the request.
|
||||
//!
|
||||
//! Many of the built-in [`filters`](../filters) will automatically reject
|
||||
//! the request with an appropriate rejection. However, you can also build
|
||||
//! new custom [`Filter`](../trait.Filter.html)s and still want other routes to be
|
||||
//! matchable in the case a predicate doesn't hold.
|
||||
//!
|
||||
//! As a request is processed by a Filter chain, the rejections are accumulated into
|
||||
//! a list contained by the [`Rejection`](struct.Rejection.html) type. Rejections from
|
||||
//! filters can be handled using [`Filter::recover`](../trait.Filter.html#method.recover).
|
||||
//! This is a convenient way to map rejections into a [`Reply`](../reply/trait.Reply.html).
|
||||
//!
|
||||
//! For a more complete example see the
|
||||
//! [Rejection Example](https://github.com/seanmonstar/warp/blob/master/examples/rejections.rs)
|
||||
//! from the repository.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use warp::{reply, Reply, Filter, reject, Rejection, http::StatusCode};
|
||||
//!
|
||||
//! #[derive(Debug)]
|
||||
//! struct InvalidParameter;
|
||||
//!
|
||||
//! impl reject::Reject for InvalidParameter {}
|
||||
//!
|
||||
//! // Custom rejection handler that maps rejections into responses.
|
||||
//! async fn handle_rejection(err: Rejection) -> Result<impl Reply, std::convert::Infallible> {
|
||||
//! if err.is_not_found() {
|
||||
//! Ok(reply::with_status("NOT_FOUND", StatusCode::NOT_FOUND))
|
||||
//! } else if let Some(e) = err.find::<InvalidParameter>() {
|
||||
//! Ok(reply::with_status("BAD_REQUEST", StatusCode::BAD_REQUEST))
|
||||
//! } else {
|
||||
//! eprintln!("unhandled rejection: {:?}", err);
|
||||
//! Ok(reply::with_status("INTERNAL_SERVER_ERROR", StatusCode::INTERNAL_SERVER_ERROR))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//!
|
||||
//! // Filter on `/:id`, but reject with InvalidParameter if the `id` is `0`.
|
||||
//! // Recover from this rejection using a custom rejection handler.
|
||||
//! let route = warp::path::param()
|
||||
//! .and_then(|id: u32| async move {
|
||||
//! if id == 0 {
|
||||
//! Err(warp::reject::custom(InvalidParameter))
|
||||
//! } else {
|
||||
//! Ok("id is valid")
|
||||
//! }
|
||||
//! })
|
||||
//! .recover(handle_rejection);
|
||||
//! ```
|
||||
use std::any::Any;
|
||||
use std::convert::Infallible;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use http::{self, StatusCode};
|
||||
pub(crate) use self::sealed::{CombineRejection, IsReject};
|
||||
/// Rejects a request with `404 Not Found`.
|
||||
#[inline]
|
||||
pub fn reject() -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
/// Rejects a request with `404 Not Found`.
|
||||
#[inline]
|
||||
pub fn not_found() -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn invalid_query() -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn missing_header(name: &'static str) -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn invalid_header(name: &'static str) -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn missing_cookie(name: &'static str) -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn method_not_allowed() -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn length_required() -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn payload_too_large() -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn unsupported_media_type() -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
/// Rejects a request with a custom cause.
|
||||
///
|
||||
/// A [`recover`][] filter should convert this `Rejection` into a `Reply`,
|
||||
/// or else this will be returned as a `500 Internal Server Error`.
|
||||
///
|
||||
/// [`recover`]: ../trait.Filter.html#method.recover
|
||||
pub fn custom<T: Reject>(err: T) -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
/// Protect against re-rejecting a rejection.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// fn with(r: warp::Rejection) {
|
||||
/// let _wat = warp::reject::custom(r);
|
||||
/// }
|
||||
/// ```
|
||||
fn __reject_custom_compilefail() {}
|
||||
/// A marker trait to ensure proper types are used for custom rejections.
|
||||
///
|
||||
/// Can be converted into Rejection.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::{Filter, reject::Reject};
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct RateLimited;
|
||||
///
|
||||
/// impl Reject for RateLimited {}
|
||||
///
|
||||
/// let route = warp::any().and_then(|| async {
|
||||
/// Err::<(), _>(warp::reject::custom(RateLimited))
|
||||
/// });
|
||||
/// ```
|
||||
pub trait Reject: fmt::Debug + Sized + Send + Sync + 'static {}
|
||||
trait Cause: fmt::Debug + Send + Sync + 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
impl<T> Cause for T
|
||||
where
|
||||
T: fmt::Debug + Send + Sync + 'static,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl dyn Cause {}
|
||||
pub(crate) fn known<T: Into<Known>>(err: T) -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
/// Rejection of a request by a [`Filter`](crate::Filter).
|
||||
///
|
||||
/// See the [`reject`](module@crate::reject) documentation for more.
|
||||
pub struct Rejection {
|
||||
reason: Reason,
|
||||
}
|
||||
enum Reason {
|
||||
NotFound,
|
||||
Other(Box<Rejections>),
|
||||
}
|
||||
enum Rejections {
|
||||
Known(Known),
|
||||
Custom(Box<dyn Cause>),
|
||||
Combined(Box<Rejections>, Box<Rejections>),
|
||||
}
|
||||
macro_rules! enum_known {
|
||||
($($(#[$attr:meta])* $var:ident ($ty:path),)+) => {
|
||||
pub (crate) enum Known { $($(#[$attr])* $var ($ty),)+ } impl Known { fn
|
||||
inner_as_any(& self) -> & dyn Any { match * self { $($(#[$attr])* Known::$var
|
||||
(ref t) => t,)+ } } } impl fmt::Debug for Known { fn fmt(& self, f : & mut
|
||||
fmt::Formatter <'_ >) -> fmt::Result { match * self { $($(#[$attr])* Known::$var
|
||||
(ref t) => t.fmt(f),)+ } } } impl fmt::Display for Known { fn fmt(& self, f : &
|
||||
mut fmt::Formatter <'_ >) -> fmt::Result { match * self { $($(#[$attr])*
|
||||
Known::$var (ref t) => t.fmt(f),)+ } } } $(#[doc(hidden)] $(#[$attr])* impl From
|
||||
<$ty > for Known { fn from(ty : $ty) -> Known { Known::$var (ty) } })+
|
||||
};
|
||||
}
|
||||
enum_known! {
|
||||
MethodNotAllowed(MethodNotAllowed), InvalidHeader(InvalidHeader),
|
||||
MissingHeader(MissingHeader), MissingCookie(MissingCookie),
|
||||
InvalidQuery(InvalidQuery), LengthRequired(LengthRequired),
|
||||
PayloadTooLarge(PayloadTooLarge), UnsupportedMediaType(UnsupportedMediaType),
|
||||
FileOpenError(crate ::fs::FileOpenError), FilePermissionError(crate
|
||||
::fs::FilePermissionError), BodyReadError(crate ::body::BodyReadError),
|
||||
BodyDeserializeError(crate ::body::BodyDeserializeError), CorsForbidden(crate
|
||||
::cors::CorsForbidden), #[cfg(feature = "websocket")] MissingConnectionUpgrade(crate
|
||||
::ws::MissingConnectionUpgrade), MissingExtension(crate ::ext::MissingExtension),
|
||||
BodyConsumedMultipleTimes(crate ::body::BodyConsumedMultipleTimes),
|
||||
}
|
||||
impl Rejection {
|
||||
/// Searches this `Rejection` for a specific cause.
|
||||
///
|
||||
/// A `Rejection` will accumulate causes over a `Filter` chain. This method
|
||||
/// can search through them and return the first cause of this type.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[derive(Debug)]
|
||||
/// struct Nope;
|
||||
///
|
||||
/// impl warp::reject::Reject for Nope {}
|
||||
///
|
||||
/// let reject = warp::reject::custom(Nope);
|
||||
///
|
||||
/// if let Some(nope) = reject.find::<Nope>() {
|
||||
/// println!("found it: {:?}", nope);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn find<T: 'static>(&self) -> Option<&T> {
|
||||
loop {}
|
||||
}
|
||||
/// Returns true if this Rejection was made via `warp::reject::not_found`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let rejection = warp::reject();
|
||||
///
|
||||
/// assert!(rejection.is_not_found());
|
||||
/// ```
|
||||
pub fn is_not_found(&self) -> bool {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: Reject> From<T> for Rejection {
|
||||
#[inline]
|
||||
fn from(err: T) -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl From<Infallible> for Rejection {
|
||||
#[inline]
|
||||
fn from(infallible: Infallible) -> Rejection {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl IsReject for Infallible {
|
||||
fn status(&self) -> StatusCode {
|
||||
loop {}
|
||||
}
|
||||
fn into_response(&self) -> crate::reply::Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl IsReject for Rejection {
|
||||
fn status(&self) -> StatusCode {
|
||||
loop {}
|
||||
}
|
||||
fn into_response(&self) -> crate::reply::Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Rejection {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Reason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Rejections {}
|
||||
unit_error! {
|
||||
#[doc = " Invalid query"] pub InvalidQuery : "Invalid query string"
|
||||
}
|
||||
unit_error! {
|
||||
#[doc = " HTTP method not allowed"] pub MethodNotAllowed : "HTTP method not allowed"
|
||||
}
|
||||
unit_error! {
|
||||
#[doc = " A content-length header is required"] pub LengthRequired :
|
||||
"A content-length header is required"
|
||||
}
|
||||
unit_error! {
|
||||
#[doc = " The request payload is too large"] pub PayloadTooLarge :
|
||||
"The request payload is too large"
|
||||
}
|
||||
unit_error! {
|
||||
#[doc = " The request's content-type is not supported"] pub UnsupportedMediaType :
|
||||
"The request's content-type is not supported"
|
||||
}
|
||||
/// Missing request header
|
||||
#[derive(Debug)]
|
||||
pub struct MissingHeader {
|
||||
name: &'static str,
|
||||
}
|
||||
impl MissingHeader {
|
||||
/// Retrieve the name of the header that was missing
|
||||
pub fn name(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for MissingHeader {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for MissingHeader {}
|
||||
/// Invalid request header
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidHeader {
|
||||
name: &'static str,
|
||||
}
|
||||
impl InvalidHeader {
|
||||
/// Retrieve the name of the header that was invalid
|
||||
pub fn name(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for InvalidHeader {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for InvalidHeader {}
|
||||
/// Missing cookie
|
||||
#[derive(Debug)]
|
||||
pub struct MissingCookie {
|
||||
name: &'static str,
|
||||
}
|
||||
impl MissingCookie {
|
||||
/// Retrieve the name of the cookie that was missing
|
||||
pub fn name(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for MissingCookie {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for MissingCookie {}
|
||||
mod sealed {
|
||||
use super::Rejection;
|
||||
use http::StatusCode;
|
||||
use std::convert::Infallible;
|
||||
use std::fmt;
|
||||
pub trait IsReject: fmt::Debug + Send + Sync {
|
||||
fn status(&self) -> StatusCode;
|
||||
fn into_response(&self) -> crate::reply::Response;
|
||||
}
|
||||
fn _assert_object_safe() {
|
||||
loop {}
|
||||
}
|
||||
pub trait CombineRejection<E>: Send + Sized {
|
||||
/// The type that should be returned when only 1 of the two
|
||||
/// "rejections" occurs.
|
||||
///
|
||||
/// # For example:
|
||||
///
|
||||
/// `warp::any().and(warp::path("foo"))` has the following steps:
|
||||
///
|
||||
/// 1. Since this is `and`, only **one** of the rejections will occur,
|
||||
/// and as soon as it does, it will be returned.
|
||||
/// 2. `warp::any()` rejects with `Never`. So, it will never return `Never`.
|
||||
/// 3. `warp::path()` rejects with `Rejection`. It may return `Rejection`.
|
||||
///
|
||||
/// Thus, if the above filter rejects, it will definitely be `Rejection`.
|
||||
type One: IsReject + From<Self> + From<E> + Into<Rejection>;
|
||||
/// The type that should be returned when both rejections occur,
|
||||
/// and need to be combined.
|
||||
type Combined: IsReject;
|
||||
fn combine(self, other: E) -> Self::Combined;
|
||||
}
|
||||
impl CombineRejection<Rejection> for Rejection {
|
||||
type One = Rejection;
|
||||
type Combined = Rejection;
|
||||
fn combine(self, other: Rejection) -> Self::Combined {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl CombineRejection<Infallible> for Rejection {
|
||||
type One = Rejection;
|
||||
type Combined = Infallible;
|
||||
fn combine(self, other: Infallible) -> Self::Combined {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl CombineRejection<Rejection> for Infallible {
|
||||
type One = Rejection;
|
||||
type Combined = Infallible;
|
||||
fn combine(self, _: Rejection) -> Self::Combined {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl CombineRejection<Infallible> for Infallible {
|
||||
type One = Infallible;
|
||||
type Combined = Infallible;
|
||||
fn combine(self, _: Infallible) -> Self::Combined {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::StatusCode;
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Left;
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Right;
|
||||
impl Reject for Left {}
|
||||
impl Reject for Right {}
|
||||
#[test]
|
||||
fn rejection_status() {
|
||||
loop {}
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn combine_rejection_causes_with_some_left_and_none_right() {
|
||||
loop {}
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn combine_rejection_causes_with_none_left_and_some_right() {
|
||||
loop {}
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn unhandled_customs() {
|
||||
loop {}
|
||||
}
|
||||
async fn response_body_string(resp: crate::reply::Response) -> String {
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn find_cause() {
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn size_of_rejection() {
|
||||
loop {}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct X(u32);
|
||||
impl Reject for X {}
|
||||
fn combine_n<F, R>(n: u32, new_reject: F) -> Rejection
|
||||
where
|
||||
F: Fn(u32) -> R,
|
||||
R: Reject,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
388
warp/src/reply.rs
Normal file
388
warp/src/reply.rs
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
//! Reply to requests.
|
||||
//!
|
||||
//! A [`Reply`](./trait.Reply.html) is a type that can be converted into an HTTP
|
||||
//! response to be sent to the client. These are typically the successful
|
||||
//! counterpart to a [rejection](../reject).
|
||||
//!
|
||||
//! The functions in this module are helpers for quickly creating a reply.
|
||||
//! Besides them, you can return a type that implements [`Reply`](./trait.Reply.html). This
|
||||
//! could be any of the following:
|
||||
//!
|
||||
//! - [`http::Response<impl Into<hyper::Body>>`](https://docs.rs/http)
|
||||
//! - `String`
|
||||
//! - `&'static str`
|
||||
//! - `http::StatusCode`
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use warp::{Filter, http::Response};
|
||||
//!
|
||||
//! // Returns an empty `200 OK` response.
|
||||
//! let empty_200 = warp::any().map(warp::reply);
|
||||
//!
|
||||
//! // Returns a `200 OK` response with custom header and body.
|
||||
//! let custom = warp::any().map(|| {
|
||||
//! Response::builder()
|
||||
//! .header("my-custom-header", "some-value")
|
||||
//! .body("and a custom body")
|
||||
//! });
|
||||
//!
|
||||
//! // GET requests return the empty 200, POST return the custom.
|
||||
//! let routes = warp::get().and(empty_200)
|
||||
//! .or(warp::post().and(custom));
|
||||
//! ```
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use crate::generic::{Either, One};
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::StatusCode;
|
||||
use hyper::Body;
|
||||
use serde::Serialize;
|
||||
pub(crate) use self::sealed::Reply_;
|
||||
use self::sealed::BoxedReply;
|
||||
#[doc(hidden)]
|
||||
pub use crate::filters::reply as with;
|
||||
/// Response type into which types implementing the `Reply` trait are convertable.
|
||||
pub type Response = ::http::Response<Body>;
|
||||
/// Returns an empty `Reply` with status code `200 OK`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // GET /just-ok returns an empty `200 OK`.
|
||||
/// let route = warp::path("just-ok")
|
||||
/// .map(|| {
|
||||
/// println!("got a /just-ok request!");
|
||||
/// warp::reply()
|
||||
/// });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn reply() -> impl Reply {
|
||||
StatusCode::OK
|
||||
}
|
||||
/// Convert the value into a `Reply` with the value encoded as JSON.
|
||||
///
|
||||
/// The passed value must implement [`Serialize`][ser]. Many
|
||||
/// collections do, and custom domain types can have `Serialize` derived.
|
||||
///
|
||||
/// [ser]: https://serde.rs
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// // GET /ids returns a `200 OK` with a JSON array of ids:
|
||||
/// // `[1, 3, 7, 13]`
|
||||
/// let route = warp::path("ids")
|
||||
/// .map(|| {
|
||||
/// let our_ids = vec![1, 3, 7, 13];
|
||||
/// warp::reply::json(&our_ids)
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If a type fails to be serialized into JSON, the error is logged at the
|
||||
/// `error` level, and the returned `impl Reply` will be an empty
|
||||
/// `500 Internal Server Error` response.
|
||||
pub fn json<T>(val: &T) -> Json
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// A JSON formatted reply.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Json {
|
||||
inner: Result<Vec<u8>, ()>,
|
||||
}
|
||||
impl Reply for Json {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReplyJsonError;
|
||||
impl fmt::Display for ReplyJsonError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for ReplyJsonError {}
|
||||
/// Reply with a body and `content-type` set to `text/html; charset=utf-8`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let body = r#"
|
||||
/// <html>
|
||||
/// <head>
|
||||
/// <title>HTML with warp!</title>
|
||||
/// </head>
|
||||
/// <body>
|
||||
/// <h1>warp + HTML = ♥</h1>
|
||||
/// </body>
|
||||
/// </html>
|
||||
/// "#;
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(move || {
|
||||
/// warp::reply::html(body)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn html<T>(body: T) -> Html<T>
|
||||
where
|
||||
Body: From<T>,
|
||||
T: Send,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// An HTML reply.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Html<T> {
|
||||
body: T,
|
||||
}
|
||||
impl<T> Reply for Html<T>
|
||||
where
|
||||
Body: From<T>,
|
||||
T: Send,
|
||||
{
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Types that can be converted into a `Response`.
|
||||
///
|
||||
/// This trait is implemented for the following:
|
||||
///
|
||||
/// - `http::StatusCode`
|
||||
/// - `http::Response<impl Into<hyper::Body>>`
|
||||
/// - `String`
|
||||
/// - `&'static str`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use warp::{Filter, http::Response};
|
||||
///
|
||||
/// struct Message {
|
||||
/// msg: String
|
||||
/// }
|
||||
///
|
||||
/// impl warp::Reply for Message {
|
||||
/// fn into_response(self) -> warp::reply::Response {
|
||||
/// Response::new(format!("message: {}", self.msg).into())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn handler() -> Message {
|
||||
/// Message { msg: "Hello".to_string() }
|
||||
/// }
|
||||
///
|
||||
/// let route = warp::any().map(handler);
|
||||
/// ```
|
||||
pub trait Reply: BoxedReply + Send {
|
||||
/// Converts the given value into a [`Response`].
|
||||
///
|
||||
/// [`Response`]: type.Response.html
|
||||
fn into_response(self) -> Response;
|
||||
}
|
||||
impl<T: Reply + ?Sized> Reply for Box<T> {
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
fn _assert_object_safe() {
|
||||
loop {}
|
||||
}
|
||||
/// Wrap an `impl Reply` to change its `StatusCode`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .map(|reply| {
|
||||
/// warp::reply::with_status(reply, warp::http::StatusCode::CREATED)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn with_status<T: Reply>(reply: T, status: StatusCode) -> WithStatus<T> {
|
||||
loop {}
|
||||
}
|
||||
/// Wrap an `impl Reply` to change its `StatusCode`.
|
||||
///
|
||||
/// Returned by `warp::reply::with_status`.
|
||||
#[derive(Debug)]
|
||||
pub struct WithStatus<T> {
|
||||
reply: T,
|
||||
status: StatusCode,
|
||||
}
|
||||
impl<T: Reply> Reply for WithStatus<T> {
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
/// Wrap an `impl Reply` to add a header when rendering.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use warp::Filter;
|
||||
///
|
||||
/// let route = warp::any()
|
||||
/// .map(warp::reply)
|
||||
/// .map(|reply| {
|
||||
/// warp::reply::with_header(reply, "server", "warp")
|
||||
/// });
|
||||
/// ```
|
||||
pub fn with_header<T: Reply, K, V>(reply: T, name: K, value: V) -> WithHeader<T>
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
||||
HeaderValue: TryFrom<V>,
|
||||
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Wraps an `impl Reply` and adds a header when rendering.
|
||||
///
|
||||
/// Returned by `warp::reply::with_header`.
|
||||
#[derive(Debug)]
|
||||
pub struct WithHeader<T> {
|
||||
header: Option<(HeaderName, HeaderValue)>,
|
||||
reply: T,
|
||||
}
|
||||
impl<T: Reply> Reply for WithHeader<T> {
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: Send> Reply for ::http::Response<T>
|
||||
where
|
||||
Body: From<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for ::http::StatusCode {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T> Reply for Result<T, ::http::Error>
|
||||
where
|
||||
T: Reply + Send,
|
||||
{
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for String {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for Vec<u8> {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for &'static str {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for Cow<'static, str> {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for &'static [u8] {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T, U> Reply for Either<T, U>
|
||||
where
|
||||
T: Reply,
|
||||
U: Reply,
|
||||
{
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T> Reply for One<T>
|
||||
where
|
||||
T: Reply,
|
||||
{
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Reply for std::convert::Infallible {
|
||||
#[inline(always)]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
mod sealed {
|
||||
use super::{Reply, Response};
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Reply_(pub(crate) Response);
|
||||
impl Reply for Reply_ {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Internal;
|
||||
pub trait BoxedReply {
|
||||
fn boxed_into_response(self: Box<Self>, internal: Internal) -> Response;
|
||||
}
|
||||
impl<T: Reply> BoxedReply for T {
|
||||
fn boxed_into_response(self: Box<Self>, _: Internal) -> Response {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use super::*;
|
||||
#[test]
|
||||
fn json_serde_error() {
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn response_builder_error() {
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn boxed_reply() {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
82
warp/src/route.rs
Normal file
82
warp/src/route.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use scoped_tls::scoped_thread_local;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use hyper::Body;
|
||||
use crate::Request;
|
||||
scoped_thread_local!(static ROUTE : RefCell < Route >);
|
||||
pub(crate) fn set<F, U>(r: &RefCell<Route>, func: F) -> U
|
||||
where
|
||||
F: FnOnce() -> U,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn is_set() -> bool {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn with<F, R>(func: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Route) -> R,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Route {
|
||||
body: BodyState,
|
||||
remote_addr: Option<SocketAddr>,
|
||||
req: Request,
|
||||
segments_index: usize,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
enum BodyState {
|
||||
Ready,
|
||||
Taken,
|
||||
}
|
||||
impl Route {
|
||||
pub(crate) fn new(req: Request, remote_addr: Option<SocketAddr>) -> RefCell<Route> {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn method(&self) -> &http::Method {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn headers(&self) -> &http::HeaderMap {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn version(&self) -> http::Version {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn extensions(&self) -> &http::Extensions {
|
||||
loop {}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
pub(crate) fn extensions_mut(&mut self) -> &mut http::Extensions {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn uri(&self) -> &http::Uri {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn path(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn full_path(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn set_unmatched_path(&mut self, index: usize) {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn query(&self) -> Option<&str> {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn matched_path_index(&self) -> usize {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn reset_matched_path_index(&mut self, index: usize) {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn take_body(&mut self) -> Option<Body> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
441
warp/src/server.rs
Normal file
441
warp/src/server.rs
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
#[cfg(feature = "tls")]
|
||||
use crate::tls::TlsConfigBuilder;
|
||||
use std::convert::Infallible;
|
||||
use std::error::Error as StdError;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(feature = "tls")]
|
||||
use std::path::Path;
|
||||
use futures_util::{future, FutureExt, TryFuture, TryStream, TryStreamExt};
|
||||
use hyper::server::conn::AddrIncoming;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::Server as HyperServer;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tracing::Instrument;
|
||||
use crate::filter::Filter;
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::Reply;
|
||||
use crate::transport::Transport;
|
||||
/// Create a `Server` with the provided `Filter`.
|
||||
pub fn serve<F>(filter: F) -> Server<F>
|
||||
where
|
||||
F: Filter + Clone + Send + Sync + 'static,
|
||||
F::Extract: Reply,
|
||||
F::Error: IsReject,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// A Warp Server ready to filter requests.
|
||||
#[derive(Debug)]
|
||||
pub struct Server<F> {
|
||||
pipeline: bool,
|
||||
filter: F,
|
||||
}
|
||||
/// A Warp Server ready to filter requests over TLS.
|
||||
///
|
||||
/// *This type requires the `"tls"` feature.*
|
||||
#[cfg(feature = "tls")]
|
||||
pub struct TlsServer<F> {
|
||||
server: Server<F>,
|
||||
tls: TlsConfigBuilder,
|
||||
}
|
||||
macro_rules! into_service {
|
||||
($into:expr) => {
|
||||
{ let inner = crate ::service($into); make_service_fn(move | transport | { let
|
||||
inner = inner.clone(); let remote_addr = Transport::remote_addr(transport);
|
||||
future::ok::< _, Infallible > (service_fn(move | req | { inner
|
||||
.call_with_addr(req, remote_addr) })) }) }
|
||||
};
|
||||
}
|
||||
macro_rules! addr_incoming {
|
||||
($addr:expr) => {
|
||||
{ let mut incoming = AddrIncoming::bind($addr) ?; incoming.set_nodelay(true); let
|
||||
addr = incoming.local_addr(); (addr, incoming) }
|
||||
};
|
||||
}
|
||||
macro_rules! bind_inner {
|
||||
($this:ident, $addr:expr) => {
|
||||
{ let service = into_service!($this .filter); let (addr, incoming) =
|
||||
addr_incoming!($addr); let srv = HyperServer::builder(incoming)
|
||||
.http1_pipeline_flush($this .pipeline).serve(service); Ok::< _, hyper::Error >
|
||||
((addr, srv)) }
|
||||
};
|
||||
(tls : $this:ident, $addr:expr) => {
|
||||
{ let service = into_service!($this .server.filter); let (addr, incoming) =
|
||||
addr_incoming!($addr); let tls = $this .tls.build() ?; let srv =
|
||||
HyperServer::builder(crate ::tls::TlsAcceptor::new(tls, incoming))
|
||||
.http1_pipeline_flush($this .server.pipeline).serve(service); Ok::< _, Box < dyn
|
||||
std::error::Error + Send + Sync >> ((addr, srv)) }
|
||||
};
|
||||
}
|
||||
macro_rules! bind {
|
||||
($this:ident, $addr:expr) => {
|
||||
{ let addr = $addr .into(); (| addr | bind_inner!($this, addr)) (& addr)
|
||||
.unwrap_or_else(| e | { panic!("error binding to {}: {}", addr, e); }) }
|
||||
};
|
||||
(tls : $this:ident, $addr:expr) => {
|
||||
{ let addr = $addr .into(); (| addr | bind_inner!(tls : $this, addr)) (& addr)
|
||||
.unwrap_or_else(| e | { panic!("error binding to {}: {}", addr, e); }) }
|
||||
};
|
||||
}
|
||||
macro_rules! try_bind {
|
||||
($this:ident, $addr:expr) => {
|
||||
{ (| addr | bind_inner!($this, addr)) ($addr) }
|
||||
};
|
||||
(tls : $this:ident, $addr:expr) => {
|
||||
{ (| addr | bind_inner!(tls : $this, addr)) ($addr) }
|
||||
};
|
||||
}
|
||||
impl<F> Server<F>
|
||||
where
|
||||
F: Filter + Clone + Send + Sync + 'static,
|
||||
<F::Future as TryFuture>::Ok: Reply,
|
||||
<F::Future as TryFuture>::Error: IsReject,
|
||||
{
|
||||
/// Run this `Server` forever on the current thread.
|
||||
pub async fn run(self, addr: impl Into<SocketAddr>) {
|
||||
loop {}
|
||||
}
|
||||
/// Run this `Server` forever on the current thread with a specific stream
|
||||
/// of incoming connections.
|
||||
///
|
||||
/// This can be used for Unix Domain Sockets, or TLS, etc.
|
||||
pub async fn run_incoming<I>(self, incoming: I)
|
||||
where
|
||||
I: TryStream + Send,
|
||||
I::Ok: AsyncRead + AsyncWrite + Send + 'static + Unpin,
|
||||
I::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Bind to a socket address, returning a `Future` that can be
|
||||
/// executed on the current runtime.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we are unable to bind to the provided address.
|
||||
pub fn bind(
|
||||
self,
|
||||
addr: impl Into<SocketAddr> + 'static,
|
||||
) -> impl Future<Output = ()> + 'static {
|
||||
let (_, fut) = self.bind_ephemeral(addr);
|
||||
fut
|
||||
}
|
||||
/// Bind to a socket address, returning a `Future` that can be
|
||||
/// executed on any runtime.
|
||||
///
|
||||
/// In case we are unable to bind to the specified address, resolves to an
|
||||
/// error and logs the reason.
|
||||
pub async fn try_bind(self, addr: impl Into<SocketAddr>) {
|
||||
loop {}
|
||||
}
|
||||
/// Bind to a possibly ephemeral socket address.
|
||||
///
|
||||
/// Returns the bound address and a `Future` that can be executed on
|
||||
/// the current runtime.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we are unable to bind to the provided address.
|
||||
pub fn bind_ephemeral(
|
||||
self,
|
||||
addr: impl Into<SocketAddr>,
|
||||
) -> (SocketAddr, impl Future<Output = ()> + 'static) {
|
||||
let (addr, srv) = bind!(self, addr);
|
||||
let srv = srv
|
||||
.map(|result| {
|
||||
if let Err(err) = result {
|
||||
tracing::error!("server error: {}", err)
|
||||
}
|
||||
});
|
||||
(addr, srv)
|
||||
}
|
||||
/// Tried to bind a possibly ephemeral socket address.
|
||||
///
|
||||
/// Returns a `Result` which fails in case we are unable to bind with the
|
||||
/// underlying error.
|
||||
///
|
||||
/// Returns the bound address and a `Future` that can be executed on
|
||||
/// the current runtime.
|
||||
pub fn try_bind_ephemeral(
|
||||
self,
|
||||
addr: impl Into<SocketAddr>,
|
||||
) -> Result<(SocketAddr, impl Future<Output = ()> + 'static), crate::Error> {
|
||||
let addr = addr.into();
|
||||
let (addr, srv) = try_bind!(self, & addr).map_err(crate::Error::new)?;
|
||||
let srv = srv
|
||||
.map(|result| {
|
||||
if let Err(err) = result {
|
||||
tracing::error!("server error: {}", err)
|
||||
}
|
||||
});
|
||||
Ok((addr, srv))
|
||||
}
|
||||
/// Create a server with graceful shutdown signal.
|
||||
///
|
||||
/// When the signal completes, the server will start the graceful shutdown
|
||||
/// process.
|
||||
///
|
||||
/// Returns the bound address and a `Future` that can be executed on
|
||||
/// the current runtime.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use warp::Filter;
|
||||
/// use futures_util::future::TryFutureExt;
|
||||
/// use tokio::sync::oneshot;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let routes = warp::any()
|
||||
/// .map(|| "Hello, World!");
|
||||
///
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
///
|
||||
/// let (addr, server) = warp::serve(routes)
|
||||
/// .bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), async {
|
||||
/// rx.await.ok();
|
||||
/// });
|
||||
///
|
||||
/// // Spawn the server into a runtime
|
||||
/// tokio::task::spawn(server);
|
||||
///
|
||||
/// // Later, start the shutdown...
|
||||
/// let _ = tx.send(());
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn bind_with_graceful_shutdown(
|
||||
self,
|
||||
addr: impl Into<SocketAddr> + 'static,
|
||||
signal: impl Future<Output = ()> + Send + 'static,
|
||||
) -> (SocketAddr, impl Future<Output = ()> + 'static) {
|
||||
let (addr, srv) = bind!(self, addr);
|
||||
let fut = srv
|
||||
.with_graceful_shutdown(signal)
|
||||
.map(|result| {
|
||||
if let Err(err) = result {
|
||||
tracing::error!("server error: {}", err)
|
||||
}
|
||||
});
|
||||
(addr, fut)
|
||||
}
|
||||
/// Create a server with graceful shutdown signal.
|
||||
///
|
||||
/// When the signal completes, the server will start the graceful shutdown
|
||||
/// process.
|
||||
pub fn try_bind_with_graceful_shutdown(
|
||||
self,
|
||||
addr: impl Into<SocketAddr> + 'static,
|
||||
signal: impl Future<Output = ()> + Send + 'static,
|
||||
) -> Result<(SocketAddr, impl Future<Output = ()> + 'static), crate::Error> {
|
||||
let addr = addr.into();
|
||||
let (addr, srv) = try_bind!(self, & addr).map_err(crate::Error::new)?;
|
||||
let srv = srv
|
||||
.with_graceful_shutdown(signal)
|
||||
.map(|result| {
|
||||
if let Err(err) = result {
|
||||
tracing::error!("server error: {}", err)
|
||||
}
|
||||
});
|
||||
Ok((addr, srv))
|
||||
}
|
||||
/// Setup this `Server` with a specific stream of incoming connections.
|
||||
///
|
||||
/// This can be used for Unix Domain Sockets, or TLS, etc.
|
||||
///
|
||||
/// Returns a `Future` that can be executed on the current runtime.
|
||||
pub fn serve_incoming<I>(self, incoming: I) -> impl Future<Output = ()>
|
||||
where
|
||||
I: TryStream + Send,
|
||||
I::Ok: AsyncRead + AsyncWrite + Send + 'static + Unpin,
|
||||
I::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
let incoming = incoming.map_ok(crate::transport::LiftIo);
|
||||
self.serve_incoming2(incoming)
|
||||
.instrument(tracing::info_span!("Server::serve_incoming"))
|
||||
}
|
||||
/// Setup this `Server` with a specific stream of incoming connections and a
|
||||
/// signal to initiate graceful shutdown.
|
||||
///
|
||||
/// This can be used for Unix Domain Sockets, or TLS, etc.
|
||||
///
|
||||
/// When the signal completes, the server will start the graceful shutdown
|
||||
/// process.
|
||||
///
|
||||
/// Returns a `Future` that can be executed on the current runtime.
|
||||
pub fn serve_incoming_with_graceful_shutdown<I>(
|
||||
self,
|
||||
incoming: I,
|
||||
signal: impl Future<Output = ()> + Send + 'static,
|
||||
) -> impl Future<Output = ()>
|
||||
where
|
||||
I: TryStream + Send,
|
||||
I::Ok: AsyncRead + AsyncWrite + Send + 'static + Unpin,
|
||||
I::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
let incoming = incoming.map_ok(crate::transport::LiftIo);
|
||||
let service = into_service!(self.filter);
|
||||
let pipeline = self.pipeline;
|
||||
async move {
|
||||
let srv = HyperServer::builder(
|
||||
hyper::server::accept::from_stream(incoming.into_stream()),
|
||||
)
|
||||
.http1_pipeline_flush(pipeline)
|
||||
.serve(service)
|
||||
.with_graceful_shutdown(signal)
|
||||
.await;
|
||||
if let Err(err) = srv {
|
||||
tracing::error!("server error: {}", err);
|
||||
}
|
||||
}
|
||||
.instrument(
|
||||
tracing::info_span!("Server::serve_incoming_with_graceful_shutdown"),
|
||||
)
|
||||
}
|
||||
async fn serve_incoming2<I>(self, incoming: I)
|
||||
where
|
||||
I: TryStream + Send,
|
||||
I::Ok: Transport + Send + 'static + Unpin,
|
||||
I::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
pub fn unstable_pipeline(mut self) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Configure a server to use TLS.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(self) -> TlsServer<F> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tls")]
|
||||
impl<F> TlsServer<F>
|
||||
where
|
||||
F: Filter + Clone + Send + Sync + 'static,
|
||||
<F::Future as TryFuture>::Ok: Reply,
|
||||
<F::Future as TryFuture>::Error: IsReject,
|
||||
{
|
||||
/// Specify the file path to read the private key.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn key_path(self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the file path to read the certificate.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn cert_path(self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the file path to read the trust anchor for optional client authentication.
|
||||
///
|
||||
/// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
|
||||
/// of the `client_auth_` methods, then client authentication is disabled by default.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn client_auth_optional_path(self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the file path to read the trust anchor for required client authentication.
|
||||
///
|
||||
/// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
|
||||
/// `client_auth_` methods, then client authentication is disabled by default.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn client_auth_required_path(self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the in-memory contents of the private key.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn key(self, key: impl AsRef<[u8]>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the in-memory contents of the certificate.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn cert(self, cert: impl AsRef<[u8]>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the in-memory contents of the trust anchor for optional client authentication.
|
||||
///
|
||||
/// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
|
||||
/// of the `client_auth_` methods, then client authentication is disabled by default.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn client_auth_optional(self, trust_anchor: impl AsRef<[u8]>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the in-memory contents of the trust anchor for required client authentication.
|
||||
///
|
||||
/// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
|
||||
/// `client_auth_` methods, then client authentication is disabled by default.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn client_auth_required(self, trust_anchor: impl AsRef<[u8]>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the DER-encoded OCSP response.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn ocsp_resp(self, resp: impl AsRef<[u8]>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
fn with_tls<Func>(self, func: Func) -> Self
|
||||
where
|
||||
Func: FnOnce(TlsConfigBuilder) -> TlsConfigBuilder,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Run this `TlsServer` forever on the current thread.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub async fn run(self, addr: impl Into<SocketAddr>) {
|
||||
loop {}
|
||||
}
|
||||
/// Bind to a socket address, returning a `Future` that can be
|
||||
/// executed on a runtime.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub async fn bind(self, addr: impl Into<SocketAddr>) {
|
||||
loop {}
|
||||
}
|
||||
/// Bind to a possibly ephemeral socket address.
|
||||
///
|
||||
/// Returns the bound address and a `Future` that can be executed on
|
||||
/// the current runtime.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn bind_ephemeral(
|
||||
self,
|
||||
addr: impl Into<SocketAddr>,
|
||||
) -> (SocketAddr, impl Future<Output = ()> + 'static) {
|
||||
loop {}
|
||||
}
|
||||
/// Create a server with graceful shutdown signal.
|
||||
///
|
||||
/// When the signal completes, the server will start the graceful shutdown
|
||||
/// process.
|
||||
///
|
||||
/// *This function requires the `"tls"` feature.*
|
||||
pub fn bind_with_graceful_shutdown(
|
||||
self,
|
||||
addr: impl Into<SocketAddr> + 'static,
|
||||
signal: impl Future<Output = ()> + Send + 'static,
|
||||
) -> (SocketAddr, impl Future<Output = ()> + 'static) {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tls")]
|
||||
impl<F> ::std::fmt::Debug for TlsServer<F>
|
||||
where
|
||||
F: ::std::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
3
warp/src/service.rs
Normal file
3
warp/src/service.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
//! Convert `Filter`s into `Service`s
|
||||
|
||||
pub use crate::filter::service::service;
|
||||
537
warp/src/test.rs
Normal file
537
warp/src/test.rs
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
//! Test utilities to test your filters.
|
||||
//!
|
||||
//! [`Filter`](../trait.Filter.html)s can be easily tested without starting up an HTTP
|
||||
//! server, by making use of the [`RequestBuilder`](./struct.RequestBuilder.html) in this
|
||||
//! module.
|
||||
//!
|
||||
//! # Testing Filters
|
||||
//!
|
||||
//! It's easy to test filters, especially if smaller filters are used to build
|
||||
//! up your full set. Consider these example filters:
|
||||
//!
|
||||
//! ```
|
||||
//! use warp::Filter;
|
||||
//!
|
||||
//! fn sum() -> impl Filter<Extract = (u32,), Error = warp::Rejection> + Copy {
|
||||
//! warp::path::param()
|
||||
//! .and(warp::path::param())
|
||||
//! .map(|x: u32, y: u32| {
|
||||
//! x + y
|
||||
//! })
|
||||
//! }
|
||||
//!
|
||||
//! fn math() -> impl Filter<Extract = (String,), Error = warp::Rejection> + Copy {
|
||||
//! warp::post()
|
||||
//! .and(sum())
|
||||
//! .map(|z: u32| {
|
||||
//! format!("Sum = {}", z)
|
||||
//! })
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! We can test some requests against the `sum` filter like this:
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! #[tokio::test]
|
||||
//! async fn test_sum() {
|
||||
//! # let sum = || warp::any().map(|| 3);
|
||||
//! let filter = sum();
|
||||
//!
|
||||
//! // Execute `sum` and get the `Extract` back.
|
||||
//! let value = warp::test::request()
|
||||
//! .path("/1/2")
|
||||
//! .filter(&filter)
|
||||
//! .await
|
||||
//! .unwrap();
|
||||
//! assert_eq!(value, 3);
|
||||
//!
|
||||
//! // Or simply test if a request matches (doesn't reject).
|
||||
//! assert!(
|
||||
//! warp::test::request()
|
||||
//! .path("/1/-5")
|
||||
//! .matches(&filter)
|
||||
//! .await
|
||||
//! );
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! If the filter returns something that implements `Reply`, and thus can be
|
||||
//! turned into a response sent back to the client, we can test what exact
|
||||
//! response is returned. The `math` filter uses the `sum` filter, but returns
|
||||
//! a `String` that can be turned into a response.
|
||||
//!
|
||||
//! ```
|
||||
//! # use warp::Filter;
|
||||
//! #[test]
|
||||
//! fn test_math() {
|
||||
//! # let math = || warp::any().map(warp::reply);
|
||||
//! let filter = math();
|
||||
//!
|
||||
//! let res = warp::test::request()
|
||||
//! .path("/1/2")
|
||||
//! .reply(&filter);
|
||||
//! assert_eq!(res.status(), 405, "GET is not allowed");
|
||||
//!
|
||||
//! let res = warp::test::request()
|
||||
//! .method("POST")
|
||||
//! .path("/1/2")
|
||||
//! .reply(&filter);
|
||||
//! assert_eq!(res.status(), 200);
|
||||
//! assert_eq!(res.body(), "Sum is 3");
|
||||
//! }
|
||||
//! ```
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(feature = "websocket")]
|
||||
use std::pin::Pin;
|
||||
#[cfg(feature = "websocket")]
|
||||
use std::task::Context;
|
||||
#[cfg(feature = "websocket")]
|
||||
use std::task::{self, Poll};
|
||||
use bytes::Bytes;
|
||||
#[cfg(feature = "websocket")]
|
||||
use futures_channel::mpsc;
|
||||
#[cfg(feature = "websocket")]
|
||||
use futures_util::StreamExt;
|
||||
|
||||
use http::{
|
||||
header::{HeaderName, HeaderValue},
|
||||
Response,
|
||||
};
|
||||
use serde::Serialize;
|
||||
#[cfg(feature = "websocket")]
|
||||
use tokio::sync::oneshot;
|
||||
use crate::filter::Filter;
|
||||
#[cfg(feature = "websocket")]
|
||||
use crate::filters::ws::Message;
|
||||
use crate::reject::IsReject;
|
||||
use crate::reply::Reply;
|
||||
|
||||
use crate::Request;
|
||||
#[cfg(feature = "websocket")]
|
||||
use crate::{Sink, Stream};
|
||||
use self::inner::OneOrTuple;
|
||||
/// Starts a new test `RequestBuilder`.
|
||||
pub fn request() -> RequestBuilder {
|
||||
loop {}
|
||||
}
|
||||
/// Starts a new test `WsBuilder`.
|
||||
#[cfg(feature = "websocket")]
|
||||
pub fn ws() -> WsBuilder {
|
||||
loop {}
|
||||
}
|
||||
/// A request builder for testing filters.
|
||||
///
|
||||
/// See [module documentation](crate::test) for an overview.
|
||||
#[must_use = "RequestBuilder does nothing on its own"]
|
||||
#[derive(Debug)]
|
||||
pub struct RequestBuilder {
|
||||
remote_addr: Option<SocketAddr>,
|
||||
req: Request,
|
||||
}
|
||||
/// A Websocket builder for testing filters.
|
||||
///
|
||||
/// See [module documentation](crate::test) for an overview.
|
||||
#[cfg(feature = "websocket")]
|
||||
#[must_use = "WsBuilder does nothing on its own"]
|
||||
#[derive(Debug)]
|
||||
pub struct WsBuilder {
|
||||
req: RequestBuilder,
|
||||
}
|
||||
/// A test client for Websocket filters.
|
||||
#[cfg(feature = "websocket")]
|
||||
pub struct WsClient {
|
||||
tx: mpsc::UnboundedSender<crate::ws::Message>,
|
||||
rx: mpsc::UnboundedReceiver<Result<crate::ws::Message, crate::error::Error>>,
|
||||
}
|
||||
/// An error from Websocket filter tests.
|
||||
#[derive(Debug)]
|
||||
pub struct WsError {
|
||||
cause: Box<dyn StdError + Send + Sync>,
|
||||
}
|
||||
impl RequestBuilder {
|
||||
/// Sets the method of this builder.
|
||||
///
|
||||
/// The default if not set is `GET`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let req = warp::test::request()
|
||||
/// .method("POST");
|
||||
/// ```
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This panics if the passed string is not able to be parsed as a valid
|
||||
/// `Method`.
|
||||
pub fn method(mut self, method: &str) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Sets the request path of this builder.
|
||||
///
|
||||
/// The default is not set is `/`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let req = warp::test::request()
|
||||
/// .path("/todos/33");
|
||||
/// ```
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This panics if the passed string is not able to be parsed as a valid
|
||||
/// `Uri`.
|
||||
pub fn path(mut self, p: &str) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Set a header for this request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let req = warp::test::request()
|
||||
/// .header("accept", "application/json");
|
||||
/// ```
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This panics if the passed strings are not able to be parsed as a valid
|
||||
/// `HeaderName` and `HeaderValue`.
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
HeaderValue: TryFrom<V>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Set the remote address of this request
|
||||
///
|
||||
/// Default is no remote address.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
///
|
||||
/// let req = warp::test::request()
|
||||
/// .remote_addr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080));
|
||||
/// ```
|
||||
pub fn remote_addr(mut self, addr: SocketAddr) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Add a type to the request's `http::Extensions`.
|
||||
pub fn extension<T>(mut self, ext: T) -> Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Set the bytes of this request body.
|
||||
///
|
||||
/// Default is an empty body.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let req = warp::test::request()
|
||||
/// .body("foo=bar&baz=quux");
|
||||
/// ```
|
||||
pub fn body(mut self, body: impl AsRef<[u8]>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Set the bytes of this request body by serializing a value into JSON.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let req = warp::test::request()
|
||||
/// .json(&true);
|
||||
/// ```
|
||||
pub fn json(mut self, val: &impl Serialize) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Tries to apply the `Filter` on this request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// async {
|
||||
/// let param = warp::path::param::<u32>();
|
||||
///
|
||||
/// let ex = warp::test::request()
|
||||
/// .path("/41")
|
||||
/// .filter(¶m)
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(ex, 41);
|
||||
///
|
||||
/// assert!(
|
||||
/// warp::test::request()
|
||||
/// .path("/foo")
|
||||
/// .filter(¶m)
|
||||
/// .await
|
||||
/// .is_err()
|
||||
/// );
|
||||
///};
|
||||
/// ```
|
||||
pub async fn filter<F>(
|
||||
self,
|
||||
f: &F,
|
||||
) -> Result<<F::Extract as OneOrTuple>::Output, F::Error>
|
||||
where
|
||||
F: Filter,
|
||||
F::Future: Send + 'static,
|
||||
F::Extract: OneOrTuple + Send + 'static,
|
||||
F::Error: Send + 'static,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Returns whether the `Filter` matches this request, or rejects it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// async {
|
||||
/// let get = warp::get();
|
||||
/// let post = warp::post();
|
||||
///
|
||||
/// assert!(
|
||||
/// warp::test::request()
|
||||
/// .method("GET")
|
||||
/// .matches(&get)
|
||||
/// .await
|
||||
/// );
|
||||
///
|
||||
/// assert!(
|
||||
/// !warp::test::request()
|
||||
/// .method("GET")
|
||||
/// .matches(&post)
|
||||
/// .await
|
||||
/// );
|
||||
///};
|
||||
/// ```
|
||||
pub async fn matches<F>(self, f: &F) -> bool
|
||||
where
|
||||
F: Filter,
|
||||
F::Future: Send + 'static,
|
||||
F::Extract: Send + 'static,
|
||||
F::Error: Send + 'static,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Returns `Response` provided by applying the `Filter`.
|
||||
///
|
||||
/// This requires that the supplied `Filter` return a [`Reply`](Reply).
|
||||
pub async fn reply<F>(self, f: &F) -> Response<Bytes>
|
||||
where
|
||||
F: Filter + 'static,
|
||||
F::Extract: Reply + Send,
|
||||
F::Error: IsReject + Send,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
impl WsBuilder {
|
||||
/// Sets the request path of this builder.
|
||||
///
|
||||
/// The default is not set is `/`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let req = warp::test::ws()
|
||||
/// .path("/chat");
|
||||
/// ```
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This panics if the passed string is not able to be parsed as a valid
|
||||
/// `Uri`.
|
||||
pub fn path(self, p: &str) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Set a header for this request.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let req = warp::test::ws()
|
||||
/// .header("foo", "bar");
|
||||
/// ```
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This panics if the passed strings are not able to be parsed as a valid
|
||||
/// `HeaderName` and `HeaderValue`.
|
||||
pub fn header<K, V>(self, key: K, value: V) -> Self
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
HeaderValue: TryFrom<V>,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
/// Execute this Websocket request against the provided filter.
|
||||
///
|
||||
/// If the handshake succeeds, returns a `WsClient`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use futures_util::future;
|
||||
/// use warp::Filter;
|
||||
/// #[tokio::main]
|
||||
/// # async fn main() {
|
||||
///
|
||||
/// // Some route that accepts websockets (but drops them immediately).
|
||||
/// let route = warp::ws()
|
||||
/// .map(|ws: warp::ws::Ws| {
|
||||
/// ws.on_upgrade(|_| future::ready(()))
|
||||
/// });
|
||||
///
|
||||
/// let client = warp::test::ws()
|
||||
/// .handshake(route)
|
||||
/// .await
|
||||
/// .expect("handshake");
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn handshake<F>(self, f: F) -> Result<WsClient, WsError>
|
||||
where
|
||||
F: Filter + Clone + Send + Sync + 'static,
|
||||
F::Extract: Reply + Send,
|
||||
F::Error: IsReject + Send,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
impl WsClient {
|
||||
/// Send a "text" websocket message to the server.
|
||||
pub async fn send_text(&mut self, text: impl Into<String>) {
|
||||
loop {}
|
||||
}
|
||||
/// Send a websocket message to the server.
|
||||
pub async fn send(&mut self, msg: crate::ws::Message) {
|
||||
loop {}
|
||||
}
|
||||
/// Receive a websocket message from the server.
|
||||
pub async fn recv(&mut self) -> Result<crate::filters::ws::Message, WsError> {
|
||||
loop {}
|
||||
}
|
||||
/// Assert the server has closed the connection.
|
||||
pub async fn recv_closed(&mut self) -> Result<(), WsError> {
|
||||
loop {}
|
||||
}
|
||||
fn pinned_tx(
|
||||
self: Pin<&mut Self>,
|
||||
) -> Pin<&mut mpsc::UnboundedSender<crate::ws::Message>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
impl fmt::Debug for WsClient {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
impl Sink<crate::ws::Message> for WsClient {
|
||||
type Error = WsError;
|
||||
fn poll_ready(
|
||||
self: Pin<&mut Self>,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
fn start_send(self: Pin<&mut Self>, message: Message) -> Result<(), Self::Error> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_close(
|
||||
self: Pin<&mut Self>,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
impl Stream for WsClient {
|
||||
type Item = Result<crate::ws::Message, WsError>;
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
impl WsError {
|
||||
fn new<E: Into<Box<dyn StdError + Send + Sync>>>(cause: E) -> Self {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for WsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl StdError for WsError {
|
||||
fn description(&self) -> &str {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
#[derive(Clone)]
|
||||
struct AddrConnect(SocketAddr);
|
||||
#[cfg(feature = "websocket")]
|
||||
impl tower_service::Service<::http::Uri> for AddrConnect {
|
||||
type Response = ::tokio::net::TcpStream;
|
||||
type Error = ::std::io::Error;
|
||||
type Future = Pin<
|
||||
Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>,
|
||||
>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
loop {}
|
||||
}
|
||||
fn call(&mut self, _: ::http::Uri) -> Self::Future {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
mod inner {
|
||||
pub trait OneOrTuple {
|
||||
type Output;
|
||||
fn one_or_tuple(self) -> Self::Output;
|
||||
}
|
||||
impl OneOrTuple for () {
|
||||
type Output = ();
|
||||
fn one_or_tuple(self) -> Self::Output {}
|
||||
}
|
||||
macro_rules! one_or_tuple {
|
||||
($type1:ident) => {
|
||||
impl <$type1 > OneOrTuple for ($type1,) { type Output = $type1; fn
|
||||
one_or_tuple(self) -> Self::Output { self.0 } }
|
||||
};
|
||||
($type1:ident, $($type:ident),*) => {
|
||||
one_or_tuple!($($type),*); impl <$type1, $($type),*> OneOrTuple for ($type1,
|
||||
$($type),*) { type Output = Self; fn one_or_tuple(self) -> Self::Output {
|
||||
self } }
|
||||
};
|
||||
}
|
||||
one_or_tuple! {
|
||||
T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
|
||||
}
|
||||
}
|
||||
212
warp/src/tls.rs
Normal file
212
warp/src/tls.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::io::{self, BufReader, Cursor, Read};
|
||||
use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use futures_util::ready;
|
||||
use hyper::server::accept::Accept;
|
||||
use hyper::server::conn::{AddrIncoming, AddrStream};
|
||||
use crate::transport::Transport;
|
||||
use tokio_rustls::rustls::{
|
||||
server::{
|
||||
AllowAnyAnonymousOrAuthenticatedClient, AllowAnyAuthenticatedClient, NoClientAuth,
|
||||
},
|
||||
Certificate, Error as TlsError, PrivateKey, RootCertStore, ServerConfig,
|
||||
};
|
||||
/// Represents errors that can occur building the TlsConfig
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TlsConfigError {
|
||||
Io(io::Error),
|
||||
/// An Error parsing the Certificate
|
||||
CertParseError,
|
||||
/// An Error parsing a Pkcs8 key
|
||||
Pkcs8ParseError,
|
||||
/// An Error parsing a Rsa key
|
||||
RsaParseError,
|
||||
/// An error from an empty key
|
||||
EmptyKey,
|
||||
/// An error from an invalid key
|
||||
InvalidKey(TlsError),
|
||||
}
|
||||
impl fmt::Display for TlsConfigError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for TlsConfigError {}
|
||||
/// Tls client authentication configuration.
|
||||
pub(crate) enum TlsClientAuth {
|
||||
/// No client auth.
|
||||
Off,
|
||||
/// Allow any anonymous or authenticated client.
|
||||
Optional(Box<dyn Read + Send + Sync>),
|
||||
/// Allow any authenticated client.
|
||||
Required(Box<dyn Read + Send + Sync>),
|
||||
}
|
||||
/// Builder to set the configuration for the Tls server.
|
||||
pub(crate) struct TlsConfigBuilder {
|
||||
cert: Box<dyn Read + Send + Sync>,
|
||||
key: Box<dyn Read + Send + Sync>,
|
||||
client_auth: TlsClientAuth,
|
||||
ocsp_resp: Vec<u8>,
|
||||
}
|
||||
impl fmt::Debug for TlsConfigBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl TlsConfigBuilder {
|
||||
/// Create a new TlsConfigBuilder
|
||||
pub(crate) fn new() -> TlsConfigBuilder {
|
||||
loop {}
|
||||
}
|
||||
/// sets the Tls key via File Path, returns `TlsConfigError::IoError` if the file cannot be open
|
||||
pub(crate) fn key_path(mut self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// sets the Tls key via bytes slice
|
||||
pub(crate) fn key(mut self, key: &[u8]) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Specify the file path for the TLS certificate to use.
|
||||
pub(crate) fn cert_path(mut self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// sets the Tls certificate via bytes slice
|
||||
pub(crate) fn cert(mut self, cert: &[u8]) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Sets the trust anchor for optional Tls client authentication via file path.
|
||||
///
|
||||
/// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
|
||||
/// of the `client_auth_` methods, then client authentication is disabled by default.
|
||||
pub(crate) fn client_auth_optional_path(mut self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Sets the trust anchor for optional Tls client authentication via bytes slice.
|
||||
///
|
||||
/// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
|
||||
/// of the `client_auth_` methods, then client authentication is disabled by default.
|
||||
pub(crate) fn client_auth_optional(mut self, trust_anchor: &[u8]) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Sets the trust anchor for required Tls client authentication via file path.
|
||||
///
|
||||
/// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
|
||||
/// `client_auth_` methods, then client authentication is disabled by default.
|
||||
pub(crate) fn client_auth_required_path(mut self, path: impl AsRef<Path>) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// Sets the trust anchor for required Tls client authentication via bytes slice.
|
||||
///
|
||||
/// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
|
||||
/// `client_auth_` methods, then client authentication is disabled by default.
|
||||
pub(crate) fn client_auth_required(mut self, trust_anchor: &[u8]) -> Self {
|
||||
loop {}
|
||||
}
|
||||
/// sets the DER-encoded OCSP response
|
||||
pub(crate) fn ocsp_resp(mut self, ocsp_resp: &[u8]) -> Self {
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn build(mut self) -> Result<ServerConfig, TlsConfigError> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
struct LazyFile {
|
||||
path: PathBuf,
|
||||
file: Option<File>,
|
||||
}
|
||||
impl LazyFile {
|
||||
fn lazy_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Read for LazyFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Transport for TlsStream {
|
||||
fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
enum State {
|
||||
Handshaking(tokio_rustls::Accept<AddrStream>),
|
||||
Streaming(tokio_rustls::server::TlsStream<AddrStream>),
|
||||
}
|
||||
pub(crate) struct TlsStream {
|
||||
state: State,
|
||||
remote_addr: SocketAddr,
|
||||
}
|
||||
impl TlsStream {
|
||||
fn new(stream: AddrStream, config: Arc<ServerConfig>) -> TlsStream {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl AsyncRead for TlsStream {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl AsyncWrite for TlsStream {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_shutdown(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
pub(crate) struct TlsAcceptor {
|
||||
config: Arc<ServerConfig>,
|
||||
incoming: AddrIncoming,
|
||||
}
|
||||
impl TlsAcceptor {
|
||||
pub(crate) fn new(config: ServerConfig, incoming: AddrIncoming) -> TlsAcceptor {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl Accept for TlsAcceptor {
|
||||
type Conn = TlsStream;
|
||||
type Error = io::Error;
|
||||
fn poll_accept(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn file_cert_key() {
|
||||
loop {}
|
||||
}
|
||||
#[test]
|
||||
fn bytes_cert_key() {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
50
warp/src/transport.rs
Normal file
50
warp/src/transport.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use hyper::server::conn::AddrStream;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
pub trait Transport: AsyncRead + AsyncWrite {
|
||||
fn remote_addr(&self) -> Option<SocketAddr>;
|
||||
}
|
||||
impl Transport for AddrStream {
|
||||
fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
pub(crate) struct LiftIo<T>(pub(crate) T);
|
||||
impl<T: AsyncRead + Unpin> AsyncRead for LiftIo<T> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: AsyncWrite + Unpin> AsyncWrite for LiftIo<T> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), io::Error>> {
|
||||
loop {}
|
||||
}
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), io::Error>> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: AsyncRead + AsyncWrite + Unpin> Transport for LiftIo<T> {
|
||||
fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue