Начало работы с Axum | Rust

Я новичок в Rust, и я искал веб-фреймворк для создания сервера или API, и я нашел Axum на Github, и я хочу начать его использовать. Я хотел узнать об Axum, поэтому я начал писать эту статью, изучая его, чтобы закрепить некоторые его концепции и возможности.

DISCLAIMER: Это не исчерпывающее руководство по Axum, если вы хотите знать каждую функцию и как их использовать, то вот документация.

В этой статье мы просто будем использовать методы get() и post() и обслуживать HTML-файл.

Согласно документации:
axum — это фреймворк для веб-приложений, который фокусируется на эргономичности и модульности.

Высокоуровневые возможности

  • Маршрутизация запросов к обработчикам с помощью API без макросов.
  • Декларативный разбор запросов с помощью экстракторов.
  • Простая и предсказуемая модель обработки ошибок.
  • Генерирование ответов с минимальным количеством шаблонов.
  • Использование всех преимуществ экосистемы промежуточного ПО, сервисов и утилит tower и tower-http.

В частности, последний пункт отличает axum от существующих фреймворков. axum не имеет собственной системы промежуточного ПО, а использует tower::Service. Это означает, что axum получает таймауты, трассировку, сжатие, авторизацию и многое другое бесплатно. Это также позволяет вам делиться промежуточным ПО с приложениями, написанными с использованием hyper или tonic.

Давайте начнем импортировать наши зависимости.

Cargo.toml


[dependencies]
axum = "0.5.11"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1.35"
tracing-subscriber = "0.3.14"
serde = { version = "1.0.138", features = ["derive"] }
serde_json = "1.0"
Вход в полноэкранный режим Выход из полноэкранного режима

main.rs

use axum::{
    routing::{get, post},
    http::StatusCode,
    response::IntoResponse,
    Json, Router};

use std::net::SocketAddr;
use serde::{Deserialize, Serialize};

#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::info!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn root() -> &'static str {
    "Hello, World!"
}

Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь давайте поговорим о приведенном выше коде. Сначала мы импортируем то, что собираемся использовать. И пишем #[tokio::main] над нашей главной функцией, чтобы нам разрешили использовать async. Затем мы создаем экземпляр Router и вызываем метод route с указанием пути и сервиса, который будет вызван, если путь совпадет, в данном случае root, и он обернут в метод get.

Затем мы используем SocketAddr для определения порта, который мы будем использовать, и передаем ему IP-адрес localhost и номер порта, в данном случае ‘8000’.

Мы используем Server и передаем ссылку addr функции bind.

Согласно документации:

Server — это основной способ начать прослушивать HTTP-запросы. Он оборачивает слушателя с помощью MakeService, а затем должен быть выполнен, чтобы начать обслуживать запросы.

Мы передаем app в качестве аргумента методу serve, но нам нужно использовать метод into_make_service, потому что serve получает make_service в качестве параметра, а app является экземпляром маршрутизатора.

Вот что говорится в документации о into_make_service:

pub fn into_make_service(self) -> IntoMakeService< Self >

Преобразует этот маршрутизатор в MakeService, который представляет собой сервис, чьим ответом является другой сервис.

Это полезно при запуске вашего приложения с сервером hyper’s Server.

И MakeService :

Создает новые значения Сервиса.

Действует как фабрика сервисов. Это полезно для случаев, когда необходимо создавать новые значения службы. В качестве примера можно привести слушателя TCP-сервера. Слушатель принимает новые TCP-потоки, получает новое значение Службы с помощью трейта MakeService и использует это новое значение Службы для обработки входящих запросов на этом новом TCP-потоке.

По сути, это псевдоним трейта для службы служб.

Вот ссылка, если вы хотите узнать больше о трейте MakeService.

Теперь запустим код

cargo run
Вход в полноэкранный режим Выйти из полноэкранного режима

В консоли должно появиться сообщение «listening on 127.0.0.1:3000», а если мы скопируем номер и вставим его в браузер, то увидим эту страницу:

Извлечение параметра из пути

Axum имеет множество экстракторов, см. документацию здесь

В этом примере мы будем использовать Path для извлечения имени из пути и использования его для отправки JSON-сообщения.

...
use axum::extract::Path;  
...

async fn json_hello(Path(name): Path<String>) -> impl IntoResponse {
    let greeting = name.as_str();
    let hello = String::from("Hello ");

    (StatusCode::OK, Json(json!({"message": hello + greeting })))
}

Вход в полноэкранный режим Выход из полноэкранного режима

В блоке кода выше мы используем impl IntoResponse в качестве возвращаемого значения функции json_hello(). Согласно документации:

Все, что реализует IntoResponse, может быть возвращено из обработчиков. Обычно вам не нужно реализовывать IntoResponse вручную, так как axum предоставляет реализации для многих распространенных типов.

