Как можно перенести данные из приложения AXUM в вектор?

Как отправить данные на сервер AXUM с клиента через POST-запрос, используя метод fetch, и сохранить их в векторе в разделе <i>Shared mutable state</i>? Я использую тип String для хранения данных, как рекомендовано в документации _docs.rs/axum/latest/axum/extract/struct.State.html, но столкнулся с проблемой, когда изменил тип на вектор в структуре. Можете подсказать, как исправить код? Также интересуют сценарии с точки зрения производительности в случаях: когда вектор только читается и когда он читается и изменяется. Кроме того, будет ли обработка данных быстрее, если вектор будет хранить данные в виде &str вместо String?
  • 16 декабря 2024 г. 12:13
Ответы на вопрос 2
Для переноса данных из приложения на Axum в вектор и их сохранения в состоянии, рекомендуется использовать `Arc<Mutex<Vec<String>>>` или похожую конструкцию. Давайте рассмотрим пример того, как это можно сделать.

### Шаг 1: Определение состояния

Сначала вам нужно определить структуру состояния, которая будет хранить ваш вектор.

```rust
use axum::{
    extract::{Extension, Json},
    response::IntoResponse,
    routing::post,
    Router,
};
use std::{sync::{Arc, Mutex}, collections::VecDeque};
use serde_json::Value;

#[derive(Debug)]
struct AppState {
    data: Vec<String>,
}

type SharedState = Arc<Mutex<AppState>>;
```

### Шаг 2: Обработка POST-запросов

Далее, создайте обработчик для обработки входящих POST-запросов и извлечения данных.

```rust
async fn add_data(
    Extension(state): Extension<SharedState>,
    Json(payload): Json<String>,
) -> impl IntoResponse {
    let mut state = state.lock().unwrap();
    state.data.push(payload);
    (axum::http::StatusCode::OK, "Data added")
}
```

### Шаг 3: Настройка маршрутов

Теперь создайте маршруты и начните сервер.

```rust
#[tokio::main]
async fn main() {
    let state = Arc::new(Mutex::new(AppState { data: Vec::new() }));

    let app = Router::new()
        .route("/add", post(add_data))
        .layer(Extension(state));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}
```

### Шаг 4: Отправка данных с клиента

Используйте метод `fetch` для отправки данных на сервер. Например, вы можете использовать следующий код на JavaScript:

```javascript
async function sendData(data) {
    const response = await fetch('http://localhost:3000/add', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
    });

    if (!response.ok) {
        console.error('Error sending data:', response.statusText);
    }
}
```

### Производительность

1. **Чтение данных:** Если ваш вектор используется только для чтения (например, в обработчиках GET-запросов), использование `Arc<Mutex<...>>` будет приемлемым, так как только один поток будет получать блокировку для чтения.

2. **Запись и чтение данных:** Когда данные также изменяются, могут возникнуть проблемы с производительностью, так как `Mutex` будет блокировать доступ к вектору. Чтобы избежать этого, можно рассмотреть использование других подходов, таких как `RwLock`, который позволяет нескольким потокам одновременно читать данные.

### Хранение данных в виде `&str` или `String`

Хранение данных в виде `&str` вместо `String` может привести к сложностям, так как `&str` является срезом строки и ему нужен запас (например, `String`), чтобы иметь возможность изменять содержимое. `String` позволяет вам динамически изменять или добавлять новые элементы. Если необходимо использовать тип `&str`, вам придется хранить его в некотором виде, например, в строках внутри `String`.

В общем, для хранения изменений, которые могут происходить многопоточно, рекомендуется использовать `Arc<Mutex<Vec<String>>>`.
Кажется, ты просто в своих же типах запутался. Всё работает (ниже работающий код) 

Подскажите, пожалуйста, как исправить код и есть ли разные решения с точки зрения скорости работы для случаев:
- такой вектор только читают
- вектор читают и изменяют.

Если только читают, то 1 раз его проинициализировал, а в стейте пусть лежит Arc без mutex.
Если и читают и пишут, то RwLock, но стоит подумать над структурами данных, тк у тебя ради добавления одной записи блокируется весь список. Что-нибудь на основе связных списков например позволит не блочить весь вектор.

use axum::extract::State;
use axum::http::StatusCode;
use axum::routing::{get, post};
use axum::{Json, Router};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};

#[derive(Debug, Clone)]
struct AppState {
    d: Arc<Mutex<Vec<String>>>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let state = AppState {
        d: Arc::new(Mutex::new(vec![])),
    };

    let app = Router::new()
        .route("/info", post(create_user))
        .route("/list_users", get(list_users))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();

    Ok(())
}

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

#[derive(Serialize)]
pub struct User {
    username: String,
}

async fn create_user(
    State(state): State<AppState>,
    Json(payload): Json<FromBrowser>,
) -> (StatusCode, Json<User>) {
    // ...
    let user = User {
        username: payload.username,
    };

    {
        let mut d = state.d.lock().expect("mutex was poisoned");
        d.push(user.username.to_owned());
    }
    println!("state = {:?}", state);
    (StatusCode::CREATED, Json(user))
}

async fn list_users(State(state): State<AppState>) -> (StatusCode, Json<Vec<String>>) {
    let users = state.d.lock().expect("mutex was poisoned").clone();
    (StatusCode::OK, Json(users))
}
Похожие вопросы