Skip to content

Commit

Permalink
Add tokio feature & make tokio optional for WASM support (#1382)
Browse files Browse the repository at this point in the history
* add server feature and make tokio and hyper/server and tcp optional

* address review comments

* don't mention any specific runtimes in the example

* sort deps

* add `tokio` feature when adding `ws`

* don't always pull in tower feature that pulls in tokio io stuff

* remove usage of `tokio_cr`

* changelog

* depend on tokio version that supports wasm

* don't make it sound like tokio doesn't support wasm

* call out new default feature

Co-authored-by: Fisher Darling <fdarlingco@gmail.com>
Co-authored-by: David Pedersen <david.pdrsn@gmail.com>
  • Loading branch information
3 people committed Sep 25, 2022
1 parent 83ba8c3 commit 31638a2
Show file tree
Hide file tree
Showing 14 changed files with 132 additions and 14 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/CI.yml
Expand Up @@ -209,6 +209,26 @@ jobs:
-p axum-macros
--target armv5te-unknown-linux-musleabi
wasm32-unknown-unknown:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
override: true
profile: minimal
- uses: Swatinem/rust-cache@v1
- name: Check
uses: actions-rs/cargo@v1
with:
command: check
args: >
--manifest-path ./examples/simple-router-wasm/Cargo.toml
--target wasm32-unknown-unknown
dependencies-are-sorted:
runs-on: ubuntu-latest
strategy:
Expand Down
6 changes: 6 additions & 0 deletions axum/CHANGELOG.md
Expand Up @@ -33,9 +33,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
can be nested or merged into a `Router` with the same state type ([#1368])
- **changed:** `Router::nest` now only accepts `Router`s, the general-purpose
`Service` nesting method has been renamed to `nest_service` ([#1368])
- **added:** Support compiling to WASM. See the `simple-router-wasm` example
for more details ([#1382])
- **breaking:** New `tokio` default feature needed for WASM support. If you
don't need WASM support but have `default_features = false` for other reasons
you likely need to re-enable the `tokio` feature ([#1382])

[#1368]: https://github.com/tokio-rs/axum/pull/1368
[#1371]: https://github.com/tokio-rs/axum/pull/1371
[#1382]: https://github.com/tokio-rs/axum/pull/1382
[#1387]: https://github.com/tokio-rs/axum/pull/1387
[#1389]: https://github.com/tokio-rs/axum/pull/1389
[#1396]: https://github.com/tokio-rs/axum/pull/1396
Expand Down
13 changes: 7 additions & 6 deletions axum/Cargo.toml
Expand Up @@ -12,7 +12,7 @@ readme = "README.md"
repository = "https://github.com/tokio-rs/axum"

[features]
default = ["form", "http1", "json", "matched-path", "original-uri", "query", "tower-log"]
default = ["form", "http1", "json", "matched-path", "original-uri", "query", "tokio", "tower-log"]
form = ["dep:serde_urlencoded"]
http1 = ["hyper/http1"]
http2 = ["hyper/http2"]
Expand All @@ -22,8 +22,9 @@ matched-path = []
multipart = ["dep:multer"]
original-uri = []
query = ["dep:serde_urlencoded"]
tokio = ["dep:tokio", "hyper/server", "hyper/tcp", "tower/make"]
tower-log = ["tower/log"]
ws = ["dep:tokio-tungstenite", "dep:sha-1", "dep:base64"]
ws = ["tokio", "dep:tokio-tungstenite", "dep:sha-1", "dep:base64"]

# Required for intra-doc links to resolve correctly
__private_docs = ["tower/full", "tower-http/full"]
Expand All @@ -36,7 +37,7 @@ bytes = "1.0"
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
http = "0.2.5"
http-body = "0.4.4"
hyper = { version = "0.14.14", features = ["server", "tcp", "stream"] }
hyper = { version = "0.14.14", features = ["stream"] }
itoa = "1.0.1"
matchit = "0.6"
memchr = "2.4.1"
Expand All @@ -45,8 +46,7 @@ percent-encoding = "2.1"
pin-project-lite = "0.2.7"
serde = "1.0"
sync_wrapper = "0.1.1"
tokio = { version = "1", features = ["time"] }
tower = { version = "0.4.13", default-features = false, features = ["util", "make"] }
tower = { version = "0.4.13", default-features = false, features = ["util"] }
tower-http = { version = "0.3.0", features = ["util", "map-response-body"] }
tower-layer = "0.3"
tower-service = "0.3"
Expand All @@ -60,6 +60,7 @@ serde_json = { version = "1.0", features = ["raw_value"], optional = true }
serde_path_to_error = { version = "0.1.8", optional = true }
serde_urlencoded = { version = "0.7", optional = true }
sha-1 = { version = "0.10", optional = true }
tokio = { package = "tokio", version = "1.21", features = ["time"], optional = true }
tokio-tungstenite = { version = "0.17.2", optional = true }

[dev-dependencies]
Expand All @@ -70,7 +71,7 @@ quickcheck_macros = "1.0"
reqwest = { version = "0.11.11", default-features = false, features = ["json", "stream", "multipart"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.6.1", features = ["macros", "rt", "rt-multi-thread", "net", "test-util"] }
tokio = { package = "tokio", version = "1.21", features = ["macros", "rt", "rt-multi-thread", "net", "test-util"] }
tokio-stream = "0.1"
tracing = "0.1"
uuid = { version = "1.0", features = ["serde", "v4"] }
Expand Down
6 changes: 5 additions & 1 deletion axum/src/ext_traits/service.rs
@@ -1,4 +1,6 @@
use crate::{extract::connect_info::IntoMakeServiceWithConnectInfo, routing::IntoMakeService};
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::routing::IntoMakeService;
use tower_service::Service;

/// Extension trait that adds additional methods to any [`Service`].
Expand Down Expand Up @@ -26,6 +28,7 @@ pub trait ServiceExt<R>: Service<R> + Sized {
/// ["Rewriting request URI in middleware"]: crate::middleware#rewriting-request-uri-in-middleware
/// [`Router`]: crate::Router
/// [`ConnectInfo`]: crate::extract::connect_info::ConnectInfo
#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C>;
}

Expand All @@ -37,6 +40,7 @@ where
IntoMakeService::new(self)
}

#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
IntoMakeServiceWithConnectInfo::new(self)
}
Expand Down
6 changes: 5 additions & 1 deletion axum/src/extract/mod.rs
Expand Up @@ -2,6 +2,7 @@

use http::header::{self, HeaderMap};

#[cfg(feature = "tokio")]
pub mod connect_info;
pub mod path;
pub mod rejection;
Expand All @@ -23,14 +24,17 @@ pub use axum_macros::{FromRequest, FromRequestParts};
#[doc(inline)]
#[allow(deprecated)]
pub use self::{
connect_info::ConnectInfo,
host::Host,
path::Path,
raw_query::RawQuery,
request_parts::{BodyStream, RawBody},
state::State,
};

#[doc(inline)]
#[cfg(feature = "tokio")]
pub use self::connect_info::ConnectInfo;

#[doc(no_inline)]
#[cfg(feature = "json")]
pub use crate::Json;
Expand Down
2 changes: 1 addition & 1 deletion axum/src/extract/ws.rs
Expand Up @@ -72,7 +72,7 @@
//! If you need to read and write concurrently from a [`WebSocket`] you can use
//! [`StreamExt::split`]:
//!
//! ```
//! ```rust,no_run
//! use axum::{Error, extract::ws::{WebSocket, Message}};
//! use futures::{sink::SinkExt, stream::{StreamExt, SplitSink, SplitStream}};
//!
Expand Down
6 changes: 5 additions & 1 deletion axum/src/handler/mod.rs
Expand Up @@ -35,9 +35,11 @@
//!
#![doc = include_str!("../docs/debugging_handler_type_errors.md")]

#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::{
body::Body,
extract::{connect_info::IntoMakeServiceWithConnectInfo, FromRequest, FromRequestParts},
extract::{FromRequest, FromRequestParts},
response::{IntoResponse, Response},
routing::IntoMakeService,
};
Expand Down Expand Up @@ -318,6 +320,7 @@ pub trait HandlerWithoutStateExt<T, B>: Handler<T, (), B> {
/// See [`WithState::into_make_service_with_connect_info`] for more details.
///
/// [`MakeService`]: tower::make::MakeService
#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<IntoService<Self, T, (), B>, C>;
Expand All @@ -335,6 +338,7 @@ where
self.with_state(()).into_make_service()
}

#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<IntoService<Self, T, (), B>, C> {
Expand Down
5 changes: 4 additions & 1 deletion axum/src/handler/with_state.rs
@@ -1,5 +1,7 @@
use super::{Handler, IntoService};
use crate::{extract::connect_info::IntoMakeServiceWithConnectInfo, routing::IntoMakeService};
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::routing::IntoMakeService;
use http::Request;
use std::task::{Context, Poll};
use tower_service::Service;
Expand Down Expand Up @@ -95,6 +97,7 @@ impl<H, T, S, B> WithState<H, T, S, B> {
///
/// [`MakeService`]: tower::make::MakeService
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
#[cfg(feature = "tokio")]
pub fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<IntoService<H, T, S, B>, C> {
Expand Down
2 changes: 2 additions & 0 deletions axum/src/lib.rs
Expand Up @@ -350,6 +350,7 @@
//! `matched-path` | Enables capturing of every request's router path and the [`MatchedPath`] extractor | Yes
//! `multipart` | Enables parsing `multipart/form-data` requests with [`Multipart`] | No
//! `original-uri` | Enables capturing of every request's original URI and the [`OriginalUri`] extractor | Yes
//! `tokio` | Enables `tokio` as a dependency and `axum::Server`, `SSE` and `extract::connect_info` types. | Yes
//! `tower-log` | Enables `tower`'s `log` feature | Yes
//! `ws` | Enables WebSockets support via [`extract::ws`] | No
//! `form` | Enables the `Form` extractor | Yes
Expand Down Expand Up @@ -461,6 +462,7 @@ pub use async_trait::async_trait;
pub use headers;
#[doc(no_inline)]
pub use http;
#[cfg(feature = "tokio")]
#[doc(no_inline)]
pub use hyper::Server;

Expand Down
7 changes: 6 additions & 1 deletion axum/src/response/mod.rs
Expand Up @@ -5,6 +5,7 @@ use http::{header, HeaderValue};

mod redirect;

#[cfg(feature = "tokio")]
pub mod sse;

#[doc(no_inline)]
Expand All @@ -28,7 +29,11 @@ pub use axum_core::response::{
};

#[doc(inline)]
pub use self::{redirect::Redirect, sse::Sse};
pub use self::redirect::Redirect;

#[doc(inline)]
#[cfg(feature = "tokio")]
pub use sse::Sse;

/// An HTML response.
///
Expand Down
5 changes: 4 additions & 1 deletion axum/src/routing/method_routing.rs
@@ -1,10 +1,11 @@
//! Route to services and handlers based on HTTP methods.

use super::IntoMakeService;
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::{
body::{Body, Bytes, HttpBody},
error_handling::{HandleError, HandleErrorLayer},
extract::connect_info::IntoMakeServiceWithConnectInfo,
handler::{Handler, IntoServiceStateInExtension},
http::{Method, Request, StatusCode},
response::Response,
Expand Down Expand Up @@ -689,6 +690,7 @@ where
///
/// [`MakeService`]: tower::make::MakeService
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
#[cfg(feature = "tokio")]
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
IntoMakeServiceWithConnectInfo::new(self)
}
Expand Down Expand Up @@ -1186,6 +1188,7 @@ impl<S, B, E> WithState<S, B, E> {
/// See [`MethodRouter::into_make_service_with_connect_info`] for more details.
///
/// [`MakeService`]: tower::make::MakeService
#[cfg(feature = "tokio")]
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
IntoMakeServiceWithConnectInfo::new(self)
}
Expand Down
4 changes: 3 additions & 1 deletion axum/src/routing/mod.rs
@@ -1,9 +1,10 @@
//! Routing between [`Service`]s and handlers.

use self::not_found::NotFound;
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::{
body::{Body, HttpBody},
extract::connect_info::IntoMakeServiceWithConnectInfo,
handler::{BoxedHandler, Handler},
util::try_downcast,
Extension,
Expand Down Expand Up @@ -547,6 +548,7 @@ where
}

#[doc = include_str!("../docs/routing/into_make_service_with_connect_info.md")]
#[cfg(feature = "tokio")]
pub fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<RouterService<B>, C> {
Expand Down
13 changes: 13 additions & 0 deletions examples/simple-router-wasm/Cargo.toml
@@ -0,0 +1,13 @@
[package]
name = "example-simple-router-wasm"
version = "0.1.0"
edition = "2018"
publish = false

[dependencies]
# `default-features = false` to not depend on tokio features which don't support wasm
# you can still pull in tokio manually and only add features that tokio supports for wasm
axum = { path = "../../axum", default-features = false }
futures-executor = "0.3.21"
http = "0.2.7"
tower-service = "0.3.1"
51 changes: 51 additions & 0 deletions examples/simple-router-wasm/src/main.rs
@@ -0,0 +1,51 @@
//! Run with
//!
//! ```not_rust
//! cd examples && cargo run -p example-simple-router-wasm
//! ```
//!
//! This example shows what using axum in a wasm context might look like. This example should
//! always compile with `--target wasm32-unknown-unknown`.
//!
//! [`mio`](https://docs.rs/mio/latest/mio/index.html), tokio's IO layer, does not support the
//! `wasm32-unknown-unknown` target which is why this crate requires `default-features = false`
//! for axum.
//!
//! Most serverless runtimes expect an exported function that takes in a single request and returns
//! a single response, much like axum's `Handler` trait. In this example, the handler function is
//! `app` with `main` acting as the serverless runtime which originally receives the request and
//! calls the app function.
//!
//! We can use axum's routing, extractors, tower services, and everything else to implement
//! our serverless function, even though we are running axum in a wasm context.

use axum::{
response::{Html, Response},
routing::get,
Router,
};
use futures_executor::block_on;
use http::Request;
use tower_service::Service;

fn main() {
let request: Request<String> = Request::builder()
.uri("https://serverless.example/api/")
.body("Some Body Data".into())
.unwrap();

let response: Response = block_on(app(request));
assert_eq!(200, response.status());
}

#[allow(clippy::let_and_return)]
async fn app(request: Request<String>) -> Response {
let mut router = Router::new().route("/api/", get(index)).into_service();

let response = router.call(request).await.unwrap();
response
}

async fn index() -> Html<&'static str> {
Html("<h1>Hello, World!</h1>")
}

0 comments on commit 31638a2

Please sign in to comment.