Server-side Web

5 януари 2023

Административни неща

За боба, 'леба и уеба

За боба, 'леба и уеба

За боба, 'леба и уеба

За боба, 'леба и уеба

Накратко как работи Интернета:

За боба, 'леба и уеба

Накратко как работи Интернета:

За боба, 'леба и уеба

Накратко как работи Интернета:

За боба, 'леба и уеба

Накратко как работи Интернета:

За боба, 'леба и уеба

Накратко как работи Интернета:

За боба, 'леба и уеба

Накратко как работи Интернета:

За боба, 'леба и уеба

Накратко как работи Интернета:

За боба, 'леба и уеба

Сървъра е просто един цикъл, който чака низова информация в определен формат и връща низова информация в определен формат. Може да го напишем на shellscript, ако искаме (но ще го пишем на Rust).

Разбира се, в реални условия е доста по-сложно да се докарат всички детайли.

(Тия обяснения вероятно не са достатъчни за начинаещ, но поне не са нищо. ¯\_(ツ)_/¯)

Hello Web

Demo

https://github.com/AndrewRadev/hello-rusty-web

Hello Web

Demo

Версии:

1 2
[dependencies]
actix-web = "4"

Бихме могли вместо #[actix_web::main] да ползваме #[tokio::main] с всичките стандартни инструменти на Tokio. Има някаква част от actix-web environment-а, която все още го изисква, но става въпрос за websockets, които са малко странична история.

Още детайли: #[actix_web::main] and #[tokio::main]

Hello Web

Demo

Hello Web

Demo

Hello Web

Demo

Hello Web

Demo

Hello Web

Demo

Actix-web

Extractor magic: https://docs.rs/actix-web/4.2.1/actix_web/trait.Handler.html.

Spotiferris

Ще разгледаме (началото на) малък проект за хостинг на музика. Stack-а:

Source: https://github.com/AndrewRadev/rust-spotiferris

Database

1
cargo install sqlx-cli --no-default-features --features rustls,postgres

Gotchas

1 2
match form.insert(&db) {
    Ok(id) => {
1 2 3 4 5 6 7 8 9 10 11 12 13
error[E0308]: mismatched types
  --> src/handlers.rs:86:13
   |
86 |             Ok(id) => {
   |             ^^^^^^ expected opaque type, found enum `std::result::Result`
   |
  ::: src/models.rs:40:48
   |
40 |     pub async fn insert(&self, db: &PgPool) -> Result<i32, sqlx::Error> {
   |                                                ------------------------ the `Output` of this `async fn`'s expected opaque type
   |
   = note: expected opaque type `impl futures::Future`
                     found enum `std::result::Result<_, _>`

Gotchas

1 2
match form.insert(&db).await {
    Ok(id) => {

All good!

Gotchas

Ако забравим async за handler-а, грешката не е много готина:

1
pub fn index(db: web::Data<PgPool>) -> HttpResponse {
1 2 3 4
route("/", web::get().to(handlers::songs::index)).
                      -- ^^^^^^^^^^^^^^^^^^^^^^ the trait `Handler<_>` is not implemented for fn item `fn(Data<Pool<Postgres>>) -> actix_web::HttpResponse {songs::index}`
                      |
                      required by a bound introduced by this call

Axum (друг фреймуърк) има #[axum::debug_handler] макрос, който помага с грешките, което е някакво подобрение, I guess.

Gotchas

Разни компилаторни грешки може да препоръчат NamedFile + статус код да се имплементира с customize().with_status(. Хубаво, ама не става съвсем:

1 2 3
pub async fn render_404(request: HttpRequest) -> Result<HttpResponse> {
    let file = NamedFile::open("static/404.html")?;
    Ok(file.customize().with_status(StatusCode::NOT_FOUND).respond_to(&request))
1 2 3 4 5 6 7 8 9 10
error[E0308]: mismatched types
   --> src/handlers.rs:24:8
    |
24  |     Ok(file.customize().with_status(StatusCode::NOT_FOUND).respond_to(&request))
    |     -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `BoxBody`, found enum `EitherBody`
    |     |
    |     arguments to this enum variant are incorrect
    |
    = note: expected struct `actix_web::HttpResponse<BoxBody>`
               found struct `actix_web::HttpResponse<EitherBody<BoxBody>>`

Gotchas

Ако пробваме impl Responder? Пак не става:

1 2 3
pub async fn render_404(request: HttpRequest) -> Result<impl Responder> {
    let file = NamedFile::open("static/404.html")?;
    Ok(file.customize().with_status(StatusCode::NOT_FOUND).respond_to(&request))
1 2 3 4 5 6 7 8 9 10 11
error[E0308]: mismatched types
   --> src/handlers.rs:126:23
    |
22  | pub async fn render_404(request: HttpRequest) -> Result<impl Responder> {
    |                                                         -------------- the found opaque type
...
126 |             Err(_) => render_404(request).await,
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `actix_web::HttpResponse`, found opaque type
    |
    = note: expected enum `std::result::Result<actix_web::HttpResponse, _>`
               found enum `std::result::Result<impl Responder, _>`

Gotchas

Твърде много типова магия. Едно решение е да спрем да се лигавим с композиране на типове и просто да си направим response, който да мутираме:

1 2 3 4 5 6 7 8
pub async fn render_404(request: HttpRequest) -> Result<HttpResponse> {
    let file = NamedFile::open_async("static/404.html").await?;
    let mut response = file.into_response(&request);

    *response.status_mut() = StatusCode::NOT_FOUND;

    Ok(response)
}

Auto-reload

1 2
cargo install cargo-watch
cargo watch -x 'run --bin server'

Testing

Най-лесно ако използвате sqlx е #[sqlx::test] анотацията на тестовете: https://docs.rs/sqlx/latest/sqlx/attr.test.html.

Проблема е, че ако просто си инстанцирате тестова база данни на ръка (което е вариант), тестовете ще вървят паралелно и ще я мажат заедно. Може да го спрете с флаг на cargo test (или с този crate: https://crates.io/crates/serial_test):

1
cargo test -- --test-threads=1

Но нали, ако можете да ползвате sqlx::test, best to just go for it. Метода му е същия, който Luca Palmieri описва в книгата си.

Spotiferris

Проблеми

Spotiferris

Проблеми

Spotiferris

Проблеми

Spotiferris

Проблеми

Spotiferris

Проблеми

Ресурси

Ресурси

Ресурси

Ресурси

Ресурси

Ресурси

Проекти?

Проекти?

Проекти?

Проекти?

Проекти?

Проекти?

Въпроси