❝Трудности в жизни не злят, а заставляют спокойнее ❞
Всем привет,Я«Семь, восемь, девять»。один「Сосредоточьтесь на интерфейсеразвиватьтехнология/Rust
иAI
Обмен знаниями о приложениях」изCoder
。
До,нассуществовать Во многих статьях упоминалосьRust Web
рамка。
ЧтосерединаиметьодинRust Web
рамкаиз Встречается очень часто -- Это аксум[1].
и в ящике trend[2]из Судя по количеству скачиванийaxum
Тоже далеко впереди。
так,нассегодняэтот В этой статье будет дано краткое введениеaxum
изиспользование。
Ладно, уже поздно, давайте приступим к делу.
❝
❞
❝「Необходимые очки знаний」,Просто введение концепции,Никаких углубленных объяснений. потому что,Эти концепции существуют в следующих статьях.,Чтобы письмо было более плавным,такдолжен был иметьсуществовать В текстеиз Сначала поставьте концептуальное объяснение。«Если вы знакомы с этими понятиями, вы можете просто игнорировать их». в то же время,Потому что мою статью читают много людей,так Некоторые очки знаний могут«Я считаю их сокровищами, а вы считаете их не более чем травой и горчицей и выбрасываете их, как изношенную обувь».。Следующие точки знаний,пожалуйста«Использовать по мере необходимости»。 ❞
❝REST[3] is an acronym for REpresentational State Transfer and an architectural style for distributed hypermedia systems. ❞
переведено насередина Вэньцзюда:REST
даREpresentational State Transfer
изакроним,такжедаРаспределенная гипермедийная система
изархитектурный стиль。
REST
Нетда Что-то вродепротоколилистандартный,ида Что-то вроде«Архитектурный стиль»。Обычно оно основано наHTTP
протокол,использоватьстандартныйизHTTPметод(нравитьсяGET
、POST
、PUT
、DELETE
)общаться。RESTful
API спроектирован так, чтобы быть простым, расширяемым, понятным и совместимым с существующими веб-стандартами.
REST основан на некоторых ограничениях и принципах.,Эти ограниченияипринципы способствуютдизайнсерединаизпростота、Масштабируемостьилицо без гражданства。RESTful
Шесть руководящих принципов или ограничений архитектуры:
Интересно, использовали ли вы когда-нибудь Express[4] для создания приложений? Если нет, ничего страшного.
const express = require("express");
const app = express();
// При отправке на главную страницу GET При запросе используйте "hello front789" в ответ
app.get("/", (req, res) => {
res.send("hello front789");
});
этотдаперехватыватьExpress
Официальный сайтиз О маршрутизацииизпример。толькоиспользовать Построен всего в несколько строк кодаодинсетевые услуги。
и,нассегодняизглавный геройAxum
также естьиExpress
из Волшебная функция。Они все следуют за классомRESTful
из API дизайн。насможет создатьФункция обработчика
(handler
)и добавьте их вaxum::Router
начальство。
async fn hello_front789() -> &'static str {
"Передняя часть Цибацзю!"
}
Затемнас Это может быть как нижеэтот样Воля Что添加приезжатьRouter
середина:
use axum::{Router, routing::get};
fn init_router() -> Router {
Router::new()
.route("/", get(hello_front789))
}
начальстволапшаизпримериExpress
достиг того же самогоиз ЭффектПри отправке на главную страницу GET При запросе используйте "Передняя часть Цибацзю" в ответ
。
дляФункция обработчика
Давайте поговорим,этонуждатьсядаодинaxum::response::Response
тип,илибыть реализованнымaxum::response::IntoResponse
。этотдлябольшинствобазовый тип
(Можно обратиться кRust Изучение типов данных[5])
Например, если мы хотим отправить пользователю некоторые JSON
данные, которые мы можем использовать Axum
из JSON
тип, использовать axum::Json
типинкапсуляциянасхотетьотправлятьразизданные.
use axum::Json;
async fn json() -> Json<Vec<String>> {
Json(vec!["front".to_owned(), "789".to_owned()])
}
Как будто мы только начали предоставлять коды,настакже МожетвозвращатьсяпрямойвозвращатьсяодинСрез струны
(&'static str
)。
Мы также можем использовать его напрямую impl IntoResponse
。нода,прямойиспользоватьтакжеиметь в видунуждаться「обеспечить всевозвращатьсятип Вседатакой жеизтип」!также就данасможетОбнаружена ненужная ошибка
。так,нас Можетдлявозвращатьсятипвыполнитьодин enum
или struct
достичь«Все возвращаемые типы имеют один и тот же тип»из Ограничения。
use axum::{
response::{
Response,
IntoResponse
},
Json,
http::StatusCode
};
use serde::Serialize;
// для упаковки `JSON` Данные тела ответа.
#[derive(Serialize)]
struct Message {
message: String
}
// Несколько типов `API` из Тип ответа.
// 1. `OK` и `Created` Соответствует разным из `HTTP` код состояния;
// 2. `JsonData` упакованный `Vec<Message>` из `JSON` данные.
enum ApiResponse {
OK,
Created,
JsonData(Vec<Message>),
}
// Это делает `ApiResponse` может быть автоматически преобразован в `axum Response`。
impl IntoResponse for ApiResponse {
fn into_response(self) -> Response {
// Проверьте переменную перечисления и верните соответствующий HTTP Код состояния иданные.
match self {
Self::OK => (StatusCode::OK).into_response(),
Self::Created => (StatusCode::CREATED).into_response(),
Self::JsonData(data) => (StatusCode::OK, Json(data)).into_response()
}
}
}
так что проходи ApiResponse
перечисление и IntoResponse
Реализация, очень удобно генерировать структуру, соответствующую структуре JSON
API ответ。и может легкоиз«Совместимость с различными типами кодов состояния ответа»。
ЗатемсуществоватьФункция обработчика
серединавыполнить该 enum
:
async fn my_function() -> ApiResponse {
ApiResponse::JsonData(vec![Message {
message: "hello 789".to_owned()
}])
}
Конечно, мы также можем использовать возвращаемое значение Result[6] Тип! Хотя типы ошибок технически допускают все, что можно преобразовать в HTTP
Ответ из содержимого, но мы также можем реализовать тип ошибки для представления HTTP
проситьсуществовать Наше приложение может потерпеть неудачу по нескольким причинам, как и в случае с успехом HTTP
просить enum
Сделай это. Например:
enum ApiError {
BadRequest,
Forbidden,
Unauthorised,
InternalServerError
}
// ... Опустить код ApiResponseиз
async fn my_function() -> Result<ApiResponse, ApiError> {
//
}
Таким образом, наша маршрутизация может различать ошибки и успехи.
существоватьRust
серединаиспользоватьбаза данных,Тогда sqlx[7] точно не обойти.
Обычно при настройке базы данных нам может потребоваться настроить соединение с базой данных:
use axum::{Router, routing::get};
use sqlx::PgPoolOptions;
#[derive(Clone)]
struct AppState {
db: PgPool
}
#[tokio::main]
async fn main() {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(<Адрес базы данных>).await;
let state = AppState { pool };
let router = Router::new().route("/", get(hello_world)).with_state(state);
//... остальная часть кода
}
Нам необходимо обеспечить себя из Postgres[8] экземпляр, независимо от того, установлен ли он локально на локальном компьютере или через Docker
Установите тот или иной способ. Однако здесь мы используем Shuttle[9] Это может упростить нашу деятельность.
#[shuttle_runtime::main]
async fn axum(
#[shuttle_shared_db::Postgres] pool: PgPool,
) -> shuttle_axum::ShuttleAxum {
let state = AppState { pool };
// .. остальная часть кода
}
существоватьAxum
серединанас Можетиспользоватьaxum::Extension[10]Для обработки хранилища глобальных переменных приложенияизвопрос。нода,это единственныйиз Недостатком являетсядатип небезопасный
。существоватьбольшинство Rust Web рамки (включает Axum
)середина,насиспользоватьтак называемыйиз«Статус заявки»(app state
) - Специальное приложение существует на основе маршрутов, которые совместно используют все переменные структур. существовать Axum
Единственное требование для завершения этой операции заключается в том, что структура должна реализовать Clone
。
use sqlx::PgPool; // Это пул соединений Postgres
#[derive(Clone)]
struct AppState {
pool: PgPool,
}
#[shuttle_runtime::main]
async fn axum(
#[shuttle_shared_db::Postgres] pool: PgPool,
) -> shuttle_axum::ShuttleAxum {
let state = AppState { pool };
// .. остальная часть кода
}
Чтобы использовать его, мы подключаем его к маршрутизатору и передаем состояние в качестве параметра функции-обработчику:
use axum::{Router, routing::get, extract::State};
fn init_router() -> Router {
Router::new()
.route("/", get(hello_front))
.route("/do_something", get(do_something))
.with_state(state)
}
// Обратите внимание, что добавление статуса заявки не является обязательным. - Только тогда, когда существующий захочет его использовать
async fn hello_front() -> &'static str {
"Hello 789!"
}
async fn do_something(
State(state): State<AppState>
) -> Result<ApiResponse, ApiError> {
// .. Наш код
}
Кромеиспользовать#[derive(Clone)]
снаружи,Мы также можем использовать Атомный счетчик ссылок(std::sync::Arc
)Инкапсуляция структуры состояния приложения。Arcs
да Что-то вроде垃圾收集形式,Можно отслеживать количество клонов,И он будет удален только в том случае, если нет копий:
use std::sync::Arc;
let state = Arc::new(AppState { db });
Теперь, когда мы добавляем состояние в наше приложение, нам нужно обязательно ссылаться на State
Тип экстрактора State<Arc<AppState>>
и Нетда State<AppState>
。
насвозвращаться Может«Извлечение дочернего состояния из состояния приложения»! Это очень полезно, когда нам нужны некоторые переменные из основного состояния, но мы хотим ограничить права управления доступом, к которым может получить доступ данный маршрут. Например:
// Статус заявки
#[derive(Clone)]
struct AppState {
// Сохраните некоторое состояние API
api_state: ApiState,
}
// конкретный статус API
#[derive(Clone)]
struct ApiState {}
// поддерживает преобразование `AppState` преобразован в `ApiState`
impl FromRef<AppState> for ApiState {
fn from_ref(app_state: &AppState) -> ApiState {
app_state.api_state.clone()
}
}
экстрактор(Extractors
)тольконравиться Что名:Они начинаются с HTTP
Извлеките содержимое из просить и передайте его в качестве параметров функции-обработчику для работы. В настоящее время имеется встроенная поддержка обычных данных, например получение индивидуальных данных. header
、путь、Запрос、форма и JSON
。
Например, мы можем использовать axum::Json
Тип передан из HTTP
Извлечено из просить JSON
просить тело обработать HTTP
просить。
use axum::Json;
use serde_json::Value;
async fn my_function(
Json(json): Json<Value>
) -> Result<ApiResponse, ApiError> {
// ...Наш код
}
Хотя приведенный выше код может получать данные, поскольку мы используем serde_json::Value
,этоизструктураиз Динамичный и изменчивыйиз,может содержать что угодно。(существоватьRust расширяет возможности фронтенд-развивания интерфейсной платформы, которая принадлежит нам.серединанасиспользоватьserde_json
иметь дело сjson
документ)
Чтобы достичь желаемой цели, мы пытаемся использовать реализацию serde::Deserialize
из Rust структуратело——этотда Воля«Необходимо преобразовать исходные данные в саму структуру»из:
use axum::Json;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Submission {
message: String
}
async fn my_function(
Json(json): Json<Submission>
) -> Result<ApiResponse, ApiError> {
println!("{}", json.message);
// ...Наш код
}
форма и URL Параметры запроса также можно обрабатывать таким же образом, добавив соответствующий тип в функцию-обработчик. - Например, экстрактор форм может выглядеть так:
async fn my_function(
Form(form): Form<Submission>
) -> Result<ApiResponse, ApiError> {
println!("{}", json.message);
// ...Наш код
}
существоватьотправлять HTTP
проситьприезжать API
из HTML
В конце, конечно, нам также необходимо убедиться, что отправляется правильный тип контента.
header
Это также можно сделать таким же образомиз Способиметь дело с,Толькодаheader
Не будет потреблятьсяпроситьтело!нас Можетиспользовать TypedHeader[11] введите, чтобы сделать это. для Axum 0.6
,наснуждаться启использоватьheaders
Функция,нодасуществовать 0.7
, оно было перенесено в axum-extra[12] ящик, нам нужно добавить typed-header
функция, как показано ниже:
cargo add axum-extra -F typed-header
использоватьтипизменятьheaders
Может简单地Воля Чтоделатьдля Добавление параметровприезжать Функция обработчикасередина:
use headers::ContentType;
use axum::{TypedHeader, headers::Origin}; // существоватьaxum Используется в версии 0.6
use axum_extra::{TypedHeader, headers::Origin}; // существоватьaxum Используется в версии 0.7
async fn my_function(
TypedHeader(origin): TypedHeader<Origin>
) -> Result<ApiResponse, ApiError> {
println!("{}", origin.hostname);
// ...Наш код
}
Кроме TypedHeaders
снаружи,axum-extra
Есть также много других полезных типов, которые можно использовать. Например, у него есть CookieJar
Экстракторы, которые могут помочь управлять cookie
。
Теперь, когда мы знаем больше об экстракторах, возможно, нам будет интересно узнать, как мы можем создавать свои собственные экстракторы. - Например, предположим, что нам нужно создать экстрактор на основе тела просить. Json
возвращатьсяда Разобрать форму。позволятьнаснастраиватьнасизструктураи Функция обработчика:
#[derive(Debug, Serialize, Deserialize)]
struct Payload {
foo: String,
}
async fn handler(JsonOrForm(payload): JsonOrForm<Payload>) {
dbg!(payload);
}
struct JsonOrForm<T>(T);
Сейчас мы можем обеспечить JsonOrForm
Реализация структуры FromRequest<S, B>
!
//выполнить `FromRequest` trait。Это делает `JsonOrForm` может использоваться как `axum extractor` использовать.
#[async_trait]
impl<S, B, T> FromRequest<S, B> for JsonOrForm<T>
where
B: Send + 'static,
S: Send + Sync,
Json<T>: FromRequest<(), B>,
Form<T>: FromRequest<(), B>,
T: 'static,
{
type Rejection = Response;
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
// Стань первым `content-type` проситьголова。 let content_type_header = req.headers().get(CONTENT_TYPE);
let content_type = content_type_header.and_then(|value| value.to_str().ok());
if let Some(content_type) = content_type {
// в случае `application/json`, используйте `req.extract()` extractor Извлечено как `Json<T>`。
if content_type.starts_with("application/json") {
let Json(payload) = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(Self(payload));
}
// в случае `application/x-www-form-urlencoded`,Извлечено как `Form<T>`。
if content_type.starts_with("application/x-www-form-urlencoded") {
let Form(payload) = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(Self(payload));
}
}
// возвращаться `Unsupported Media Type` из Ошибка.
Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
}
}
Таким образом, этот код позволяет нам гибко с ним обращаться. JSON
и Form
Форматизпросить Body
,делатьдляодинудобныйиз extractor
существовать handler
используется в.
Это позволяет избежать повторного извлечения парсинга кодов для разных просит. А также унифицировали обработчик подписи.
существовать Axum 0.7
середина,этот略иметь修改。axum::body::Body
Больше никакого реэкспорта hyper::body::Body
,ида Собственныйизтип - Это означает, что это уже не дженерик, а Request
Тип всегда будет использоваться axum::body::Body
。этотвеществоначальствоиметь в видунас Тольконуждатьсяудалить B
Дженерики:
#[async_trait]
impl<S, T> FromRequest<S> for JsonOrForm<T>
where
S: Send + Sync,
Json<T>: FromRequest<()>,
Form<T>: FromRequest<()>,
T: 'static,
{
// ...то же, что и выше
}
как упоминалось ранее,По сравнению с другими фреймворками,Axum
Огромным преимуществом существования является то, что оно имеет tower
crates Совместимо, что означает, что мы можем Rust API использовать「кто-нибудь хочетиз Tower промежуточное программное обеспечение"!примернравиться,Можем добавить Башню Промежуточное ПО для сжатия ответа:
use tower_http::compression::CompressionLayer;
use axum::{routing::get, Router};
fn init_router() -> Router {
Router::new()
.route("/", get(hello_world))
.layer(CompressionLayer::new)
}
Есть много причин Tower
промежуточное программное обеспечениекомпозицияиз crate
доступно без необходимости писать что-либо самостоятельно программное Если мы существуем, используется в любом приложении! Tower
промежуточное программное обеспечение, это хороший способ повторно использовать наше изпромежуточное программное обеспечение,без написания дополнительного кода,Потому что совместимость гарантирует отсутствие проблем.
Мы также можем создать свою собственную, написав Функцияизпромежуточное программное обеспечение. Функция должна быть включена Request
и Next
типруководить<B>
Общая привязка,потому что Axum
из body
типсуществовать 0.6 in — это общий тип из. Вот пример:
use axum::{http::Request, middleware::Next};
async fn check_hello_world<B>(
req: Request<B>,
next: Next<B>
) -> Result<Response, StatusCode> {
// Требуется http crate, чтобы получить имя заголовка
if req.headers().get(CONTENT_TYPE).unwrap() != "application/json" {
return Err(StatusCode::BAD_REQUEST);
}
Ok(next.run(req).await)
}
существовать Axum 0.7 середина,насвстречаудалить<B>
ограничение,потому что Axum
из axum::body::Body
Тип больше не является универсальным:
use axum::{http::Request, middleware::Next};
async fn check_hello_world(
req: Request,
next: Next
) -> Result<Response, StatusCode> {
// ...то же, что и выше
}
Для существования нашего приложения внедрить новое изпромежуточное программное программное обеспечение, мы хотим использовать axum
из axum::middleware::from_fn
функция,Это позволяет нам использовать функцию в качестве обработчика. существуют на практике,Это выглядит так:
use axum::middleware::self;
fn init_router() -> Router {
Router::new()
.route("/", get(hello_world))
.layer(middleware::from_fn(check_hello_world))
}
Если нам нужно добавить состояние приложения в промежуточное программное обеспечение, мы можем добавить его в функцию-обработчик, а затем использовать middleware::from_fn_with_state
:
fn init_router() -> Router {
let state = setup_state(); // Инициализировать состояние приложения
Router::new()
.route("/", get(hello_world))
.layer(middleware::from_fn_with_state(state.clone(), check_hello_world))
.with_state(state)
}
Суммируя,Axum
через его Tower
изсовместимость,длясуществовать Rust API Используется в мощном изпромежуточном программное обеспечение обеспечивает большое удобство.
Предположим, мы хотим существовать Axum
Предоставьте несколько статических файлов в —— Или мы использовали что-то вроде React
Таким образом, передняя часть JavaScript
framework для создания приложения и хотите объединить его с Rust Axum задняя частьобъединены водин Большое приложениеиспользоватьпрограмма,и Нетда Размещайте интерфейс отдельноизадняя часть。
Axum
сам по себе не предоставляет эту функциональность, однако имеет; tower-http
такой жеиз Функция,Последний обеспечивает способ обслуживания наших собственных статических файлов.,несмотря ни на чтонасдабегатьSPA
,возвращатьсядаиспользовать Next.js
Когда платформа генерирует статические файлы, это просто. HTML
、CSS
и JavaScript
можно использовать сAxum
Слияние。
Если мы Используя статически сгенерированный файл, мы можем легко вставить его в маршрутизатор (при условии, что из статического файла существует корневой каталог проекта dist
папка):
use tower_http::services::ServeDir;
fn init_router() -> Router {
Router::new()
.nest_service("/", ServeDir::new("dist"))
}
Если мы используем React
、Vue
,может бытьbundle
Строитьприезжать Связанныйдокументпапкасередина,Затем используйте следующее:
use tower_http::services::{ServeDir, ServeFile};
fn init_router() -> Router {
Router::new().nest_service(
"/", ServeDir::new("dist")
.not_found_service(ServeFile::new("dist/index.html")),
)
}
Мы также можем использовать askama[13]、tera[14] и maud[15] (существоватьиспользовать Rust строить React Server Components из Web Сервер[16] Как из легкого JavaScript библиотеки для ускорения производства.
В связи с необходимостью использования Dockerfile
,использовать Rust
Развертывание серверных программ всегда немного обременительно. нода,Если мы используем Shuttle
,Только需использовать cargo shuttle deploy
Развертывание завершено. Никакой настройки не требуется.