mirror of
https://github.com/tokio-rs/axum.git
synced 2024-11-28 19:22:56 +01:00
Update readme
This commit is contained in:
parent
f690e74275
commit
c0bf77cbe4
2 changed files with 68 additions and 31 deletions
94
README.md
94
README.md
|
@ -10,7 +10,7 @@ with. Will probably change the name to something else.
|
||||||
|
|
||||||
- As easy to use as tide. I don't really consider warp easy to use due to type
|
- As easy to use as tide. I don't really consider warp easy to use due to type
|
||||||
tricks it uses. `fn route() -> impl Filter<...>` also isn't very ergonomic.
|
tricks it uses. `fn route() -> impl Filter<...>` also isn't very ergonomic.
|
||||||
Just `async fn(Request) -> Result<Response, Error>` would be nicer.
|
Just `async fn(Request) -> Response` would be nicer.
|
||||||
- Deep integration with Tower meaning you can
|
- Deep integration with Tower meaning you can
|
||||||
- Apply middleware to the entire application.
|
- Apply middleware to the entire application.
|
||||||
- Apply middleware to a single route.
|
- Apply middleware to a single route.
|
||||||
|
@ -40,8 +40,8 @@ Defining a single route looks like this:
|
||||||
```rust
|
```rust
|
||||||
let app = tower_web::app().at("/").get(root);
|
let app = tower_web::app().at("/").get(root);
|
||||||
|
|
||||||
async fn root(req: Request<Body>) -> Result<&'static str, Error> {
|
async fn root(req: Request<Body>) -> &'static str {
|
||||||
Ok("Hello, World!")
|
"Hello, World!"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -59,14 +59,14 @@ let app = tower_web::app()
|
||||||
Handler functions are just async functions like:
|
Handler functions are just async functions like:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn handler(req: Request<Body>) -> Result<&'static str, Error> {
|
async fn handler(req: Request<Body>) -> &'static str {
|
||||||
Ok("Hello, World!")
|
"Hello, World!"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
They most take the request as the first argument but all arguments following
|
They must take the request as the first argument but all arguments following
|
||||||
are called "extractors" and are used to extract data from the request (similar
|
are called "extractors" and are used to extract data from the request (similar
|
||||||
to rocket but no macros):
|
to rocket but without macros):
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -86,7 +86,7 @@ async fn handler(
|
||||||
user: extract::Json<UserPayload>,
|
user: extract::Json<UserPayload>,
|
||||||
// deserialize query string into a `Pagination`
|
// deserialize query string into a `Pagination`
|
||||||
pagination: extract::Query<Pagination>,
|
pagination: extract::Query<Pagination>,
|
||||||
) -> Result<&'static str, Error> {
|
) -> &'static str {
|
||||||
let user: UserPayload = user.into_inner();
|
let user: UserPayload = user.into_inner();
|
||||||
let pagination: Pagination = pagination.into_inner();
|
let pagination: Pagination = pagination.into_inner();
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ The inputs can also be optional:
|
||||||
async fn handler(
|
async fn handler(
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
user: Option<extract::Json<UserPayload>>,
|
user: Option<extract::Json<UserPayload>>,
|
||||||
) -> Result<&'static str, Error> {
|
) -> &'static str {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -112,7 +112,7 @@ async fn handler(
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
// buffer the whole request body
|
// buffer the whole request body
|
||||||
body: Bytes,
|
body: Bytes,
|
||||||
) -> Result<&'static str, Error> {
|
) -> &'static str {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -124,56 +124,96 @@ async fn handler(
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
// max body size in bytes
|
// max body size in bytes
|
||||||
body: extract::BytesMaxLength<1024>,
|
body: extract::BytesMaxLength<1024>,
|
||||||
) -> Result<&'static str, Error> {
|
) -> &'static str {
|
||||||
Ok("Hello, World!")
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Params from dynamic routes like `GET /users/:id` can be extracted like so
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn handle(
|
||||||
|
req: Request<Body>,
|
||||||
|
// get a map of key value pairs
|
||||||
|
map: extract::UrlParamsMap,
|
||||||
|
) -> &'static str {
|
||||||
|
let raw_id: Option<&str> = map.get("id");
|
||||||
|
let parsed_id: Option<i32> = map.get_typed::<i32>("id");
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(
|
||||||
|
req: Request<Body>,
|
||||||
|
// or get a tuple with each param
|
||||||
|
params: extract::UrlParams<(i32, String)>,
|
||||||
|
) -> &'static str {
|
||||||
|
let (id, name) = params.into_inner();
|
||||||
|
|
||||||
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Anything that implements `FromRequest` can work as an extractor where
|
Anything that implements `FromRequest` can work as an extractor where
|
||||||
`FromRequest` is a simple async trait:
|
`FromRequest` is an async trait:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait FromRequest: Sized {
|
pub trait FromRequest: Sized {
|
||||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Error>;
|
type Rejection: IntoResponse<B>;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This "extractor" pattern is inspired by Bevy's ECS. The idea is that it should
|
This "extractor" pattern is inspired by Bevy's ECS. The idea is that it should
|
||||||
be easy to parse pick apart the request without having to repeat yourself a lot
|
be easy to pick apart the request without having to repeat yourself a lot or use
|
||||||
or use macros.
|
macros.
|
||||||
|
|
||||||
Dynamic routes like `GET /users/:id` is also supported.
|
The return type must implement `IntoResponse`:
|
||||||
|
|
||||||
You can also return different response types:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn string_response(req: Request<Body>) -> Result<String, Error> {
|
async fn empty_response(req: Request<Body>) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets `content-type: text/plain`
|
||||||
|
async fn string_response(req: Request<Body>) -> String {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets `content-type: appliation/json`. `Json` can contain any `T: Serialize`
|
// gets `content-type: appliation/json`. `Json` can contain any `T: Serialize`
|
||||||
async fn json_response(req: Request<Body>) -> Result<response::Json<User>, Error> {
|
async fn json_response(req: Request<Body>) -> response::Json<User> {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets `content-type: text/html`. `Html` can contain any `T: Into<Bytes>`
|
// gets `content-type: text/html`. `Html` can contain any `T: Into<Bytes>`
|
||||||
async fn html_response(req: Request<Body>) -> Result<response::Html<String>, Error> {
|
async fn html_response(req: Request<Body>) -> response::Html<String> {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
// or for full control
|
// or for full control
|
||||||
async fn response(req: Request<Body>) -> Result<Response<Body>, Error> {
|
async fn response(req: Request<Body>) -> Response<Body> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result is supported if each type implements `IntoResponse`
|
||||||
|
async fn response(req: Request<Body>) -> Result<Html<String>, StatusCode> {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This makes error handling quite simple. Basically handlers are not allowed to
|
||||||
|
fail and must always produce a response. This also means users are in charge of
|
||||||
|
how their errors are mapped to responses rather than a framework providing some
|
||||||
|
opaque catch all error type.
|
||||||
|
|
||||||
You can also apply Tower middleware to single routes:
|
You can also apply Tower middleware to single routes:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let app = tower_web::app()
|
let app = tower_web::app()
|
||||||
.at("/")
|
.at("/")
|
||||||
.get(send_some_large_file.layer(tower_http::compression::CompressionLayer::new()))
|
.get(send_some_large_file.layer(CompressionLayer::new()))
|
||||||
```
|
```
|
||||||
|
|
||||||
Or to the whole app:
|
Or to the whole app:
|
||||||
|
@ -222,12 +262,6 @@ See the examples directory for more examples.
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- Error handling should probably be redone. Not quite sure if its bad the
|
|
||||||
`Error` is just an enum where everything is public.
|
|
||||||
- Audit which error codes we return for each kind of error. This will probably
|
|
||||||
be changed when error handling is re-done.
|
|
||||||
- Probably don't want to require `hyper::Body` for request bodies. Should
|
|
||||||
have our own so hyper isn't required.
|
|
||||||
- `RouteBuilder` should have an `async fn serve(self) -> Result<(),
|
- `RouteBuilder` should have an `async fn serve(self) -> Result<(),
|
||||||
hyper::Error>` for users who just wanna create a hyper server and not care
|
hyper::Error>` for users who just wanna create a hyper server and not care
|
||||||
about the lower level details. Should be gated by a `hyper` feature.
|
about the lower level details. Should be gated by a `hyper` feature.
|
||||||
|
|
|
@ -73,7 +73,10 @@ impl IntoResponse<Body> for &'static str {
|
||||||
|
|
||||||
impl IntoResponse<Body> for String {
|
impl IntoResponse<Body> for String {
|
||||||
fn into_response(self) -> Response<Body> {
|
fn into_response(self) -> Response<Body> {
|
||||||
Response::new(Body::from(self))
|
let mut res = Response::new(Body::from(self));
|
||||||
|
res.headers_mut()
|
||||||
|
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue