How to share immutable configuration data with hyper request handlers?

一世执手 提交于 2020-05-09 06:38:07

问题


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`

回答1:


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);
}


来源:https://stackoverflow.com/questions/55606450/how-to-share-immutable-configuration-data-with-hyper-request-handlers

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!