Если вы хотите реализовать собственный тип ошибки, вот документация.

Теперь давайте обновим нашу функцию main().

#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root))
        .route("/hello/:name", get(json_hello));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::info!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

Вход в полноэкранный режим Выход из полноэкранного режима

Если мы напишем в браузере localhost:3000/hello/Carlos, он покажет следующее:

Теперь давайте воспользуемся методом post(), чтобы создать пользователя.

...

#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

#[derive(Debug, Serialize,Deserialize, Clone, Eq, Hash, PartialEq)]
struct User {
    id: u64,
    username: String,
}
...

async fn create_user(Json(payload): Json<CreateUser>,) -> impl IntoResponse {
    let user = User {
        id: 1337,
        username: payload.username
    };

    (StatusCode::CREATED, Json(user))
}

Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном выше коде мы создаем две структуры: CreateUser и User. Мы используем JSON extractor, чтобы извлечь полезную нагрузку, то есть данные из JSON, передать их в качестве значения в поле username, и вернуть переменную user в виде JSON.

Добавляем маршрут post() в функцию main().

...

#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root))
        .route("/hello/:name", get(json_hello))
        .route("/user", post(create_user);

...

Вход в полноэкранный режим Выход из полноэкранного режима

Обслуживание файлов

Для обслуживания файлов мы должны добавить tower-http к зависимостям нашего проекта.

Cargo.toml

...

[dependencies]
...
tower-http = { version = "0.3.0", features = ["fs", "trace"] }

Вход в полноэкранный режим Выход из полноэкранного режима

Мы создаем каталог и создаем в нем HTML-файл.

src
static/
    hello.html
Cargo.toml

Вход в полноэкранный режим Выход из полноэкранного режима

hello.html

<h1>Hello everyone</h1>
<h2>This is a static file</h2>

Войти в полноэкранный режим Выход из полноэкранного режима

main.rs

Теперь мы обновим нашу функцию main(), чтобы добавить маршрут, обслуживающий наш HTML-файл.

...

#[tokio::main]
async fn main() {

...
.route("/hello/:name", get(json_hello))
.route("/static", get_service(ServeFile::new("static/hello.html"))
        .handle_error(|error: io::Error| async move {
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Unhandled internal error: {}", error),
            )
        }));

...

}

Вход в полноэкранный режим Выход из полноэкранного режима

Запустите код и перейдите на localhost:3000/static, мы должны увидеть эту страницу:

Полный код

main.rs

use axum::{
    routing::{get, post, get_service},
    http::StatusCode,
    response::IntoResponse,
    Json, Router};

use axum::extract::Path;
use tower_http::services::ServeFile;

use std::net::SocketAddr;
use serde::{Deserialize, Serialize};
use serde_json::{json};

use std::{io};



#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

#[derive(Debug, Serialize,Deserialize, Clone, Eq, Hash, PartialEq)]
struct User {
    id: u64,
    username: String,
}

#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root))
        .route("/user", post(create_user))
        .route("/hello/:name", get(json_hello))
        .route("/static", get_service(ServeFile::new("static/hello.html"))
            .handle_error(|error: io::Error| async move {
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!("Unhandled internal error: {}", error),
                )
            }));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::info!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();


}

async fn root() -> &'static str {
    "Hello, World!"
}

async fn create_user(Json(payload): Json<CreateUser>) -> impl IntoResponse {
    let user = User {
        id: 1337,
        username: payload.username
    };

    (StatusCode::CREATED, Json(user))
}


async fn json_hello(Path(name): Path<String>) -> impl IntoResponse {
    let greeting = name.as_str();
    let hello = String::from("Hello ");

    (StatusCode::OK, Json(json!({"message": hello + greeting })))
}

Вход в полноэкранный режим Выход из полноэкранного режима

Заключение

Axum — это первый веб-фреймворк, который я попробовал на Rust, и он мне понравился. Документация хорошо написана и имеет много примеров на странице Github. Один из аспектов, который мне понравился в нем — это API без макросов, другой аспект — это возможность цепочки маршрутов, так что я могу обернуть все свои обработчики в один блок кода.

Единственное, что мешает мне часто использовать его и делиться другими аспектами — это недостаток знаний в Rust, но я должен быть более дисциплинированным, и я буду, потому что хочу внести свой вклад в этот проект.

У Axum есть канал discord, сообщество действительно замечательное и полезное, вот ссылка.

Спасибо, что уделили время и прочитали эту статью.

Если у вас есть какие-либо рекомендации, советы, подсказки о том, как улучшить мой код, мой английский или что-либо еще, пожалуйста, оставьте комментарий или свяжитесь со мной через Twitter или LinkedIn.

Исходный код находится здесь.

Ссылка

Примеры

Документация Axum

Документация Tower_http

Документация Hyper

Оцените статью
devanswers.ru
Добавить комментарий