
Привет, Хабр!
Actix появился на свет благодаря Николаю Киму, также известного в сообществе под ником fafhrd91. Николай создал экосистему, которая позволяет строить масштабируемые, надежные веб-приложения и микросервисы. Actix основан на модели акторов, концепции, которая была впервые предложена в 1973 году для симуляции активных компонентов в распределенных системах.
Actix требует Rust версии 1.39 или выше. Можно легко добавить Actix юзая Cargo. Для этого достаточно добавить зависимости Actix-web в файл Cargo.toml вашего проекта:
[dependencies] actix-web = "4.0"
После добавления юзаем cargo build, и карго автоматом скомпилирует Actix Web вместе с необходимыми зависимостями.
Основные возможности
Основные элементы Actix — это акторы, система и арбитр.
Актор — это объект, который инкапсулирует состояние и поведение. Он взаимодействует с другими акторами исключительно посредством асинхронных сообщений:
use actix::prelude::*; struct MyActor; impl Actor for MyActor { type Context = Context<Self>; } impl MyActor { fn new() -> Self { MyActor } }
Каждый актор в Actix должен реализовать трейт Actor, определяющий контекст, в котором актор исполняется.
System в Actix — это контейнер, который запускает и управляет акторами.
fn main() { let system = System::new(); system.block_on(async { let addr = MyActor::new().start(); // отправка сообщений актору и другие операции }); }
Arbiter — это сущность, управляющая пулом потоков для выполнения асинхронных задач. Каждый Arbiter может запускать несколько акторов, обеспечивая параллельное выполнение их задач:
fn main() { let system = System::new(); system.block_on(async { let arbiter = Arbiter::new(); arbiter.spawn(async { // задачи, выполняемые в контексте нового Arbiter }); }); }
Как это все взаимодействует:
use actix::prelude::*; struct Ping; impl Message for Ping { type Result = String; } struct PongActor; impl Actor for PongActor { type Context = Context<Self>; } impl Handler<Ping> for PongActor { type Result = String; fn handle(&mut self, _msg: Ping, _ctx: &mut Context<Self>) -> Self::Result { "Pong".to_string() } } fn main() { let system = System::new(); system.block_on(async { let pong_actor_address = PongActor.start(); let result = pong_actor_address.send(Ping).await.unwrap(); println!("Received: {}", result); }); }
PongActor обрабатывает сообщения типа Ping и отвечает сообщением «Pong». Cоздаем актора, отправляем ему сообщение и асинхронно ожидаем ответа.
Обработчики могут быть простыми, возвращая статические данные, или сложными, выполняя асинхронные операции, такие как запросы к базам данных или внешним API. В Actix Web для объявления обработчика используется макрос #[get("/path")], #[post("/path")] и так далее, в зависимости от HTTP-метода, который обработчик должен обрабатывать:
use actix_web::{web, App, HttpResponse, HttpServer, Responder}; async fn greet(req: web::HttpRequest) -> impl Responder { let name = req.match_info().get("name").unwrap_or("World"); HttpResponse::Ok().body(format!("Hello, {}!", name)) }
Мидлвари в Actix Web могут модифицировать запросы перед тем, как они достигнут обработчика, или изменять ответы после того, как обработчик их сгенерил, но до того, как они отправлены клиенту:
use actix_web::{middleware, App, HttpServer}; #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .wrap(middleware::Logger::default()) // мидлвари логирования .service(/* обработчики */) }) .bind("127.0.0.1:8080")? .run() .await }
Асинхронность в Actix
В Actix каждый актор исполняется асинхронно, имея возможность обрабатывать сообщения параллельно с другими акторами. Акторы могут отправлять, получать и обрабатывать сообщения асинхронно, используя async/await Rust:
use actix::prelude::*; struct MyAsyncActor; impl Actor for MyAsyncActor { type Context = Context<Self>; } #[derive(Message)] #[rtype(result = "String")] struct MyMessage; impl Handler<MyMessage> for MyAsyncActor { type Result = ResponseFuture<String>; fn handle(&mut self, _msg: MyMessage, _ctx: &mut Context<Self>) -> Self::Result { Box::pin(async { // какая-нибудь длительная асинхронная операция actix_rt::time::sleep(std::time::Duration::from_secs(1)).await; "Hello from async actor!".to_string() }) } }
MyAsyncActor асинхронно обрабатывает сообщение MyMessage, имитируя длительную операцию с использованием sleep. Возвращаемый тип ResponseFuture<String> показывает то что все будет выполнено асинхронно.
С Actix можно асинхронного взаимодействовать между акторами через отправку сообщений. Акторы могут использовать метод .send() для асинхронной отправки сообщений другим акторам и ожидать ответа с помощью await:
#[actix_rt::main] async fn main() { let addr = MyAsyncActor.start(); let res = addr.send(MyMessage).await; match res { Ok(result) => println!("Received response: {}", result), Err(e) => println!("Encountered an error: {}", e), } }
Отправляем сообщение MyMessage актору MyAsyncActor и асинхронно ожидаем ответ.
Actix поддерживает асинхронную обработку стримов:
use actix::prelude::*; use futures::stream::{self, StreamExt}; struct StreamActor; impl Actor for StreamActor { type Context = Context<Self>; } impl StreamHandler<i32> for StreamActor { fn handle(&mut self, item: i32, _ctx: &mut Context<Self>) { println!("Received: {}", item); } } #[actix_rt::main] async fn main() { let addr = StreamActor.start(); // создание простого стрима let stream = stream::iter(vec![1, 2, 3]); // отправка стрима актору addr.add_stream(stream); }
StreamActor подписывается на стрим целые числа и асинхронно обрабатывает каждое поступающее значение.
Прочие возможности
Создадим простейший веб-сервер, который будет отвечать на запросы по корневому пути (/):
use actix_web::{web, App, HttpResponse, HttpServer, Responder}; async fn greet() -> impl Responder { HttpResponse::Ok().body("Hello, Habr!") } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/", web::get().to(greet)) }) .bind("127.0.0.1:8080")? .run() .await }
Создаем HTTP-сервер, который слушает на 127.0.0.1:8080 и использует функцию greet для обработки GET-запросов по адресу /. Функция greet возвращает простое приветствие.
Создадим обработчик, который принимает имя пользователя через путь запроса и возвращает персонализированное приветствие:
use actix_web::{web, App, HttpResponse, HttpServer, Responder}; async fn greet_user(name: web::Path<String>) -> impl Responder { HttpResponse::Ok().body(format!("Zdarova, {}!", name)) } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/greet/{name}", web::get().to(greet_user)) }) .bind("127.0.0.1:8080")? .run() .await }
Используем web::Path для извлечения параметра name из пути URL и возвращаем пользователю персонализированное сообщение.
Для работы с базами данных в Actix-web часто юзает асинхронные ORM, например, Diesel или sqlx. Используемsqlx для асинхронного взаимодействия с бдшкой PostgreSQL:
use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use sqlx::postgres::PgPoolOptions; use std::env; async fn get_user(pool: web::Data<sqlx::PgPool>) -> impl Responder { let row: (String,) = sqlx::query_as("SELECT name FROM users WHERE id = $1") .bind(1) .fetch_one(&**pool) .await .unwrap(); HttpResponse::Ok().body(format!("Hello, {}!", row.0)) } #[actix_web::main] async fn main() -> std::io::Result<()> { let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let pool = PgPoolOptions::new() .connect(&database_url) .await .expect("Failed to create pool."); HttpServer::new(move || { App::new() .app_data(web::Data::new(pool.clone())) .route("/user", web::get().to(get_user)) }) .bind("127.0.0.1:8080")? .run() .await }
Создался пул соединений к бд PostgreSQL и он используется для его для асинхронного выполнения запроса к бд в обработчике get_user.
Можно интегрироваться с API. Например с reqwest, можно асинхронно отправлять HTTP-запросы к внешним сервисам:
use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use reqwest::Client; async fn fetch_data() -> impl Responder { let client = Client::new(); let res = client.get("https://api.example.com/data") .send() .await .unwrap() .text() .await .unwrap(); HttpResponse::Ok().body(res) } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/fetch", web::get().to(fetch_data)) }) .bind("127.0.0.1:8080")? .run() .await }
Функция fetch_data использует reqwest для асинхронного получения данных от внешнего API и возвращает полученный ответ клиенту.
Для тех, кто желает узнать больше — Actix GitHub.
А про практические инструменты и другие ЯП лучше всего узнавать от практикующих экспертов, например, в рамках онлайн-курсов.
ссылка на оригинал статьи https://habr.com/ru/articles/795565/
Добавить комментарий