如何与超级请求处理程序共享不可变的配置数据?

How to share immutable configuration data with hyper request handlers?

我正在尝试用 Rust 开发一个基于 hyper 的服务器应用程序。有一个 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`

serve 中的两个级别的关闭要小心观察。 第二层的闭包(传递给 service_fn_ok),用 move 定义,将尝试将唯一的实例 cfg 移动到其中,甚至在任何调用 [=15= 之前] 制作。 如果不克隆,这个移动不能多次完成,因此闭包将只实现 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);
}