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`
The two levels of closures in
serve
are to be observed with care. The closure at the second level (which is passed toservice_fn_ok
), defined withmove
, will attempt to move the only instancecfg
into it, even before any call toclone()
is made. This move cannot be done multiple times without cloning, and therefore the closure will only implementFnOnce
. 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.