Введення в Iron: плавим руду на Rust


Iron — це високорівненвий веб-фреймворк, написаний на мові програмування Rust і побудований на базі іншої відомої бібліотеки hyper. Iron розроблений таким чином, щоб користуватися всіма перевагами, які надає нам Rust.

Філософія
Iron побудований на принципі розширюваності настільки, наскільки це можливо.
Він вводить поняття для розширення власного функціоналу:
  • "проміжні" типажі — використовуються для реалізації наскрізного функціоналу в обробці запитів;
  • модифікатори використовуються для зміни запитів і відповідей найбільш ергономічним
    способом.
З базовою частиною модифікаторів і проміжних типажів ви познайомитеся в ході статті.
Офіційний сайт Iron
Створення проекту
Для початку створимо проект з допомогою Cargo, використовуючи команду:
cargo new rust-iron-tutorial --bin

Далі додамо в розділ
[dependencies]
file
Cargo.toml
залежність
iron = "0.4.0"
.
Пишемо першу програму з використанням Iron
Напишемо першу простеньку програму на Rust з використанням Iron, яка на будь-які запити по порту 3000 буде відповідати текстом "Hello habrahabr!".
extern crate iron;
use iron::prelude::*;
use iron::status;
fn main() {
Iron::new(|_: &mut Request| {
Ok(Response::with((status::Ok, "Hello habrahabr!\n")))
}).http("localhost:3000").unwrap();
}

Запустіть код за допомогою команди
cargo run
і після того, як компіляція завершиться і програма запуститься, протестуйте сервіс, наприклад, за допомогою curl:
[loomaclin@loomaclin ~]$ curl localhost:3000
Hello habrahabr!

Давайте розберемо програму, щоб розуміти, що тут відбувається. У першому рядку програми імпортується пакет
iron
.
У другому рядку був підключений модуль-прелюдія, що містить набір найбільш важливих типажів, таких як
Request
,
Response
,
IronRequest
,
IronResult
,
IronError
та
Iron
. У третьому рядку підключається модуль
status
, що містить списки кодів для відповідей на запити.
Iron::new
створює новий інстанси Iron'а, який, у свою чергу, є базовим об'єктом вашого сервера. Він приймає параметром об'єкт, що реалізує типаж
Handler
. У нашому випадку ми передаємо замикання, аргументом якого є змінна посилання на переданий запит.
Вказуємо mime-type в заголовку відповіді
Найчастіше при побудові веб-сервісів (SOAP, REST) потрібно відсилати відповіді із зазначенням типу контенту, який вони містять. Для цього в Iron передбачені спеціальні засоби.
Виконаємо наступне.
Підключаємо відповідну структуру:
use iron::mime::Mime;

Пов'язуємо ім'я
content_type
, яка буде зберігати распарсенное за допомогою підключеного типажу
Mime
значення типу:
let content_type = "application/json".parse::<Mime>().unwrap();

Модифікуємо рядок відповіді на запит наступним чином:
Ok(Response::with((content_type, status::Ok, "{}")))

Запускаємо програму і перевіряємо працездатність:
[loomaclin@loomaclin ~]$ curl -v localhost:3000
* Rebuilt URL to: localhost:3000/
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Tue, 12 Jul 2016 19:53:21 GMT
< Content-Length: 2
<
* Connection #0 to host localhost left intact
{}

Керування статус-кодами відповідей
У перерахуванні
StatusCode
, розташованому в модулі
status
, розташовуються всілякі статус-коди. Давайте скористаємося цим і повернемо "клієнтові" помилку 404 —
NotFound
, змінивши рядок з формуванням відповіді на запит:
Ok(Response::with((content_type, status::NotFound)))

Перевірка:
[loomaclin@loomaclin ~]$ curl -v localhost:3000
* Rebuilt URL to: localhost:3000/
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Length: 2
< Content-Type: application/json
< Date: Tue, 12 Jul 2016 20:55:40 GMT
<
* Connection #0 to host localhost left intact

Примітка: по суті, весь модуль
status
є обгорткою для відповідних перерахувань у бібліотеці
hyper
, на якій базується
iron
.
Перенаправлення запитів
Для редіректу
iron
використовується структура
Redirect
з модуля
modifiers
(не плутати з
modifier
). Вона складається з URL мети, куди необхідно буде зробити перенаправлення.
Спробуємо її застосувати, виконавши наступні зміни:
Підключаємо структуру
Redirect
:
use iron::modifiers::Redirect;

До підключення модуля
status
додаємо підключення модуля
Url
:
use iron::{Url, status};

