Commit Graph
405 Commits
Author SHA1 Message Date
David Pedersen 7eb2c40b24 Add Router::route_layer (#474)
This addresses something thats been bothering me for some time: Most middleware need to run regardless if the request matches a route or not. For example you don't wanna skip logging for unmatched requests.

However middleware such as authorization only make sense to run for matching requests. This previously wasn't possible to express and you'd have to manually apply the middleware to each handler. Consider this:

```rust
Router::new()
    .route("/foo", get(|| async {}))
    .layer(RequireAuthorizationLayer::bearer("password"));
```

Calling `GET /foo` with an invalid token would receive `401 Unauthorized` as expected however calling some unknown route like `GET /not-found` would also return `401 Unauthorized`. I think this is unexpected and have seen a few users ask questions about it.

It happened because the 404 you'd otherwise see is generated by a fallback service stored on `Router`. When adding a layer to the router the layer would also be applied to the fallback, which in the case of auth means the fallback would never be called for unauthorized requests.

I think what axum does today is the right default however I still think we should support this somehow. Especially since [`extractor_middleware`](https://docs.rs/axum/0.3.1/axum/extract/fn.extractor_middleware.html) is mainly useful for auth but it doesn't work great today due to this gotcha.

This PR proposes adding `Router::layer_on_matching_route` which only applies layers to routes, not the fallback, which fixes the issue. I'm not a big fan of the name `layer_on_matching_route`, would like something shorter, but I think it communicates the purpose decently.

The generics are a bit different since the request body used on the routes and the fallback must match, so layers that changes the request body type are not compatible with `layer_on_matching_route`. Such middleware are very rare so that should be fine.
2021-11-08 18:48:02 +01:00
David Pedersen 5d8ebce211 Add axum-flash to list of community maintained crates 2021-11-07 17:17:13 +01:00
Kelly Thomas Kline b26fd03810 Add recommendation (#476) 2021-11-07 07:54:53 +00:00
EdorianDark 513c5a694d Update tracing-subscriber to 0.3 (#472)
Fixes #469
2021-11-06 14:50:23 +00:00
David Pedersen 09bcd8862d Version 0.3.1 (#473)
- **fixed:** Implement `Clone` for `IntoMakeServiceWithConnectInfo` ([#471])

[#471]: https://github.com/tokio-rs/axum/pull/471
v0.3.1 axum-v0.3.1
2021-11-06 13:26:23 +00:00
David Pedersen b3b377d467 Implement Clone for IntoMakeServiceWithConnectInfo (#471)
Fixes https://github.com/tokio-rs/axum/issues/470
2021-11-06 13:06:20 +00:00
Jonas Platte b4b022da5f Add turbo.fish to community showcase (#466) 2021-11-05 13:23:53 +01:00
Eray Karatay 394ba31385 update low-level-rustls example (#465) 2021-11-05 12:02:58 +01:00
Frederik-Baetens 07641450c5 Fix links in readme (#464) 2021-11-04 19:57:05 +01:00
David Pedersen 0be0ea6763 Actually run the routing tests 🤦 2021-11-04 12:48:37 +01:00
david-perez a47d7eca40 Derive Debug for Router instead of implementing manually (#463) 2021-11-04 12:35:50 +01:00
david-perez 071a1b26e6 Fix typos (#461) 2021-11-03 19:03:46 +00:00
David Pedersen ba4d8a2357 Move axum crate into workspace subfolder (#458)
* Move axum crate into workspace subfolder

Over time I imagine we're gonna have other crates in this repo that
provide utilities or integrations for axum. This prepares for that by
moving the main axum crate into its own folder.

The README situation is a bit annoying because we want `./README.md`
for viewing the repo on github but `axum/README.md` for crates.io. For
now I've just copy/pasted it and added CI step to make sure they're
identical.

* update changelog link

* Add licenses to all examples

* is this how you install `diff`?

* or maybe this is how?

* fix readme links

* like this?

* fix cargo-deny step

* Try making root readme a symlink

* remove compare readme step

not needed since readme in repo root is now a symlink

* Revert "Add licenses to all examples"

This reverts commit ab321b7fb9.
2021-11-03 12:38:48 +01:00
David Pedersen b60bfd7f34 Mention "axum-typed-websockets" in community ecosystem 2021-11-03 10:26:20 +01:00
David Pedersen 0f1f28062d Reorganize tests (#456)
* Reorganize tests

This breaks up the large `crate::tests` module by moving some of the
tests into a place that makes more sense. For example tests of JSON
serialization are moved to the `crate::json` module. The remaining routing
tests have been moved to `crate::routing::tests`.

I generally prefer having tests close to the code they're testing. Makes
it easier to see how/if something is tested.

* Try pinning to older version of async-graphql

* Revert "Try pinning to older version of async-graphql"

This reverts commit 2e2cae7d12f5e433a16d6607497d587863f04384.

* don't test examples on 1.54 on CI

* move ci steps around a bit
2021-11-03 10:22:31 +01:00
David Pedersen 420918a53a Clarify change to handling multiple HTTP methods 2021-11-03 09:58:59 +01:00
arctic-alpaca a442920ba3 Restructure community showcase (#454) 2021-11-03 09:20:01 +01:00
David Pedersen 5367bc65d1 Fix typo 2021-11-03 08:26:13 +01:00
David Pedersen 708988bfae Fix readme example link 2021-11-03 08:25:31 +01:00
1c60255b9f Add readme example project (#450)
* Add project based on the readme example

* Add readme project link to README.md

* Typo correction

* Update examples/readme-example/Cargo.toml

Use tracing-subscriber 0.2 to match other the other examples

Co-authored-by: David Pedersen <[email protected]>

* Update README.md

Use original readme phrasing for crate docs

Co-authored-by: David Pedersen <[email protected]>

* Rename readme-exmaple to readme

* Revert tracing call to debug from info

Co-authored-by: Jordan Gould <[email protected]>
Co-authored-by: David Pedersen <[email protected]>
2021-11-03 07:23:28 +00:00
Jonas Platte 2ba9cdce45 Fix typos in CHANGELOG.md (#449) 2021-11-02 20:12:27 +00:00
David Pedersen baefa47634 0.3.0 is out! 2021-11-02 18:41:29 +01:00
David Pedersen 35dba50b32 Fix typo in changelog 2021-11-02 18:40:57 +01:00
David Pedersen 85731a4f1d Version 0.3.0 (#448)
- Overall:
  - **fixed:** All known compile time issues are resolved, including those with
    `boxed` and those introduced by Rust 1.56 ([#404])
  - **breaking:** The router's type is now always `Router` regardless of how many routes or
    middleware are applied ([#404])

    This means router types are all always nameable:

    ```rust
    fn my_routes() -> Router {
        Router::new().route(
            "/users",
            post(|| async { "Hello, World!" }),
        )
    }
    ```
  - **breaking:** Added feature flags for HTTP1 and JSON. This enables removing a
    few dependencies if your app only uses HTTP2 or doesn't use JSON. Its only a
    breaking change if you depend on axum with `default_features = false`. ([#286])
  - **breaking:** `Route::boxed` and `BoxRoute` have been removed as they're no longer
    necessary ([#404])
  - **breaking:** `Nested`, `Or` types are now private. They no longer had to be
    public because `Router` is internally boxed ([#404])
  - **breaking:** Remove `routing::Layered` as it didn't actually do anything and
    thus wasn't necessary
  - **breaking:** Vendor `AddExtensionLayer` and `AddExtension` to reduce public
    dependencies
  - **breaking:** `body::BoxBody` is now a type alias for
    `http_body::combinators::UnsyncBoxBody` and thus is no longer `Sync`. This
    is because bodies are streams and requiring streams to be `Sync` is
    unnecessary.
  - **added:** Implement `IntoResponse` for `http_body::combinators::UnsyncBoxBody`.
  - **added:** Add `Handler::into_make_service` for serving a handler without a
    `Router`.
  - **added:** Add `Handler::into_make_service_with_connect_info` for serving a
    handler without a `Router`, and storing info about the incoming connection.
  - **breaking:** axum's minimum support rust version is not 1.54
- Routing:
  - Big internal refactoring of routing leading to several improvements ([#363])
    - **added:** Wildcard routes like `.route("/api/users/*rest", service)` are now supported.
    - **fixed:** The order routes are added in no longer matters.
    - **fixed:** Adding a conflicting route will now cause a panic instead of silently making
      a route unreachable.
    - **fixed:** Route matching is faster as number of routes increase.
  - **fixed:** Correctly handle trailing slashes in routes:
    - If a route with a trailing slash exists and a request without a trailing
      slash is received, axum will send a 301 redirection to the route with the
      trailing slash.
    - Or vice versa if a route without a trailing slash exists and a request
      with a trailing slash is received.
    - This can be overridden by explicitly defining two routes: One with and one
      without trailing a slash.
  - **breaking:** Method routing for handlers have been moved from `axum::handler`
    to `axum::routing`. So `axum::handler::get` now lives at `axum::routing::get`
    ([#405])
  - **breaking:** Method routing for services have been moved from `axum::service`
    to `axum::routing`. So `axum::service::get` now lives at, etc.
    `axum::routing::service_method_routing::get`, etc. ([#405])
  - **breaking:** `Router::or` renamed to `Router::merge` and will now panic on
    overlapping routes. It now only accepts `Router`s and not general `Service`s.
    Use `Router::fallback` for adding fallback routes ([#408])
  - **added:** `Router::fallback` for adding handlers for request that didn't
    match any routes. `Router::fallback` must be use instead of `nest("/", _)` ([#408])
  - **breaking:** `EmptyRouter` has been renamed to `MethodNotAllowed` as its only
    used in method routers and not in path routers (`Router`)
  - **breaking:** Remove support for routing based on the `CONNECT` method. An
    example of combining axum with and HTTP proxy can be found [here][proxy] ([#428])
- Extractors:
  - **fixed:** Expand accepted content types for JSON requests ([#378])
  - **fixed:** Support deserializing `i128` and `u128` in `extract::Path`
  - **breaking:** Automatically do percent decoding in `extract::Path`
    ([#272])
  - **breaking:** Change `Connected::connect_info` to return `Self` and remove
    the associated type `ConnectInfo` ([#396])
  - **added:** Add `extract::MatchedPath` for accessing path in router that
    matched the request ([#412])
- Error handling:
  - **breaking:** Simplify error handling model ([#402]):
    - All services part of the router are now required to be infallible.
    - Error handling utilities have been moved to an `error_handling` module.
    - `Router::check_infallible` has been removed since routers are always
      infallible with the error handling changes.
    - Error handling closures must now handle all errors and thus always return
      something that implements `IntoResponse`.

    With these changes handling errors from fallible middleware is done like so:

    ```rust,no_run
    use axum::{
        routing::get,
        http::StatusCode,
        error_handling::HandleErrorLayer,
        response::IntoResponse,
        Router, BoxError,
    };
    use tower::ServiceBuilder;
    use std::time::Duration;

    let middleware_stack = ServiceBuilder::new()
        // Handle errors from middleware
        //
        // This middleware most be added above any fallible
        // ones if you're using `ServiceBuilder`, due to how ordering works
        .layer(HandleErrorLayer::new(handle_error))
        // Return an error after 30 seconds
        .timeout(Duration::from_secs(30));

    let app = Router::new()
        .route("/", get(|| async { /* ... */ }))
        .layer(middleware_stack);

    fn handle_error(_error: BoxError) -> impl IntoResponse {
        StatusCode::REQUEST_TIMEOUT
    }
    ```

    And handling errors from fallible leaf services is done like so:

    ```rust
    use axum::{
        Router, service,
        body::Body,
        routing::service_method_routing::get,
        response::IntoResponse,
        http::{Request, Response},
        error_handling::HandleErrorExt, // for `.handle_error`
    };
    use std::{io, convert::Infallible};
    use tower::service_fn;

    let app = Router::new()
        .route(
            "/",
            get(service_fn(|_req: Request<Body>| async {
                let contents = tokio::fs::read_to_string("some_file").await?;
                Ok::<_, io::Error>(Response::new(Body::from(contents)))
            }))
            .handle_error(handle_io_error),
        );

    fn handle_io_error(error: io::Error) -> impl IntoResponse {
        // ...
    }
    ```
- Misc:
  - `InvalidWebsocketVersionHeader` has been renamed to `InvalidWebSocketVersionHeader` ([#416])
  - `WebsocketKeyHeaderMissing` has been renamed to `WebSocketKeyHeaderMissing` ([#416])

[#339]: https://github.com/tokio-rs/axum/pull/339
[#286]: https://github.com/tokio-rs/axum/pull/286
[#272]: https://github.com/tokio-rs/axum/pull/272
[#378]: https://github.com/tokio-rs/axum/pull/378
[#363]: https://github.com/tokio-rs/axum/pull/363
[#396]: https://github.com/tokio-rs/axum/pull/396
[#402]: https://github.com/tokio-rs/axum/pull/402
[#404]: https://github.com/tokio-rs/axum/pull/404
[#405]: https://github.com/tokio-rs/axum/pull/405
[#408]: https://github.com/tokio-rs/axum/pull/408
[#412]: https://github.com/tokio-rs/axum/pull/412
[#416]: https://github.com/tokio-rs/axum/pull/416
[#428]: https://github.com/tokio-rs/axum/pull/428
[proxy]: https://github.com/tokio-rs/axum/blob/main/examples/http-proxy/src/main.rs
v0.3.0
2021-11-02 18:14:41 +01:00
David Pedersen 394d4b14f6 Mention handling extractor failures in error handling docs 2021-11-02 18:02:09 +01:00
David Pedersen 2957f48ace Mention MSRV bump in changelog 2021-11-02 17:58:21 +01:00
David Pedersen 6b69368895 Small changelog fixes 2021-11-02 17:55:42 +01:00
David Pedersen 9512d14c99 Add Handler::{into_make_service, into_make_service_with_connect_info} (#444)
* Make `IntoMakeService(WithConnectInfo)?` work with any Service

* Add `Handler::{into_make_service, into_make_service_with_connect_info}`

These are useful if you want to run a handler without a `Router`, for
example to make a proxy.
2021-11-02 17:11:18 +01:00
David Pedersen 98907b8887 Add example showing how to handle empty vs missing query params (#443) 2021-11-02 14:42:47 +01:00
David Pedersen a048d6443b Revert hello-world example
Was accidentally changed in another PR
2021-11-02 13:07:49 +01:00
Michael Grigoryan 5e44295a82 Fix typo in CHANGELOG (#442) 2021-11-02 07:50:10 +01:00
David Pedersen 1b98328dc8 Clean up some todos (#441) 2021-11-01 22:36:17 +00:00
David Pedersen 3d07d40e02 Remove Sync requirement from response bodies (#440)
As we learned [in Tonic] bodies don't need to be `Sync` because they can
only be polled from one thread at a time.

This changes axum's bodies to no longer require `Sync` and makes
`BoxBody` an alias for `UnsyncBoxBody<Bytes, axum::Error>`.

[in Tonic]: https://github.com/hyperium/tonic/issues/117
2021-11-01 22:37:16 +01:00
David Pedersen 9465128f3e Rework docs (#437)
This reworks axum's docs in an attempt to make things easier to find. Previously I wasn't a fan of those docs for the same topic were spread across the root module docs and more specific places like types and methods.

This changes it such that the root module docs only gives a high level introduction to a topic, perhaps with a small example, and then link to other places where all the details are. This means `Router` is now the single place to learn about routing, and etc for the topics like handlers and error handling.
2021-11-01 21:13:37 +00:00
David Pedersen 1c6038c09f Depend on tower 0.4.10 (#439)
axum uses `impl Layer for ServiceBuilder` internally which requires
tower 0.4.10. Making that explicit should help users updating to axum
0.3 without also having to manually bump their tower dependency.
2021-11-01 13:15:34 +00:00
David Pedersen 2b8494a576 Make MethodNotAllowed public (#436)
Not sure why this wasn't caught by `#![deny(unreachable_pub, private_in_public)]`
2021-10-31 20:57:32 +01:00
David Pedersen 857ecd7314 Break root module docs into separate files (#432)
For 0.3 I'm thinking about some changes I wanna make to the docs. I
don't like how information is currently spread over so many places.
Still thinking about how I wanna re-organize it.

However I do think it makes sense to break the root module docs into
separate files that get included with `#![doc = include_str!("file")]`.
Makes working on a single section easier and more focused. It looks the
same for the user reading the docs.

This means axum's MSRV is now 1.54 but since thats two releases ago I'm
fine with that.
2021-10-31 20:37:56 +01:00
Paul Butler 9145c0e396 Add notify.run to community showcase (#435) 2021-10-31 08:22:46 +01:00
david-perez be62f49b7e Fix typo in sealed trait used in Handler (#430) 2021-10-28 16:44:04 +00:00
david-perez 21e5b654ab Fix NotFound docs (#431) 2021-10-28 16:41:52 +00:00
David Pedersen 2d1635a6d7 Fix sse and ws examples (#429)
* Fix sse example

* Fix websockets example
2021-10-27 15:52:41 +00:00
David Pedersen a3822cb175 Add test for wildcards with trailing slashes
Its already working, just to make sure we don't regress in the future
2021-10-27 09:32:14 +02:00
David Pedersen a2627e9084 Remove support for CONNECT (#428)
Given how different `CONNECT` requests are I think its fine to require
users to handle them manually. Since they don't contain paths you
probably don't need a full routing framework 🤷

An example can be found [here](https://github.com/tokio-rs/axum/blob/main/examples/http-proxy/src/main.rs)

Fixes #418
2021-10-26 21:11:33 +00:00
David Pedersen 94330b7796 Panic if routing to router (#427)
This panics if you pass a `Router` to `Router::route`. Thats invalid and
will result in unreachable routes.

Unfortunately I don't think we can make it a type error while supporting
general `Service`s. So I think this is a decent workaround.

Fixes https://github.com/tokio-rs/axum/issues/174
2021-10-26 20:44:16 +00:00
David Pedersen 92f96a201c Implement nesting as regular wildcard routes (#426)
When fixing bugs with `MatchedPath` (introduced to fix https://github.com/tokio-rs/axum/issues/386) I realized nesting could basically be implemented using regular routes, if we can detect that the service passed to `nest` is in fact a `Router`. Then we can transfer over all its routes and add the prefix.

This makes nesting much simpler in general and should also be slightly faster since we're no longer nesting routers.
2021-10-26 20:31:22 +00:00
David Pedersen fc9bfb8a50 Add HTTP proxy example (#425) 2021-10-26 20:46:40 +02:00
David Pedersen 302a720271 Breakup routing module (#424)
The routing module had gotten quite hard to navigate so this breaks it
into smaller modules. No user facing changes.
2021-10-26 17:51:22 +00:00
David Pedersen 91981db8c7 Fix middleware not seeing the full URI for nested routes (#423)
We stripped the prefix before calling any middleware. Instead the prefix
should be stripped just before calling the actual route. This way
middleware still see the full URI, as it used to work in 0.2.

Fixes #419
2021-10-26 19:29:59 +02:00
David Pedersen 8fe4eaf1d5 Run middleware for requests with no matching route (#422)
While thinking about #419 I realized that `main` had a bug where
middleware wouldn't be called if no route matched the incoming request.
`Router` would just directly return a 404 without calling any
middleware.

This fixes by making the fallback default to a service that always
returns 404 and always calling that if no route matches.

Layers applied to the router is then also applied to the fallback.

Unfortunately this breaks #380 but I don't currently see a way to
support both. Auth middleware need to run _after_ routing because you
don't care about auth for unknown paths, but logging middleware need to
run _before_ routing because they do care about seeing requests for
unknown paths so they can log them...

Part of #419
2021-10-26 18:39:05 +02:00
David Pedersen e9533c566f Don't allow empty paths (#421) 2021-10-26 15:37:25 +00:00