tokio-rs / axum Goto Github PK
View Code? Open in Web Editor NEWErgonomic and modular web framework built with Tokio, Tower, and Hyper
Ergonomic and modular web framework built with Tokio, Tower, and Hyper
The Rust Playground has a number of assets (mainly JavaScript) that rarely get updated. To save bandwidth, these are served in a compressed form. To save CPU time, they are precompressed on disk.
When a request is made for foo.js
and the client indicates that they accept GZip-compressed data, we look instead for foo.js.gz
. If that's found, we return it. Otherwise, foo.js
is attempted.
The current implementation of this (for the old Iron framework) has a fair amount of hand-written code to achieve this. Most web frameworks I've investigated make it very difficult (impossible?) to reuse relevant code such as path traversal protection or file -> HTTP caching information.
I don't think that this is something that Axum itself needs to provide, as it's a relatively uncommon requirement. However, I do think that it should be possible to reuse large chunks of Axum code such as serving a static file.
If it seems generally interesting, then I don't see why it couldn't be added to Axum proper, of course.
I believe many people punt this off to a different web server. For example, NGINX plus ngx_http_gzip_static_module
. I don't like this path, but I see why many people choose it.
There could be a lot of ways of doing this, but the idea is basically the same - allow to pass custom regex to each capture group in route string. Imo best would be to allow to specify name of the the passed group (lookup custom converter in flask). These groups would be predefined in some sort of CustomConverterMap<string, Regex> and somehow passed to the route function (wip). Perhaps there could be different route function (like in django case re_path instead of path) or constrains in case of rails.
# how it is now (id matches everything)
route("/greet/:id", handler);
# how it could be
route("/greet/<int:id>", handler);
route("/greet/<int:id>-<str:name>", handler);
# or even to pass regex string directly into route match string
route("/greet/<r`[1-9][0-9]{3}`:id>", handler); # match into id only ints between 1000 and 9999
How others are doing it:
Probably the only alternative is changing the spec for url patterns of the application I'm afraid. Stll With full path regex match we can employ same strategy as with 404 messages - use tower response middleware (aka MapResponseLayer) to simply handle it there. It is not the best option though since it decreases readibility - best is to have all route matches explicitly.
All steps to reproduce the problem (including the cargo output):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://github.com/tokio-rs/axum.git
cd axum/
cargo build --features ws --example websocket
Updating crates.io index
Compiling autocfg v1.0.1
Compiling cfg-if v1.0.0
Compiling proc-macro2 v1.0.28
Compiling unicode-xid v0.2.2
Compiling syn v1.0.74
Compiling libc v0.2.98
Compiling version_check v0.9.3
Compiling memchr v2.4.0
Compiling log v0.4.14
Compiling pin-project-lite v0.2.7
Compiling bytes v1.0.1
Compiling futures-core v0.3.16
Compiling smallvec v1.6.1
Compiling typenum v1.13.0
Compiling proc-macro-hack v0.5.19
Compiling scopeguard v1.1.0
Compiling proc-macro-nested v0.1.7
Compiling futures-channel v0.3.16
Compiling futures-sink v0.3.16
Compiling futures-task v0.3.16
Compiling bitflags v1.2.1
Compiling ryu v1.0.5
Compiling futures-io v0.3.16
Compiling pin-utils v0.1.0
Compiling matches v0.1.8
Compiling serde_derive v1.0.126
Compiling slab v0.4.3
Compiling tinyvec_macros v0.1.0
Compiling lazy_static v1.4.0
Compiling itoa v0.4.7
Compiling serde v1.0.126
Compiling percent-encoding v2.1.0
Compiling radium v0.5.3
Compiling opaque-debug v0.3.0
Compiling cc v1.0.69
Compiling fnv v1.0.7
Compiling pkg-config v0.3.19
Compiling ppv-lite86 v0.2.10
Compiling base64 v0.13.0
Compiling cpufeatures v0.1.5
Compiling lexical-core v0.7.6
Compiling wyz v0.2.0
Compiling tap v1.0.1
Compiling httparse v1.4.1
Compiling arrayvec v0.5.2
Compiling static_assertions v1.1.0
Compiling funty v1.1.0
Compiling byteorder v1.4.3
Compiling once_cell v1.8.0
Compiling openssl v0.10.35
Compiling alloc-no-stdlib v2.0.1
Compiling subtle v2.4.1
Compiling tower-service v0.3.1
Compiling async-trait v0.1.51
Compiling foreign-types-shared v0.1.1
Compiling hashbrown v0.11.2
Compiling crc32fast v1.2.1
Compiling regex-syntax v0.6.25
Compiling serde_json v1.0.66
Compiling try-lock v0.2.3
Compiling adler v1.0.2
Compiling native-tls v0.2.7
Compiling fallible-iterator v0.2.0
Compiling siphasher v0.3.6
Compiling httpdate v1.0.1
Compiling openssl-probe v0.1.4
Compiling mime v0.3.16
Compiling tower-layer v0.3.1
Compiling askama_escape v0.10.1
Compiling utf-8 v0.7.6
Compiling humansize v1.1.1
Compiling encoding_rs v0.8.28
Compiling ipnet v2.3.1
Compiling ansi_term v0.12.1
Compiling instant v0.1.10
Compiling lock_api v0.4.4
Compiling input_buffer v0.4.0
Compiling generic-array v0.14.4
Compiling nom v6.1.2
Compiling unicase v2.6.0
Compiling tokio v1.9.0
Compiling futures-macro v0.3.16
Compiling futures-util v0.3.16
Compiling num-traits v0.2.14
Compiling indexmap v1.7.0
Compiling miniz_oxide v0.4.4
Compiling num-integer v0.1.44
Compiling unicode-bidi v0.3.5
Compiling tinyvec v1.3.1
Compiling tracing-core v0.1.18
Compiling sharded-slab v0.1.1
Compiling http v0.2.4
Compiling form_urlencoded v1.0.1
Compiling alloc-stdlib v0.2.1
Compiling thread_local v1.1.3
Compiling foreign-types v0.3.2
Compiling phf_shared v0.8.0
Compiling openssl-sys v0.9.65
Compiling brotli-decompressor v2.3.1
Compiling phf v0.8.0
Compiling want v0.3.0
Compiling tracing-log v0.1.2
Compiling quote v1.0.9
Compiling aho-corasick v0.7.18
Compiling unicode-normalization v0.1.19
Compiling regex-automata v0.1.10
Compiling parking_lot_core v0.8.3
Compiling mio v0.7.13
Compiling num_cpus v1.13.0
Compiling getrandom v0.2.3
Compiling socket2 v0.4.1
Compiling bitvec v0.19.5
Compiling http-body v0.4.2
Compiling mime_guess v2.0.3
Compiling parking_lot v0.11.1
Compiling rand_core v0.6.3
Compiling regex v1.5.4
Compiling flate2 v1.0.20
Compiling brotli v3.3.0
Compiling idna v0.2.3
Compiling stringprep v0.1.2
Compiling matchers v0.0.1
Compiling digest v0.9.0
Compiling block-buffer v0.9.0
Compiling crypto-mac v0.10.1
Compiling rand_chacha v0.3.1
Compiling url v2.2.2
Compiling chrono v0.4.19
Compiling hmac v0.10.1
Compiling sha2 v0.9.5
Compiling md-5 v0.9.1
Compiling sha-1 v0.9.7
Compiling rand v0.8.4
Compiling postgres-protocol v0.6.1
Compiling postgres-types v0.2.1
Compiling iri-string v0.4.0
Compiling tokio-macros v1.3.0
Compiling tracing-attributes v0.1.15
Compiling pin-project-internal v1.0.8
Compiling thiserror-impl v1.0.26
Compiling thiserror v1.0.26
Compiling tungstenite v0.13.0
Compiling pin-project v1.0.8
Compiling tracing v0.1.26
Compiling futures-executor v0.3.16
Compiling futures v0.3.16
Compiling tokio-util v0.6.7
Compiling async-compression v0.3.8
Compiling tokio-native-tls v0.3.0
Compiling bb8 v0.7.0
Compiling tokio-tungstenite v0.14.0
Compiling h2 v0.3.3
Compiling tower v0.4.8
Compiling tokio-postgres v0.7.2
Compiling tower-http v0.1.1
Compiling bb8-postgres v0.7.0
Compiling toml v0.5.8
Compiling serde_urlencoded v0.7.0
Compiling tracing-serde v0.1.2
Compiling uuid v0.8.2
Compiling tracing-subscriber v0.2.19
Compiling askama_shared v0.11.1
Compiling hyper v0.14.11
Compiling askama_derive v0.10.5
Compiling askama v0.10.5
Compiling hyper-tls v0.5.0
Compiling axum v0.1.1 (~/axum)
Compiling reqwest v0.11.4
error[E0432]: unresolved import `axum::extract::TypedHeader`
--> examples/websocket.rs:13:5
|
13 | extract::TypedHeader,
| ^^^^^^^^^^^^^^^^^^^^ no `TypedHeader` in `extract`
error[E0433]: failed to resolve: use of undeclared crate or module `headers`
--> examples/websocket.rs:64:42
|
64 | TypedHeader(user_agent): TypedHeader<headers::UserAgent>,
| ^^^^^^^ use of undeclared crate or module `headers`
error[E0277]: the trait bound `fn(WebSocket, [type error]) -> impl Future {handle_socket}: WebSocketHandler<_, _>` is not satisfied
--> examples/websocket.rs:46:22
|
46 | .route("/ws", ws(handle_socket))
| ^^^^^^^^^^^^^ the trait `WebSocketHandler<_, _>` is not implemented for `fn(WebSocket, [type error]) -> impl Future {handle_socket}`
|
::: ~/axum/src/ws/mod.rs:90:8
|
90 | F: WebSocketHandler<B, T>,
| ---------------------- required by this bound in `ws`
error[E0277]: the trait bound `fn(WebSocket, [type error]) -> impl Future {handle_socket}: WebSocketHandler<_, _>` is not satisfied
--> examples/websocket.rs:46:19
|
46 | .route("/ws", ws(handle_socket))
| ^^^^^^^^^^^^^^^^^ the trait `WebSocketHandler<_, _>` is not implemented for `fn(WebSocket, [type error]) -> impl Future {handle_socket}`
|
= note: required because of the requirements on the impl of `Service<Request<_>>` for `WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, _, _>`
error[E0277]: the trait bound `fn(WebSocket, [type error]) -> impl Future {handle_socket}: WebSocketHandler<hyper::Body, _>` is not satisfied
--> examples/websocket.rs:56:16
|
56 | .serve(app.into_make_service())
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `WebSocketHandler<hyper::Body, _>` is not implemented for `fn(WebSocket, [type error]) -> impl Future {handle_socket}`
|
= note: required because of the requirements on the impl of `Service<Request<hyper::Body>>` for `WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>`
= note: 2 redundant requirements hidden
= note: required because of the requirements on the impl of `Service<Request<hyper::Body>>` for `tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>`
= note: required because of the requirements on the impl of `hyper::service::http::HttpService<hyper::Body>` for `axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>`
= note: required because of the requirements on the impl of `hyper::service::make::MakeServiceRef<AddrStream, hyper::Body>` for `tower::make::make_service::shared::Shared<axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>>`
error[E0277]: the trait bound `hyper::common::exec::Exec: hyper::common::exec::ConnStreamExec<tower_http::trace::ResponseFuture<RouteFuture<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>, hyper::Body>, ServerErrorsAsFailures, DefaultOnResponse, DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure>, tower_http::trace::ResponseBody<http_body::combinators::box_body::BoxBody<hyper::body::Bytes, BoxStdError>, NeverClassifyEos<ServerErrorsFailureClass>, DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure>>` is not satisfied
--> examples/websocket.rs:55:5
|
55 | / hyper::Server::bind(&addr)
56 | | .serve(app.into_make_service())
57 | | .await
| |______________^ the trait `hyper::common::exec::ConnStreamExec<tower_http::trace::ResponseFuture<RouteFuture<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>, hyper::Body>, ServerErrorsAsFailures, DefaultOnResponse, DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure>, tower_http::trace::ResponseBody<http_body::combinators::box_body::BoxBody<hyper::body::Bytes, BoxStdError>, NeverClassifyEos<ServerErrorsFailureClass>, DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure>>` is not implemented for `hyper::common::exec::Exec`
|
= help: the following implementations were found:
<hyper::common::exec::Exec as hyper::common::exec::ConnStreamExec<F, B>>
= note: required because of the requirements on the impl of `Future` for `Server<AddrIncoming, tower::make::make_service::shared::Shared<axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>>>`
= note: required by `poll`
error[E0277]: the trait bound `hyper::common::exec::Exec: hyper::common::exec::NewSvcExec<AddrStream, tower::make::make_service::shared::SharedFuture<axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>>, axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>, hyper::common::exec::Exec, hyper::server::conn::spawn_all::NoopWatcher>` is not satisfied
--> examples/websocket.rs:55:5
|
55 | / hyper::Server::bind(&addr)
56 | | .serve(app.into_make_service())
57 | | .await
| |______________^ the trait `hyper::common::exec::NewSvcExec<AddrStream, tower::make::make_service::shared::SharedFuture<axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>>, axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>, hyper::common::exec::Exec, hyper::server::conn::spawn_all::NoopWatcher>` is not implemented for `hyper::common::exec::Exec`
|
= help: the following implementations were found:
<hyper::common::exec::Exec as hyper::common::exec::NewSvcExec<I, N, S, E, W>>
= note: required because of the requirements on the impl of `Future` for `Server<AddrIncoming, tower::make::make_service::shared::Shared<axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>>>`
= note: required by `poll`
error[E0277]: the trait bound `fn(WebSocket, [type error]) -> impl Future {handle_socket}: WebSocketHandler<hyper::Body, _>` is not satisfied
--> examples/websocket.rs:55:5
|
55 | / hyper::Server::bind(&addr)
56 | | .serve(app.into_make_service())
57 | | .await
| |______________^ the trait `WebSocketHandler<hyper::Body, _>` is not implemented for `fn(WebSocket, [type error]) -> impl Future {handle_socket}`
|
= note: required because of the requirements on the impl of `Service<Request<hyper::Body>>` for `WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>`
= note: 2 redundant requirements hidden
= note: required because of the requirements on the impl of `Service<Request<hyper::Body>>` for `tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>`
= note: required because of the requirements on the impl of `hyper::service::http::HttpService<hyper::Body>` for `axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>`
= note: required because of the requirements on the impl of `hyper::service::make::MakeServiceRef<AddrStream, hyper::Body>` for `tower::make::make_service::shared::Shared<axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>>`
= note: required because of the requirements on the impl of `Future` for `Server<AddrIncoming, tower::make::make_service::shared::Shared<axum::routing::Layered<tower_http::trace::Trace<Route<WebSocketUpgrade<fn(WebSocket, [type error]) -> impl Future {handle_socket}, hyper::Body, _>, Nested<axum::service::OnMethod<BoxResponseBody<HandleError<ServeDir, [closure@examples/websocket.rs:36:31: 41:18], hyper::Body>, hyper::Body>, EmptyRouter>, EmptyRouter>>, SharedClassifier<ServerErrorsAsFailures>>>>>`
= note: required by `poll`
error: aborting due to 8 previous errors
Some errors have detailed explanations: E0277, E0432, E0433.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `axum`
To learn more, run the command again with --verbose.
a0f6dcc (main branch August 2nd)
popOS! / Linux
Linux pop-os 5.11.0-7620-generic #21~1626191760~21.04~55de9c3-Ubuntu SMP Tue Jul 20 22:18:55 UTC x86_64 x86_64 x86_64 GNU/Linux
It DOES work on macOS M1
20.3.0 Darwin Kernel Version 20.3.0: Thu Jan 21 00:06:51 PST 2021; root:xnu-7195.81.3~1/RELEASE_ARM64_T8101 arm64
git clone ...
cd axum
RUST_LOG=tower_http=debug,key_value_store=trace \
cargo run \
--all-features \
--example websocket
websocat ws://127.0.0.1/ws
...error connecting 400...
They should use IntoResponse
.
Can probably base it on warp's implementation.
Quick little app that has a form
use http::Request;
use hyper::Server;
use std::{collections::HashMap, net::SocketAddr};
use tower::make::Shared;
use tower_web::prelude::*;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
// build our application with some routes
let app = route("/", get(show_form).post(accept_form))
.layer(tower_http::trace::TraceLayer::new_for_http());
// run it with hyper
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
let server = Server::bind(&addr).serve(Shared::new(app));
server.await.unwrap();
}
async fn show_form(_req: Request<Body>) -> response::Html<&'static str> {
response::Html(
r#"
<!doctype html>
<html>
<head></head>
<body>
<form action="/" method="post">
<label for="name">Enter your name: </label>
<input type="text" name="name" id="name" required>
<label for="email">Enter your email: </label>
<input type="email" name="email" id="email" required>
<input type="submit" value="Subscribe!">
</form>
</body>
</html>
"#,
)
}
async fn accept_form(
req: Request<Body>,
query: Option<extract::Query<HashMap<String, String>>>,
body: String,
) {
println!("query = {:?}", query);
println!("body = {}", body);
println!("headers = {:?}", req.headers());
}
Should also handle file uploads.
Something along the lines of
#![allow(unused_imports)]
use bytes::Bytes;
use futures::prelude::*;
use futures::SinkExt;
use http::{header::HeaderName, HeaderValue, Request, Response, StatusCode};
use http_body::Empty;
use hyper::{
server::conn::AddrStream,
upgrade::{OnUpgrade, Upgraded},
Body,
};
use sha1::{Digest, Sha1};
use std::future::Future;
use std::pin::Pin;
use std::task::Context;
use std::{convert::Infallible, task::Poll};
use tokio_tungstenite::{
tungstenite::protocol::{self, WebSocketConfig},
WebSocketStream,
};
use tower::{make::Shared, ServiceBuilder};
use tower::{BoxError, Service};
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
use tower_http::LatencyUnit;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let svc = ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.service(WebSocketUpgrade::new(handle_socket));
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 3000));
hyper::Server::bind(&addr)
.serve(Shared::new(svc))
.await
.unwrap();
}
async fn handle_socket(mut socket: WebSocket) {
while let Some(msg) = socket.recv().await {
println!("received message: {:?}", msg);
}
}
#[derive(Debug, Clone)]
pub struct WebSocketUpgrade<F> {
callback: F,
config: WebSocketConfig,
}
impl<F> WebSocketUpgrade<F> {
pub fn new(callback: F) -> Self {
Self {
callback,
config: WebSocketConfig::default(),
}
}
}
impl<ReqBody, F, Fut> Service<Request<ReqBody>> for WebSocketUpgrade<F>
where
F: FnOnce(WebSocket) -> Fut + Clone + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
type Response = Response<Empty<Bytes>>;
type Error = BoxError;
type Future = ResponseFuture;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
// TODO(david): missing `upgrade` should return "bad request"
if !header_eq(
&req,
HeaderName::from_static("upgrade"),
HeaderValue::from_static("websocket"),
) {
todo!()
}
if !header_eq(
&req,
HeaderName::from_static("sec-websocket-version"),
HeaderValue::from_static("13"),
) {
todo!()
}
let key = if let Some(key) = req.headers_mut().remove("sec-websocket-key") {
key
} else {
todo!()
};
let on_upgrade = req.extensions_mut().remove::<OnUpgrade>().unwrap();
let config = self.config;
let callback = self.callback.clone();
tokio::spawn(async move {
let upgraded = on_upgrade.await.unwrap();
let socket =
WebSocketStream::from_raw_socket(upgraded, protocol::Role::Server, Some(config))
.await;
let socket = WebSocket { inner: socket };
callback(socket).await;
});
ResponseFuture { key: Some(key) }
}
}
#[derive(Debug)]
pub struct ResponseFuture {
key: Option<HeaderValue>,
}
impl Future for ResponseFuture {
type Output = Result<Response<Empty<Bytes>>, BoxError>;
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = Response::builder()
.status(StatusCode::SWITCHING_PROTOCOLS)
.header(
http::header::CONNECTION,
HeaderValue::from_str("upgrade").unwrap(),
)
.header(
http::header::UPGRADE,
HeaderValue::from_str("websocket").unwrap(),
)
.header(
http::header::SEC_WEBSOCKET_ACCEPT,
sign(self.as_mut().key.take().unwrap().as_bytes()),
)
.body(Empty::new())
.unwrap();
Poll::Ready(Ok(res))
}
}
fn header_eq<B>(req: &Request<B>, key: HeaderName, value: HeaderValue) -> bool {
let header = if let Some(x) = req.headers().get(&key) {
x
} else {
return false;
};
header == value
}
// from https://github.com/hyperium/headers/blob/master/src/common/sec_websocket_accept.rs#L38
fn sign(key: &[u8]) -> HeaderValue {
let mut sha1 = Sha1::default();
sha1.update(key);
sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]);
let b64 = Bytes::from(base64::encode(&sha1.finalize()));
HeaderValue::from_maybe_shared(b64).expect("base64 is a valid value")
}
#[derive(Debug)]
pub struct WebSocket {
inner: WebSocketStream<Upgraded>,
}
impl WebSocket {
pub async fn recv(&mut self) -> Option<Result<protocol::Message, BoxError>> {
self.inner.next().await.map(|opt| opt.map_err(Into::into))
}
}
// TODO(david): impl Stream<Message>
// TODO(david): WebSocket::close
Handlers and extractors should probably be generic over the request body type after all. Otherwise you wouldn't be able to use RequestBodyTimeout
.
Version
, Method
, and Uri
are all cheap to clone so having take_*
methods for them on RequestParts
probably isn't necessary. Could just have methods like
fn method(&self) -> Method {}
fn method_mut(&mut self) -> &mut Method {}
// same for version and uri...
That would also remove the Option
s making extractors that use those types easier to write. It might also remove the need for some the types in https://docs.rs/axum/0.1.2/axum/extract/rejection/index.html, namely things like MethodAlreadyExtracted
.
For HeaderMap
, Extensions
, and body we still take_*
methods as those aren't cheap to clone, or cannot be cloned at all.
I think we should do this for 0.2.
I think it makes sense of WebSocketHandler::call
to return Result<impl Future, impl IntoResponse>
. I can imagine setting some data via an extractor and doing some fallible thing. Currently its not possible to return an error if an error is encountered.
This is a breaking change so has to wait for 0.2.
The router has a few slightly surprising behaviors due to its design that should we clearly document:
route("/foo", get(handler)).route("/foo", post(handler))
only matches POST and not GET. This is #62Would be nice to provide an example of setting up a basic GraphQL server. Don't think it matters if the example uses async-graphql or juniper. I expect both implementations to be fairly similar.
MethodFilter
is currently somewhat restricted, it only supports singular methods as well as a catch-all variant. It would be nice if it were more flexible.
Note that I do not have a specific use case for this, I just thought it would be neat and feel more natural than an Any
enum variant.
Implement the type as bitflags. It will grow to be two instead of one bytes, but that's a minute cost.
Have a MethodFilter
trait implemented on Method as well as closures. This would support custom HTTP methods as well as more advanced filters, but I'm not sure how useful that is.
Axum already has UrlParams
and UrlParamsMap
for extracting data from request URIs. However I was recently made aware of actic_web::web::Path
which seems more powerful since it relies on serde::Deserialize
. I think that is something we should look into.
All steps to reproduce the problem (including the cargo output):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://github.com/tokio-rs/axum.git
cd axum/
cargo build --example hello_world
Updating crates.io index
Downloaded askama_escape v0.10.1
Downloaded askama_shared v0.11.1
Downloaded bb8 v0.7.0
Downloaded flate2 v1.0.20
Downloaded md-5 v0.9.1
Downloaded digest v0.9.0
Downloaded miniz_oxide v0.4.4
Downloaded adler v1.0.2
Downloaded phf v0.8.0
Downloaded num-integer v0.1.44
Downloaded futures-macro v0.3.16
Downloaded futures-channel v0.3.16
Downloaded lock_api v0.4.4
Downloaded async-trait v0.1.51
Downloaded async-compression v0.3.8
Downloaded static_assertions v1.1.0
Downloaded num-traits v0.2.14
Downloaded askama v0.10.5
Downloaded ansi_term v0.12.1
Downloaded alloc-no-stdlib v2.0.1
Downloaded futures-sink v0.3.16
Downloaded foreign-types-shared v0.1.1
Downloaded alloc-stdlib v0.2.1
Downloaded nom v6.1.2
Downloaded arrayvec v0.5.2
Downloaded bb8-postgres v0.7.0
Downloaded foreign-types v0.3.2
Downloaded funty v1.1.0
Downloaded futures v0.3.16
Downloaded matchers v0.0.1
Downloaded futures-core v0.3.16
Downloaded proc-macro2 v1.0.28
Downloaded radium v0.5.3
Downloaded postgres-types v0.2.1
Downloaded socket2 v0.4.1
Downloaded memchr v2.4.0
Downloaded autocfg v1.0.1
Downloaded parking_lot v0.11.1
Downloaded indexmap v1.7.0
Downloaded rand_core v0.6.3
Downloaded futures-io v0.3.16
Downloaded regex-automata v0.1.10
Downloaded futures-executor v0.3.16
Downloaded regex v1.5.4
Downloaded aho-corasick v0.7.18
Downloaded native-tls v0.2.7
Downloaded fallible-iterator v0.2.0
Downloaded ppv-lite86 v0.2.10
Downloaded tracing-subscriber v0.2.19
Downloaded phf_shared v0.8.0
Downloaded openssl-probe v0.1.4
Downloaded mime_guess v2.0.3
Downloaded cc v1.0.69
Downloaded form_urlencoded v1.0.1
Downloaded humansize v1.1.1
Downloaded futures-util v0.3.16
Downloaded serde v1.0.126
Downloaded scopeguard v1.1.0
Downloaded cpufeatures v0.1.5
Downloaded matches v0.1.8
Downloaded pin-project-internal v1.0.8
Downloaded hyper-tls v0.5.0
Downloaded fnv v1.0.7
Downloaded generic-array v0.14.4
Downloaded pin-project v1.0.8
Downloaded ipnet v2.3.1
Downloaded tokio-postgres v0.7.2
Downloaded http-body v0.4.2
Downloaded getrandom v0.2.3
Downloaded opaque-debug v0.3.0
Downloaded stringprep v0.1.2
Downloaded crc32fast v1.2.1
Downloaded ryu v1.0.5
Downloaded futures-task v0.3.16
Downloaded h2 v0.3.3
Downloaded postgres-protocol v0.6.1
Downloaded unicode-normalization v0.1.19
Downloaded thread_local v1.1.3
Downloaded subtle v2.4.1
Downloaded wyz v0.2.0
Downloaded sha2 v0.9.5
Downloaded log v0.4.14
Downloaded hmac v0.10.1
Downloaded openssl v0.10.35
Downloaded httparse v1.4.1
Downloaded mio v0.7.13
Downloaded typenum v1.13.0
Downloaded slab v0.4.3
Downloaded tokio-util v0.6.7
Downloaded unicode-bidi v0.3.5
Downloaded tracing-attributes v0.1.15
Downloaded uuid v0.8.2
Downloaded sharded-slab v0.1.1
Downloaded http v0.2.4
Downloaded url v2.2.2
Downloaded siphasher v0.3.6
Downloaded once_cell v1.8.0
Downloaded tinyvec_macros v0.1.0
Downloaded quote v1.0.9
Downloaded num_cpus v1.13.0
Downloaded lazy_static v1.4.0
Downloaded hashbrown v0.11.2
Downloaded tap v1.0.1
Downloaded mime v0.3.16
Downloaded reqwest v0.11.4
Downloaded serde_json v1.0.66
Downloaded chrono v0.4.19
Downloaded bytes v1.0.1
Downloaded cfg-if v1.0.0
Downloaded tokio-macros v1.3.0
Downloaded brotli-decompressor v2.3.1
Downloaded tracing v0.1.26
Downloaded toml v0.5.8
Downloaded serde_urlencoded v0.7.0
Downloaded unicase v2.6.0
Downloaded block-buffer v0.9.0
Downloaded crypto-mac v0.10.1
Downloaded tower-service v0.3.1
Downloaded try-lock v0.2.3
Downloaded tokio-native-tls v0.3.0
Downloaded tower-http v0.1.1
Downloaded hyper v0.14.11
Downloaded tinyvec v1.3.1
Downloaded smallvec v1.6.1
Downloaded httpdate v1.0.1
Downloaded tracing-serde v0.1.2
Downloaded tracing-log v0.1.2
Downloaded byteorder v1.4.3
Downloaded bitvec v0.19.5
Downloaded version_check v0.9.3
Downloaded tracing-core v0.1.18
Downloaded bitflags v1.2.1
Downloaded tower v0.4.8
Downloaded unicode-xid v0.2.2
Downloaded want v0.3.0
Downloaded regex-syntax v0.6.25
Downloaded parking_lot_core v0.8.3
Downloaded itoa v0.4.7
Downloaded syn v1.0.74
Downloaded pkg-config v0.3.19
Downloaded tokio v1.9.0
Downloaded tower-layer v0.3.1
Downloaded iri-string v0.4.0
Downloaded idna v0.2.3
Downloaded openssl-sys v0.9.65
Downloaded instant v0.1.10
Downloaded rand_chacha v0.3.1
Downloaded proc-macro-hack v0.5.19
Downloaded percent-encoding v2.1.0
Downloaded base64 v0.13.0
Downloaded serde_derive v1.0.126
Downloaded proc-macro-nested v0.1.7
Downloaded pin-utils v0.1.0
Downloaded pin-project-lite v0.2.7
Downloaded rand v0.8.4
Downloaded lexical-core v0.7.6
Downloaded askama_derive v0.10.5
Downloaded brotli v3.3.0
Downloaded libc v0.2.98
Downloaded encoding_rs v0.8.28
Downloaded 160 crates (10.9 MB) in 3.86s (largest was `brotli` at 1.4 MB)
Compiling autocfg v1.0.1
Compiling cfg-if v1.0.0
Compiling proc-macro2 v1.0.28
Compiling unicode-xid v0.2.2
Compiling syn v1.0.74
Compiling libc v0.2.98
Compiling memchr v2.4.0
Compiling version_check v0.9.3
Compiling log v0.4.14
Compiling pin-project-lite v0.2.7
Compiling bytes v1.0.1
Compiling futures-core v0.3.16
Compiling smallvec v1.6.1
Compiling scopeguard v1.1.0
Compiling proc-macro-hack v0.5.19
Compiling futures-sink v0.3.16
Compiling proc-macro-nested v0.1.7
Compiling bitflags v1.2.1
Compiling futures-task v0.3.16
Compiling futures-channel v0.3.16
Compiling serde_derive v1.0.126
Compiling ryu v1.0.5
Compiling pin-utils v0.1.0
Compiling slab v0.4.3
Compiling typenum v1.13.0
Compiling lazy_static v1.4.0
Compiling serde v1.0.126
Compiling futures-io v0.3.16
Compiling matches v0.1.8
Compiling itoa v0.4.7
Compiling pkg-config v0.3.19
Compiling radium v0.5.3
Compiling cc v1.0.69
Compiling tinyvec_macros v0.1.0
Compiling percent-encoding v2.1.0
Compiling lexical-core v0.7.6
Compiling fnv v1.0.7
Compiling tap v1.0.1
Compiling static_assertions v1.1.0
Compiling funty v1.1.0
Compiling arrayvec v0.5.2
Compiling wyz v0.2.0
Compiling base64 v0.13.0
Compiling once_cell v1.8.0
Compiling ppv-lite86 v0.2.10
Compiling crc32fast v1.2.1
Compiling openssl v0.10.35
Compiling httparse v1.4.1
Compiling tower-service v0.3.1
Compiling hashbrown v0.11.2
Compiling alloc-no-stdlib v2.0.1
Compiling async-trait v0.1.51
Compiling subtle v2.4.1
Compiling foreign-types-shared v0.1.1
Compiling opaque-debug v0.3.0
Compiling adler v1.0.2
Compiling try-lock v0.2.3
Compiling serde_json v1.0.66
Compiling cpufeatures v0.1.5
Compiling regex-syntax v0.6.25
Compiling native-tls v0.2.7
Compiling openssl-probe v0.1.4
Compiling byteorder v1.4.3
Compiling fallible-iterator v0.2.0
Compiling mime v0.3.16
Compiling httpdate v1.0.1
Compiling siphasher v0.3.6
Compiling humansize v1.1.1
Compiling askama_escape v0.10.1
Compiling tower-layer v0.3.1
Compiling encoding_rs v0.8.28
Compiling ipnet v2.3.1
Compiling ansi_term v0.12.1
Compiling instant v0.1.10
Compiling lock_api v0.4.4
Compiling generic-array v0.14.4
Compiling nom v6.1.2
Compiling unicase v2.6.0
Compiling tokio v1.9.0
Compiling futures-macro v0.3.16
Compiling futures-util v0.3.16
Compiling num-traits v0.2.14
Compiling indexmap v1.7.0
Compiling miniz_oxide v0.4.4
Compiling num-integer v0.1.44
Compiling tracing-core v0.1.18
Compiling sharded-slab v0.1.1
Compiling unicode-bidi v0.3.5
Compiling tinyvec v1.3.1
Compiling http v0.2.4
Compiling form_urlencoded v1.0.1
Compiling thread_local v1.1.3
Compiling alloc-stdlib v0.2.1
Compiling foreign-types v0.3.2
Compiling phf_shared v0.8.0
Compiling openssl-sys v0.9.65
Compiling brotli-decompressor v2.3.1
Compiling phf v0.8.0
Compiling want v0.3.0
Compiling tracing-log v0.1.2
Compiling aho-corasick v0.7.18
Compiling unicode-normalization v0.1.19
Compiling quote v1.0.9
Compiling parking_lot_core v0.8.3
Compiling num_cpus v1.13.0
Compiling mio v0.7.13
Compiling getrandom v0.2.3
Compiling socket2 v0.4.1
Compiling bitvec v0.19.5
Compiling regex-automata v0.1.10
error: failed to run custom build command for `openssl-sys v0.9.65`
Caused by:
process didn't exit successfully: `~/axum/target/debug/build/openssl-sys-95bfa9550bf83440/build-script-main` (exit status: 101)
--- stdout
cargo:rustc-cfg=const_fn
cargo:rerun-if-env-changed=X86_64_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR
X86_64_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR unset
cargo:rerun-if-env-changed=OPENSSL_LIB_DIR
OPENSSL_LIB_DIR unset
cargo:rerun-if-env-changed=X86_64_UNKNOWN_LINUX_GNU_OPENSSL_INCLUDE_DIR
X86_64_UNKNOWN_LINUX_GNU_OPENSSL_INCLUDE_DIR unset
cargo:rerun-if-env-changed=OPENSSL_INCLUDE_DIR
OPENSSL_INCLUDE_DIR unset
cargo:rerun-if-env-changed=X86_64_UNKNOWN_LINUX_GNU_OPENSSL_DIR
X86_64_UNKNOWN_LINUX_GNU_OPENSSL_DIR unset
cargo:rerun-if-env-changed=OPENSSL_DIR
OPENSSL_DIR unset
cargo:rerun-if-env-changed=OPENSSL_NO_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG
cargo:rerun-if-env-changed=OPENSSL_STATIC
cargo:rerun-if-env-changed=OPENSSL_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_STATIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64_unknown_linux_gnu
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_PATH
cargo:rerun-if-env-changed=PKG_CONFIG_PATH
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64_unknown_linux_gnu
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_LIBDIR
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64_unknown_linux_gnu
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
run pkg_config fail: "`\"pkg-config\" \"--libs\" \"--cflags\" \"openssl\"` did not exit successfully: exit status: 1\n--- stderr\nPackage openssl was not found in the pkg-config search path.\nPerhaps you should add the directory containing `openssl.pc'\nto the PKG_CONFIG_PATH environment variable\nNo package 'openssl' found\n"
--- stderr
thread 'main' panicked at '
Could not find directory of OpenSSL installation, and this `-sys` crate cannot
proceed without this knowledge. If OpenSSL is installed and this crate had
trouble finding it, you can set the `OPENSSL_DIR` environment variable for the
compilation process.
Make sure you also have the development packages of openssl installed.
For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.
If you're in a situation where you think the directory *should* be found
automatically, please open a bug at https://github.com/sfackler/rust-openssl
and include information about your system as well as this message.
$HOST = x86_64-unknown-linux-gnu
$TARGET = x86_64-unknown-linux-gnu
openssl-sys = 0.9.65
'~/.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.65/build/find_normal.rs:174:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...
error: build failed
My system:
lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal
Our router is very similar to tonic's router. Might be possible to replace tonic's implementation. Something to explore.
I would expect the responses from these two routes to be identical:
route(
"/one",
get(|| async { (HeaderMap::new(), "Hello, World!") }),
)
.route(
"/two",
get(|| async { "Hello, World!" }),
);
But since impl<T: IntoResponse> IntoResponse for (HeaderMap, T)
replaces all the headers we loose the content-type
header set by impl IntoResponse for &'static str
.
(HeaderMap, T)
should instead merge the headers into the response.
0.1.1
Only last method is accepted when same route path with different methods, other methods returns 404.
I tried this code:
use axum::prelude::*;
use hyper::server::Server;
async fn index() -> &'static str {
"Hi"
}
#[tokio::main]
async fn main() {
let app = route("/", get(index)).route("/", post(index));
// run it with hyper on localhost:3000
Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
I expected to see this happen: request / with get method returns 404.
Instead, this happened: request / with get method returns "hi".
We need enhance documentation for the examples. There are a lot of dependencies. Its hard to know what is needed to compile, and makes hard to setup a new project as we dont know from what crates this functions come.
axum = { version = "0.1.2", optional = true }
http = { version = "0.2.4", optional = true }
hyper = { version = "0.14.11", optional = true, features = ["full"] }
tower = { version = "0.4.8", optional = true }
Darwin Kernel Version 20.5.0: Sat May 8 05:10:33 PDT 2021;
root:xnu-7195.121.3~9/RELEASE_X86_64 x86_64 i386
The axum
crate can't be use.
I include axum
in my project and try to run cargo update
:
error: failed to select a version for `memchr`.
... required by package `nom v6.2.1`
... which is depended on by `xxx v0.3.0`
versions that meet the requirements `>=2.0, <2.4` are: 2.3.4, 2.3.3, 2.3.2, 2.3.0, 2.2.1, 2.2.0, 2.1.3, 2.1.2, 2.1.1, 2.1.0, 2.0.2, 2.0.1, 2.0.0
all possible versions conflict with previously selected packages.
previously selected package `memchr v2.4.0`
... which is depended on by `aho-corasick v0.7.18`
... which is depended on by `regex v1.5.0`
... which is depended on by `axum v0.1.2`
... which is depended on by `xxx v0.3.0`
failed to select a version for `memchr` which could resolve this conflict
In my project, i have use nom:6.2.1
crate.
If you nest a service at "/"
like this:
let app = axum::routing::nest(
"/",
get(|method: Method, uri: Uri| async move { println!("{} {} called", method, uri) }),
);
and then call GET /foo/bar
the URI the handler receives is foo/bar
which is wrong. I should be /foo/bar
. Basically nest
should make sure the URI always has a leading slash.
It would be useful to add an example like this:
https://github.com/seanmonstar/warp/blob/master/examples/tls.rs
If the extract::*
types like Json
would implement std::ops::Deref
you could write
#[derive(Deserialize, Serialize)]
struct Request {
foo: u8,
}
async fn handler(req: Json<Request>) {
println!("{}", req.foo);
}
instead of
async fn handler(Json(req): Json<Request>) {
println!("{}", req.foo);
}
or
async fn handler(req: Json<Request>) {
let req = req.0;
println!("{}", req.foo);
}
which IMO is easier to read.
The disadvantage is that it introduces some "Magic" that might be difficult to understand for newer rust developers.
The bevy game engine can write ECS systems similar to how axum
's request handlers work (example) and there you can write e.g.
fn system(resource: Res<Foo>) {
println!("{}", resource.foo);
}
which works really well and leads to very readably systems.
Could have fallible methods for taking the body, headers, or whole request. Without requiring Body: Default
.
For SPAs its often useful to first check if the URI matches some static resource (like javascript or css) and if not fallback to calling a handler. The handler should receive the entire URI, regardless of what it is, so routing can be done client side.
Its might be possible to do such a setup with axum today using some combination of ServeDir
(from tower-http) and nest
but I wouldn't be very ergonomic. We should investigate ways to make it easier.
I'm not exactly sure what the best and most general solution is. Could be wildcard routes similarly to what tide has. I think looking into how tide handles setting up something like this and then learning from that is a good place to start.
Communicating with an axum
app from the browser is a popular use case and many browsers restrict cross-origin requests by default.
MDN CORS
CORS server flowchart
Related issue on OPTIONS
requests #44
Related issue on SPAs #87
Warp "wrapping filter"
Tide middleware
3rd party crate recommended for rocket
Add ergonomic support and docs for handling CORS preflight requests.
axum
already provides support for setting headers globally or specifically using middleware, however, I'm not sure of an ergonomic way to handle preflight requests. That's an OPTIONS
request preceding e.g. a POST
request to the same path.
An app could handle this for example by (pseudocode)
let app = route("/user", get(get_user).options(cors_fn));
Since routing precedence (I think?) limits the ability to put a path wildcard OPTIONS
at the root of the application, or some part of the application, the developer is required to chain options
to every path declaration.
I'm not ready to propose any specific changes or weigh in on alternatives yet as I'm a new user and this an issue I'm encountering.
Trying to generate docs while working on axum fails when cargo doc
isn't run with --all-features
or with --features
used to enable the features that the main readme has intradoc links to.
I think a reasonable way to fix this would be removing deny(broken_intra_doc_links)
from lib.rs
and adjusting CI to pass -D broken_intra_doc_links
to rustdoc (not sure whether it has to be passed to cargo doc
directly, after --
, or rather via RUSTDOCFLAGS
).
Today, those features are so basic. Since all modern browsers implements them, what do you think about to enabled them as default?
#[derive(Deserialize)]
pub struct Pagination {
pub size: Option<u64>,
pub page: Option<u64>,
}
in such a case, the query string maybe empty.
For example I don't think there currently is an easy way to add auth to a ws endpoint. Being able to plug in any extractor would be cool.
An app like this wont compile:
let app =
// this route fails with Infallible
route("/foo", get(|| async {}))
// this route fails with hyper::Error
.route(
"/",
axum::service::get(tower::service_fn(|_: Request<Body>| async {
Ok::<_, hyper::Error>(Response::new(Body::empty()))
})),
);
Because the two error types don't match:
error[E0271]: type mismatch resolving `<Route<axum::handler::OnMethod<axum::handler::IntoService<[closure@examples/foo:10:33: 10:44], hyper::Body, ()>, EmptyRouter>, EmptyRouter> as Service<http::Request<hyper::Body>>>::Error == hyper::Error`
--> examples/foo:20:10
|
20 | .serve(app.into_make_service())
| ^^^^^ expected enum `Infallible`, found struct `hyper::Error`
The solution currently is to use .handle_error
and convert the tower::service_fn
s error type to Infallible
. However that might not be what you want. Or at the very least we should investigate if things could be changed to "just" work.
I believe the error happens because of this trait bound which requires the two services to have the same error types. Nested
suffers from the same issue.
If you are wring a RESTful API service, the OpenAPI/Swagger documentation is very useful. It would be great if axum
is able to generate them automatically.
Currently there is axum::extract::Json
and axum::response::Json
, this introduces naming conflicts if handler accepts Json and returns Json.
Actix have actix_web::web::Json
which can do at the same time.
When setting up CI for axum I just copied the setup from Tower thinking that would be fine. However I didn't notice it included an MSRV of 1.40 which is tested on CI.
axum requires at least Rust 1.51 due the use of const generics in ContentLengthLimit
. So it seems the 1.40 step on CI isn't actually running, since it should be failing ๐ค
We should fix CI so it actually does perform the checks and bump our MSRV to 1.51. If it turns out we use a feature not available on 1.51 I would be fine with requiring an even newer version, but it depends on the exact case.
Path routing is currently handled quite cleanly in Axum: there is a module, routing
, which contains all the methods related to path routing requests. However, method routing is a lot more messy: there are two nearly-identical versions of each utility split across two seemingly unrelated modules (handler
and service
). The service
module is especially odd because it contains service utilities and some concrete services, but doesn't contain any of the other concrete services that this crate also contains (contrary to the module's description), which is confusing.
Create a new module, method
, that is completely dedicated to method routing. This method will contain any
, connect
, delete
, on
, OnMethod
, etc from axum::service
, but will also provide any_to
, connect_to
, delete_to
, etc which will take in a Handler
for the same ergonomics that the handler
module used to have. MethodFilter
can also be placed inside axum::method
. It was quite out of place in axum::routing
anyway where the rest of the functions were about path routing.
The existing handler
and service
modules should be stripped of all their method-related functions, since that is now provided by method
. The module description of axum::service
will become the much more specific and accurate "Utilities for working with Service
s". routing
could also be renamed to route
, for the consistency of using nouns for everything.
Move all the method-handling functions to axum::routing
, and not have a method
module. I would be totally OK with this. It has the advantage of not needing to move MethodFilter
or rename routing
, but the module could end up a bit large.
I'm playing with axum and I noticed that there doesn't seem to be any way to extract the remote address (or even just IP) of the other end of the connection. As far as I can tell there's no way to do this anywhere in Tower; tower
's example logging middleware doesn't even log the remote address.
Any of the following perhaps:
SocketAddr
of the peerRequest
with the peer informationhyper::server::AddrStream
?Uh, I guess put another HTTP server in front of axum
that copies the remote address into a header?
handles HEAD requests automatically when there exists a GET route that would otherwise match. It does this by stripping the body from the response, if there is one. You can also specialize the handling of a HEAD request by declaring a route for it; won't interfere with HEAD requests your application explicitly handles.
Just like rocket did.
Should be 500 not 200.
This would be more in line with Body
and Query
, and be shorter (I think for some apps this type name will be written down a lot).
Rename UrlParams
to Path
. Not sure about UrlParamsMap
, I guess PathMap
isn't too bad.
FromRequest
is currently requires from_request
to return Result<Self, _>
.
This means when you implement it for something like extract::Extension
you
must return an extract::Extension
value. That requires users to pattern match
to pull out the inner value. That can get a bit tedious, especially when using
nested extractors like ContentLengthLimit
.
If we added an associated type Output
to FromRequest
it would allow
extractors to return values other than Self
and you wouldn't need to pattern
match:
#[async_trait]
pub trait FromRequest<B> {
type Output;
type Rejection: IntoResponse;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self::Output, Self::Rejection>;
}
pub struct Extension<T>(PhantomData<T>);
#[async_trait]
impl<T, B> FromRequest<B> for Extension<T> {
type Output = T;
type Rejection = /* ... */;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self::Output, Self::Rejection> {
// ...
}
}
async fn handler(value: Extension<SomeValue>) {
// `value` is now `SomeValue`
// no need for matching with `Extension(value): Extension<SomeValue>`
}
Would be a breaking change so something to consider for 0.2
Keep the current API as it works just fine ๐คท
Being able to merge routes/apps like this would be really cool
let app = route("/foo", get(|| async {}));
let other_route = route("/bar", get(|| async {}));
// this accepts `GET /foo` and `GET /bar`
let merged = app.or(other_route);
Things to consider:
Route
like BoxRoute
, Nested
, basically anything that implements RouteDsl
.As of now, to test a REST endpoint that returns a JSON object, two steps are needed; the first one, to get the body of the response using hyper, and the second one to convert it to JSON with serde:
#[tokio::test]
async fn json() {
let app = app();
let response = app.oneshot(Request::builder()...).await.unwrap();
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body: Value = serde_json::from_slice(&body).unwrap();
assert_eq!(body, json!({ "data": [1, 2, 3, 4] }));
}
It would be great to have a single fn that does all the job, so one can write, for example:
#[tokio::test]
async fn json() {
let app = app();
let response = app.oneshot(Request::builder()...).await.unwrap();
let body: Value = test::read_response_json(response).unwrap();
assert_eq!(body, json!({ "data": [1, 2, 3, 4] }));
}
though this would require, probably, the creation of a test
module
Currently handling websockets works quite differently from handling other requests. This has a few downsides:
handler/mod.rs
.Change the websocket API to instead use a regular handler and a special IntoResponse
. Something like
// build our application with a route
let app = route("/", get(handler));
async fn ws_handler(
// this extractor checks for the required headers
ws: extract::WebSocketUpgrade,
// you can put other extractors here
) -> impl IntoResponse {
// here you can do whatever you want to prepare for handling the connection
// possibly return an error and rejection the connection all together
// actually upgrade the connection and call the async closure with
// the websocket connection when we have it
ws.on_upgrade(|socket: WebSocket| async {
// do stuff...
})
}
This is similar to how things work in actix.
This solution was suggest by gbaranski#5119
on Discord.
Leads to slightly more code in the basic case where you don't wanna apply any extractors and just wanna start a ws connection without rejections. I think that tradeoff is worth it though.
The following test returns HTTP status 500:
#[tokio::test]
async fn test_extractors() {
let app = route("/api",
post(|req: Request<Body>, payload: extract::Json<String>| async move {
println!("CALL RECEIVED");
}));
// Act
let response = app
.oneshot(Request::builder()
.method(http::Method::POST)
.header(http::header::CONTENT_TYPE, "application/json")
.uri("/api")
.body(Body::from(
serde_json::to_vec(&"payload").unwrap(),
)).unwrap())
.await
.unwrap();
// Assert
assert_eq!(response.status(), StatusCode::OK);
}
The issue is caused by the fact that the Request and the Json extractors are used at the same time.
I tried to replace the Request
with extract::RequestParts
, but, by doing that, the compiler complains that
the trait bound `[closure@....]: axum::handler::Handler<_, _>` is not satisfied.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.