Пов'язуємо ім'я
url
, яка буде зберігати распарсенное значення адреси редіректу:
let url = Url::parse("https://habrahabr.ru/").unwrap();

Міняємо блок ініціалізації Iron наступним чином:
Iron::new(move |_: &mut Request | {
Ok(Response::with((status::Found, Redirect(url.clone ()))))
}).http("localhost:3000").unwrap();

Перевіряємо результат:
[loomaclin@loomaclin ~]$ curl -v localhost:3000
* Rebuilt URL to: localhost:3000/
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Location: https://habrahabr.ru/
< Date: Tue, 12 Jul 2016 21:39:24 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

Також ви можете скористатися ще однією структурою
RedirectRaw
з модуля
modifiers
, для конструювання якої вимагається лише рядок.
Робота з типом http-запиту
У структури
Request
є поле
method
, що дозволяє визначати тип прийшов http-запиту.
Напишемо сервіс, який буде зберігати у файл дані, передані в тілі запиту з типом
Put
,
зчитувати дані з файлу і передавати їх у відповіді на запит з типом
Get
.
Аннотируем імпортований контейнер
iron
атрибутом
macro_use
, щоб надалі використовувати макроси
iexpect
та
itry
для обробки помилкових ситуацій:
#[macro_use]
extern crate iron;

Підключаємо модулі для роботи з файловою системою та введенням/виведенням із стандартної бібліотеки:
use std::io;
use std::fs;

Підключаємо модуль
method
, що містить список типів запитів http:
use iron::method;

Міняємо блок ініціалізації
Iron
таким чином, щоб зв'язати отриманий запит з ім'ям
req
:
Iron::new(|req: &mut Request| {
...
...
...
}.http("localhost:3000").unwrap();

Додаємо в обробку запиту зіставлення зі зразком поля
method
для двох типів запитів
Get
та
Put
, а для решти будемо використовувати відповідь у вигляді статус-коду
BadRequest
:
Ok(match req.method {
method::Get => {
let f = iexpect!(fs::File::open("foo.txt").ok(), (status::Ok, ""));
Response::with((status::Ok, f))
},
method::Put => {
let mut f = itry!(fs::File::create("foo.txt"));
itry!(io::copy(&mut req.body &mut f));
Response::with(status::Created)
},
_ => Response::with(status::BadRequest)
}

Iron
макрос
iexcept
використовується для розгортання переданого в нього об'єкта типу
Option
та в разі, якщо
Option
містить
None
макрос повертає
Ok(Response::new())
з модифікатором за замовчуванням
status::BadRequest
.
Макрос
itry
використовується для обгортання помилки в
IronError
.
Пробуємо запустити і перевірити працездатність.
PUT:
[loomaclin@loomaclin ~]$ curl -X PUT -d my_file_content localhost:3000
[loomaclin@loomaclin ~]$ cat ~/IdeaProjects/cycle/foo.txt
my_file_content

GET:
[loomaclin@loomaclin ~]$ curl localhost:3000
my_file_content

POST:
[loomaclin@loomaclin ~]$ curl -X POST -v localhost:3000
* Rebuilt URL to: localhost:3000/
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> POST / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Content-Length: 0
< Date: Tue, 12 Jul 2016 22:29:58 GMT
<
* Connection #0 to host localhost left intact

Реалізація наскрізного функціоналу за допомогою пре — і пост-процесингу
iron
також присутні типажі
BeforeMiddleware
,
AfterMiddleware
та
AroundMiddleware
для наскрізного функціоналу,
дозволяють реалізовувати логіку по обробці запиту до того, як вона почалася в основному процесорі, і після того, як вона там завершилася.
Напишемо приклад використання AOP'про подібні зазначених типажів:
Код прикладу
extern crate iron;

use iron::prelude::*;
use iron::{BeforeMiddleware, AfterMiddleware, AroundMiddleware, Handler};

struct SampleStruct;
struct SampleStructAroundHandler<H:Handler> { logger: SampleStruct, handler: H}

impl BeforeMiddleware for SampleStruct {
fn before(&self, req: &mut Request) -> IronResult<()> {
println!("До обробки запиту.");
Ok(())
}
}

impl AfterMiddleware for SampleStruct {
fn after(&self, req: &mut Request, res: Response) -> IronResult<Response> {
println!("Після обробки запиту.");
Ok(res)
}
}

impl<H: Handler> Handler for SampleStructAroundHandler<H> {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
println!("А я ще один обробник запиту.");
let res = self.handler.handle(req);
res
}
}

impl AroundMiddleware for SampleStruct {
fn around(self, handler: Box<Handler>) -> Box<Handler> {
Box::new(SampleStructAroundHandler {
logger: self,
handler: handler
}) as Box<Handler>
}
}

fn sample_of_middlewares(_:&mut Request) -> IronResult<Response> {
println!("В основному процесорі запиту.");
Ok(Response::with((iron::status::Ok, "Привіт, я відповідь на запит!")))
}

fn main() {
let mut chain = Chain::new(sample_of_middlewares);
chain.link_before(SampleStruct);
chain.link_after(SampleStruct);
chain.link_around(SampleStruct);
Iron::new(chain).http("localhost:3000").unwrap();
}

У цьому прикладі вводиться структура
SampleStruct,
для якої реалізуються типажі
BeforeMiddleware
з функцією
before
та
AfterMiddleware
з функцією
after
. З їх допомогою може бути реалізована вся наскрізна логіка. Типаж
AroundMiddleware
використовується спільно з типажем
Handler
для додавання додаткового обробника. Додавання всіх реалізованих
обробників в життєвий цикл обробки запиту проводиться за допомогою спеціального типажу
Chain
, що дозволяє формувати ланцюг викликів пре — і пост-обробників.
Протестуємо програму.
В консолі:
[loomaclin@loomaclin ~]$ curl localhost:3000
Привіт, я відповідь на запит!

У виведенні програми:
Running `target/debug/cycle`
До обробки запиту.
А я ще один обробник запиту.
В основному процесорі запиту.
Після обробки запиту.

Роутинг
Яке серверне API може обійтися без роутінга? Додамо його =) Модифікуємо наш базовий приклад з початку статті наступним чином.
Підключаємо колекцію із стандартної бібліотеки:
use std::collections:HashMap;

