Parsing an url in Iron and getting a dynamic part - rust

I have 2 routes in Iron:
/something/:some_int # integer
/something2/:some_str # string
How can I get these :some_{int, str} parts of them?
fn my_something_int_route_handler(req: &mut Request) -> IronResult<Response> {
let ref query = req.extensions.get::<Router>().unwrap().find("query").unwrap_or("/");
//what next ???
// how can I get the ":some_int" from :/something/:some_int
fn my_something2_str_route_handler(req: &mut Request) -> IronResult<Response> {
let ref query = req.extensions.get::<Router>().unwrap().find("query").unwrap_or("/");
// how can I get the ":some_str" from :/something/:some_str

Use the router crate:
let mut router = Router::new();
router.get("/users/:user_id", user_show, "user_show");
let _server = Iron::new(router).http(("127.0.0.1", 8787)).unwrap();
Inside your handler, you get a reference to the Router's Params which lets you get the value for each named parameter. Note that the argument to Params::find matches the names assigned when the route was defined:
fn user_show(req: &mut Request) -> IronResult<Response> {
let router = req.extensions.get::<Router>()
.expect("Unable to get router");
user_id = router.find("user_id")
.expect("A user id is required");
}
One you have a parameter as string, you parse it into a number like anywhere else in Rust.

Related

Borrowed value does not live long enough when using async/await

I am trying to make two async api call and eventually get error E0597.
Here is a code:
async fn make_request() -> Result<()> {
.........
.........
.........
let mut result = client.get(uri).await?;
let some_key = result.headers().get("some_key");
let next_url = match some_key {
Some(url) => {
let some_result = client.get(Uri::from_static(url.to_str().unwrap())).await?
}
None => println!("....")
};
Ok(())
}
When I run this code the error "borrowed value does not live long enough argument requires that result is borrowed for `'static"
I have created a compile-able example based on your snipped to reproduce the error in the playground, and if you are able to do something like this in your question (for future reference), it usually helps you get more specific answers.
The Request passed into the function has no lifetime guarantees, so this will fail with the error you mentioned:
use http::{Request, Uri};
async fn make_request(result: &Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// `url` is a reference to the string in the "some_key" header
Some(url) => {
let some_result = Uri::from_static(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}
You can add that lifetime requirement, but that probably isn't what you need, and will likely give you the same error message, just in a different place:
async fn make_request_static(result: &'static Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// because the request is static, so can be `url`
Some(url) => {
let some_result = Uri::from_static(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}
Uri implements the FromStr trait, though, so you would be best off using that. There is no longer a lifetime requirement, so it can work with any string you pass in, even one which is currently borrowed:
// need to import the trait to use its methods
use std::str::FromStr;
async fn make_request_3(result: &Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// because the request is static, so can be `url`
Some(url) => {
let some_result = Uri::from_str(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}

how do you borrow a value during match in Rust?

I'm a new to Rust.
I created a structure to hold system information.
pub struct systemConfig {
pub admin_address: String,
pub engine_name: Option<String>,
pub group_name: Option<String>
}
I want to pass this structure to the make_msg function to create a json body and send it as a request to another server.
fn make_msg(config: systemConfig) -> String{
let (host_name, cpus) = get_system_info();
let engine_name = match config.engine_name {
Some(name) => name,
None => host_name.clone(),
};
let group_name = match config.group_name {
Some(name) => name,
None => String::from("")
};
let msg = json!({
"engineName": engine_name,
"groupName": group_name,
"hostName": host_name,
});
msg.to_string()
}
fn get_system_info() -> (String, usize){
use sysinfo::{ System, SystemExt };
// monitoring info
let mut my_system = System::new_all();
my_system.refresh_all();
// hostname
let hostname = get_hostname(&my_system);
// logical cpu count
let cpus = get_logical_cpus(&my_system);
(hostname, cpus)
}
I have two questions.
engine_name and group_name are values ​​obtained from process argument. The reason that type is defined as Option is that its value is not required. If the engine name is not entered, the hostname is filled in. And If the group name is not entered, it is sent as ""(empty String).
I used the match syntax, but is there a more appropriate syntax? (if let Some/None,,
more concise and intuitive)
None => host_name.clone(),
If clone() is not performed here, a borrow issue occurs. I'm looking for advice on whether using clone() is the right way, or if there is a better way.
I add test code
//cargo.toml
[dependencies]
sysinfo = "0.23.12"
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
use sysinfo::{System, SystemExt};
use serde_json::json;
struct systemConfig {
pub admin_address: String,
pub engine_name: Option<String>,
pub group_name: Option<String>
}
fn main() {
let config = systemConfig {
admin_address: String::from("127.0.0.1:8080"),
engine_name: Some(String::from("hello")),
group_name: Some(String::from("world"))
};
let msg = make_msg(config);
println!("msg: {}", msg);
}
fn make_msg(config: systemConfig) -> String{
let host_name = get_system_info();
let engine_name = match config.engine_name {
Some(name) => name,
None => host_name.clone(),
};
let group_name = match config.group_name {
Some(name) => name,
None => String::from("")
};
let msg = json!({
"engineName": engine_name,
"groupName": group_name,
"hostName": host_name,
});
msg.to_string()
}
fn get_system_info() -> String {
use sysinfo::{ System, SystemExt };
// monitoring info
let mut my_system = System::new_all();
my_system.refresh_all();
// hostname
let hostname = get_hostname(&my_system);
hostname
}
pub fn get_hostname(s: &System) -> String {
s.host_name().unwrap()
}
I used the match syntax, but is there a more appropriate syntax? (if let Some/None,, more concise and intuitive)
Option has a few utilities that you could use. In the engine_name case, unwrap_or_else() is less verbose than your match:
let engine_name = config.engine_name
.unwrap_or_else(|| host_name.clone());
For group_name you can use unwrap_or_default() since the Default implementation on String returns an empty string:
let group_name = config.group_name.unwrap_or_default();
Note that both of these options are superior in this case to unwrap_or() since they do not require building the alternative value unless it's needed. For example, in the engine_name case this won't clone host_name unless config.engine_name is None.
I'm looking for advice on whether using clone() is the right way, or if there is a better way.
You can make it work using only references like this:
let engine_name = match &config.engine_name {
Some(ref name) => name,
None => &host_name,
};
Or, like above, you can use unwrap_or() (combined with as_ref()):
let engine_name = config.engine_name.as_ref().unwrap_or(&host_name);
However, the JSON Value::String variant requires an owned string, so not cloning here isn't really an optimization -- the json! macro will just clone it anyway.
Two questions, two answers:
The situation of unwrapping or replacing an Option is common enough that it got its own function: Option::unwrap_or:
let engine_name = config.engine_name.unwrap_or(host_name.clone());
let group_name = config.group_name.unwrap_or(String::from(""));
Clone is the right way. In some situations, engineName and hostName will contain the same string, so a .clone() will be required at some point either way.

Calling async function inside of async function results in lifetime issues

The function run_av_check receives a vector filled with URLs which need to get called using reqwest. Iterating through the URLs gives me the error that the borrowed value does not live long enough. My understanding is that as long as check_availability is not finished for all URLs which are called in run_av_check using a for loop the run_av_check function is not finished, therefore the variable URLs should still exist during the runtime of all check_availibility calls.
I have read the Tokio and Rust documentation but I am not able to find a clue how and why this behaviour occurs. Why does the error pop up and what needs to be changed to make this work, both program wise and in my way of thinking?
async fn check_availability(host: &str) -> Result<reqwest::StatusCode, reqwest::Error> {
// ... building url
match client.get(&url).send().await {
Ok(r) => Ok(r.status()),
Err(_e) => Ok(reqwest::StatusCode::GATEWAY_TIMEOUT),
}
}
async fn run_av_check(urls: Vec<std::string::String>) -> Result<AvResponse, AvError> {
let mut av_urls: Vec<std::string::String> = vec![];
let mut s: std::string::String = "".to_string();
let mut attributes = Vec::new();
for url in urls.iter() {
attributes.push(("instance", url));
let resp = tokio::task::spawn(check_availability(url));
// ... processing result
let res = AvResponse {
av_urls,
prom_details: s,
};
Ok(res)
}
}
The problem is that you are starting independent tasks in tokio::task::spawn(check_availability(url));
This function takes only 'static references but your url refers to the local vector urls.
You need to make check_availability accept the String type (fn check_availability(host: String)) and call it like let resp = tokio::task::spawn(check_availability(url.to_string()));

How to convert closure to js_sys::Function?

How to convert a local closure into a js_sys::Function?
I want to do something like this:
let canvas = document.get_element_by_id("canvas").unwrap();
let e: web_sys::HtmlElement = canvas.dyn_into().unwrap();
let f = || {};
e.set_onresize(Some(&f.into()));
I have found a solution here:
let f = Closure::wrap(Box::new(move || { /* whatever */}) as Box<dyn FnMut()>);
e.set_onresize(Some(f.as_ref().unchecked_ref()));
f.forget(); // It is not good practice, just for simplification!
An answer can be found on the wasm_bindgen::closure::Closure page, as well.
use wasm_bindgen::{closure::Closure, JsCast};
let cb = Closure::new(|| { ... });
let cb = cb.as_ref().unchecked_ref();
The following way to set a closure as a callback is slightly shorter than shown in Hossein Noroozpour's answer. It's adapted from this example:
let closure: Closure<dyn FnMut()> = Closure::new(move || {
// Do something here
});
your_html_element.set_oninput(Some(closure.as_ref().unchecked_ref()));
closure.forget();
Here's a more complete example, showing how to set the callback on a file picker. The callback accesses a JavaScript worker:
let document = window().unwrap().document().unwrap();
let worker = Worker::new("./worker.js").unwrap();
let file_picker_elem_id = "file_picker";
match get_input_element(file_picker_elem_id, &document) {
Some(file_picker) => {
// We need "move" to access the worker inside the closure
let closure: Closure<dyn FnMut()> = Closure::new(move || {
log_to_browser(format!(
"The file picker callback. Worker: {worker:?}"
));
});
file_picker.set_oninput(Some(closure.as_ref().unchecked_ref()));
closure.forget();
}
None => log_to_browser(format!(
"Couldn't get file picker. Used \"{file_picker_elem_id}\" as its ID"
)),
}
/// Gets an [[HtmlInputElement]] by its `elem_id`.
fn get_input_element(elem_id: &str, document: &Document) -> Option<HtmlInputElement> {
document
.get_element_by_id(elem_id)
.and_then(|elem| elem.dyn_ref::<HtmlInputElement>().map(ToOwned::to_owned))
}
fn log_to_browser(log_msg: String) {
console::log_1(&log_msg.into());
}
I tried simplifying the conversion of closures to js_sys::Functions:
pub fn to_js_func<F>(closure: F) -> Option<&'static js_sys::Function>
where
F: IntoWasmClosure<dyn FnMut()> + 'static,
{
let big_closure: Closure<dyn FnMut()> = Closure::new(closure);
let func: &js_sys::Function = big_closure.as_ref().unchecked_ref();
big_closure.forget();
Some(func)
}
But this doesn't work since the function owns the created closure. Also, to use the type js_sys::Function, I needed to add the js-sys dependency to Cargo.toml. If anybody knows a way around this, please let us know.
A less universal helper function that works though:
fn set_on_input<F>(input_elem: &HtmlInputElement, closure: F)
where
F: IntoWasmClosure<dyn FnMut()> + 'static,
{
let big_closure: Closure<dyn FnMut()> = Closure::new(closure);
input_elem.set_oninput(Some(big_closure.as_ref().unchecked_ref()));
big_closure.forget();
}
This allows setting a Rust closure as the callback on the HTML element:
let closure = move || {
log_to_browser(format!(
"The file picker callback. Worker: {worker:?}"
));
};
set_on_input(&file_picker, closure);
Instead of a closure, you can also set a function as the callback:
set_on_input(&file_picker, test_callback);
Where test_callback is just a regular Rust function:
fn test_callback() {
log_to_browser("test_callback 🥳".into());
}

How to setup routes in Rouille by iterating a vector of routes

The Rouille hello world example shows how to use the router! macro for a fixed set of routes.
The following example code illustrates the need to be able to "bootstrap" routes from a database or from pluggable code - which I've currently been able to do with the Iron web framework:
pub struct Route {
pub http_method: String,
pub url_path: String,
pub callback_func: fn(_: &mut Request) -> IronResult<Response>,
}
impl Route {
pub fn new(m: String, u: String, f: fn(_: &mut Request) -> IronResult<Response>) -> Route {
Route {
http_method: m,
url_path: u,
callback_func: f,
}
}
}
fn main() {
// router is an Iron middleware
let mut router = Router::new();
// prepare routes for bootstrapping into the Iron router
let r1 = Route::new("get".to_string(), "/*".to_string(), my_callback_func);
let r2 = Route::new("get".to_string(), "/".to_string(), my_callback_func);
let mut routes = Vec::new();
routes.push(r1);
routes.push(r2);
for route in routes.iter_mut() {
if route.http_method == "get" {
// passes each route to the Iron router
router.get(&route.url_path, (&*route).callback_func);
} // else if, put, post, delete...
}
Iron::new(router).http("localhost:3000").unwrap();
}
fn my_callback_func(_: &mut Request) -> IronResult<Response> {
//...
}
(Playground)
Although I'm reading up on macros in Rust, I do not have a good enough understanding of Rouille's router! macro, Rust or macros in general, to figure out how to achieve the equivalent with Rouille.
If you examine the main source of the router macro, it's long-ish but not supremely complicated:
($request:expr, $(($method:ident) ($($pat:tt)+) => $value:block,)* _ => $def:expr) => {
{
let request = &$request;
// ignoring the GET parameters (everything after `?`)
let request_url = request.url();
let request_url = {
let pos = request_url.find('?').unwrap_or(request_url.len());
&request_url[..pos]
};
let mut ret = None;
$({
if ret.is_none() && request.method() == stringify!($method) {
ret = router!(__check_pattern request_url $value $($pat)+);
}
})+
if let Some(ret) = ret {
ret
} else {
$def
}
}
};
In words, it takes a request, zero-or-more patterns to match, and a default. It gets ahold of the URL, then dispatches to the other arms of the macro to see if the URL matches the path and does some recursive trickery to define some variables with components of the path. Whichever arm matches first will set the return value, and if nothing matches, the default will be used.
Unfortunately, the macro expects idents for the methods and the paths, so basically you cannot use it with expressions. This means we can't pass in variables or literals like "foo". This makes it very difficult for you.
So, we do what all good programmers do: copy and paste the code. Lifting chunks out of the macro and repurpose them:
#[macro_use]
extern crate rouille;
use rouille::Request;
use rouille::Response;
struct Route(&'static str, &'static str, fn(&Request) -> Response);
fn main() {
let routes = [
Route("GET", "/one", do_one),
Route("GET", "/two", do_two),
];
rouille::start_server("0.0.0.0:9080", move |request| {
let mut result = None;
let request = &request;
// ignoring the GET parameters (everything after `?`)
let request_url = request.url();
let request_url = {
let pos = request_url.find('?').unwrap_or(request_url.len());
&request_url[..pos]
};
for &Route(method, path, f) in &routes {
if result.is_none() {
// This checking of the path is terrible, limited, and hacky
if request.method() == method && request_url.ends_with(path) {
result = Some(f(request));
}
}
}
result.unwrap_or_else(|| Response::text("Default!"))
});
}
fn do_one(_: &Request) -> Response {
Response::text("hello world one")
}
fn do_two(_: &Request) -> Response {
Response::text("hello world two")
}
This runs the various handlers for /one, /two and everything else.
I'm no expert in Rouille, in fact I've never used it before today, but it certainly seems like you are trying to use it for something beyond what it is currently designed for. Perhaps this is deliberate and the authors are attempting to present a very opinionated tool. Perhaps it is accidental and they haven't thought of this use case. Perhaps it is temporary, and they just haven't gotten around to it.
Either way, I'd suggest asking the authors. If it's not an intended use-case, they can update the project docs to clearly state so. Otherwise they might create issues to implement the feature, and you could be instrumental in helping design it.

Resources