How to share immutable configuration data with hyp

2020-04-13 10:36发布

I am trying to develop a hyper based server application in Rust. There is an INI file holding configuration like binding IP, database and so on.

I don't want to parse the INI file on each request and it is OK to keep the configuration data until server restart. How can I give a struct of already parsed data to the request handler?

I have tried several approaches like using std::sync::Arc, but the only thing working so far is to use a static, but I want to avoid unsafe blocks.

Here is a complete (non working) example:

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

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`

标签: rust hyper
1条回答
霸刀☆藐视天下
2楼-- · 2020-04-13 11:33

The two levels of closures in serve are to be observed with care. The closure at the second level (which is passed to service_fn_ok), defined with move, will attempt to move the only instance cfg into it, even before any call to clone() is made. This move cannot be done multiple times without cloning, and therefore the closure will only implement FnOnce. This is a case of double move: the second closure wants to receive a resource in an environment that only allows it to do that once.

To solve this problem, we want the first closure to receive cfg, and clone it each time there.

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);
}
查看更多
登录 后发表回答