Оголосимо структуру для зберігання колекції виду "шлях — обробник" і опишемо для цієї структури конструктор, який буде проводити ініціалізацію цієї колекції, і функцію для додавання в колекцію нових роутов з їх обробниками:
struct Router {
routes: HashMap<String, Box<Handler>>
}

impl Router {

fn new() -> Self {
Router {
routes: HashMap::new()
}
}

fn add_route<H>(&mut self, path: String, handler: H) where H: Handler {
self.routes.insert(path, Box::new(handler));
}
}

Для використання нашої структури в зв'язці з
Iron
необхідно реалізувати для неї типаж
Handler
з функцією
handle
:
impl Handler for Router {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
match self.routes.get(&req.url.path().join("/")) {
Some(handler) => handler.handle(req),
None => Ok(Response::with(status::NotFound))
}
}
}

У функції
handle
ми за переданим в запиті шляху знаходимо відповідний обробник в колекції і викликаємо обробник цього шляху з передачею в нього запиту. У разі якщо переданий у запиті шлях не "зареєстрований" у колекції — повертається відповідь з кодом помилки
NotFound
.
Останнє, що залишилося реалізувати, — це ініціалізація нашого роутера та реєстрація в ньому необхідних нам шляхів з їх обробниками:
fn main() {
let mut router = Router::new();

router.add_route("hello_habrahabr".to_string(), |_: &mut Request| {
Ok(Response::with((status::Ok, "Hello Loo Maclin!\n")))
});

router.add_route("hello_habrahabr/again".to_string(), |_: &mut Request| {
Ok(Response::with((status::Ok, "Ти повторяешься!\n")))
});

router.add_route("error".to_string(), |_: &mut Request| {
Ok(Response::with(status::BadRequest))
});
...

Додавання нових шляхів відбувається шляхом виклику реалиованной вище функції.
Ініціалізуємо інстанси
Iron
з використанням нашого роутера:
Iron::new(router).http("localhost:3000").unwrap();

Тестуємо:
[loomaclin@loomaclin ~]$ curl localhost:3000/hello_habrahabr
Hello Loo Maclin!
[loomaclin@loomaclin ~]$ curl localhost:3000/hello_habrahabr/again
Ти повторяешься!
[loomaclin@loomaclin ~]$ curl -v localhost:3000/error
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET /error HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Date: Wed, 13 Jul 2016 21:29:20 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

Висновок
На цьому стаття підходить до кінця.
Крім розглянутого функціоналу, Iron виносить велику частину типовою для веб-фреймворку функціональності за базовим розширень:
Стаття призначена для базового ознайомлення з Iron, і хочеться сподіватися, що вона справляється з цією метою.
Дякую за увагу!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.