Как поделиться неизменяемыми данными конфигурации с обработчиками гиперзапросов? - PullRequest
1 голос
/ 10 апреля 2019

Я пытаюсь разработать гипер-серверное приложение на Rust.Существует INI-файл, содержащий конфигурацию, такую ​​как привязка IP, базы данных и так далее.

Я не хочу анализировать INI-файл при каждом запросе, и можно сохранять данные конфигурации до перезагрузки сервера.Как я могу передать структуру уже проанализированных данных обработчику запросов?

Я пробовал несколько подходов, таких как использование std::sync::Arc, но единственное, что работает до сих пор, это использование static, но я хочучтобы избежать unsafe блоков.

Вот полный (нерабочий) пример:

Cargo.toml

[package]
name = "demo"
version = "0.1.0"
edition = "2018"

[dependencies]
hyper = "0.12"
rust-ini = "0.13"

demo.ini

[Demo]
value="some value"

src / main.rs

extern crate hyper;
extern crate ini;

use hyper::rt::{self, Future};
use hyper::service::service_fn_ok;
use hyper::{Body, Request, Response, Server};
use ini::Ini;
use std::sync::Arc;

pub struct Config {
    pub value: String,
}

fn request_handler(req: Request<Body>, config: &Config) -> Response<Body> {
    let user_agent = req.headers()["user-agent"].to_str().unwrap();
    println!("user agent: {:?}", &user_agent);
    println!("config value: {:?}", &config.value);
    Response::new(Body::from(""))
}

fn read_ini(config_file: &str) -> Arc<Config> {
    match Ini::load_from_file(config_file) {
        Ok(c) => {
            let demo_section = c.section(Some("Demo".to_owned())).unwrap();
            let value = match demo_section.get("value") {
                Some(v) => v,
                None => {
                    println!("Error reading ini");
                    std::process::exit(-1)
                }
            };

            Arc::<Config>::new(Config {
                value: value.to_string(),
            })
        }
        _ => {
            eprintln!("CRITICAL: Could not open config file: {:?}", &config_file);
            std::process::exit(-1)
        }
    }
}

fn main() {
    let cfg = read_ini("demo.ini");
    let addr = "127.0.0.1:3000".parse().unwrap();

    let server = Server::bind(&addr)
        .serve(|| service_fn_ok(move |req: Request<Body>| request_handler(req, &cfg.clone())))
        .map_err(|e| println!("server error: {}", e));

    rt::run(server);
}

Ошибка

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> src/main.rs:49:16
   |
49 |         .serve(|| service_fn_ok(move |req: Request<Body>| request_handler(req, &cfg.clone())))
   |                ^^^^^^^^^^^^^^^^^-------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                |                |
   |                |                closure is `FnOnce` because it moves the variable `cfg` out of its environment
   |                this closure implements `FnOnce`, not `Fn`

1 Ответ

4 голосов
/ 10 апреля 2019

Два уровня закрытия в serve должны соблюдаться с осторожностью.Закрытие на втором уровне (которое передается на service_fn_ok), определенное с помощью move, будет пытаться переместить в него единственный экземпляр cfg, даже до того, как будет сделан любой вызов на clone().Этот шаг не может быть выполнен несколько раз без клонирования, и поэтому замыкание будет реализовано только FnOnce.Это случай double move : второе закрытие хочет получить ресурс в среде, которая позволяет ему сделать это только один раз.

Чтобы решить эту проблему, мы хотим первое закрытиеполучить cfg и клонировать его каждый раз там.

fn main() {
    let cfg = read_ini("demo.ini");
    let addr = "127.0.0.1:3000".parse().unwrap();

    let server = Server::bind(&addr)
        .serve(move || {
            let cfg = cfg.clone();
            service_fn_ok(move |req| request_handler(req, &cfg))
        })
        .map_err(|e| println!("server error: {}", e));

    rt::run(server);
}
...