I'm trying to make a web server with a prometheus endpoint to put out metrics on my other pages. I'm running into an issue with collecting those metrics. It stems from warp seemingly executing each route on every request, with every route that that request doesn't match returning a 404.
Here's a toy example:
https://github.com/myaple/warp-test/tree/master/src
main.rs
extern crate warp;
extern crate tokio;
extern crate fern;
use warp::Filter;
#[tokio::main]
async fn main() {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
))
})
.level(log::LevelFilter::Debug)
.chain(std::io::stdout())
.apply();
let hlog = warp::log("hlog");
let glog = warp::log("glog");
// GET /hello/warp => 200 OK with body "Hello, warp!"
let hello = warp::path!("hello" / String)
.map(|name| format!("Hello, {}!", name))
.with(hlog);
let goodbye = warp::path!("goodbye")
.map(|| format!("goodbye"))
.with(glog);
let routes = hello.or(goodbye);
warp::serve(routes)
.run(([127, 0, 0, 1], 8080))
.await;
}
With this example, if I hit localhost:8080/goodbye, I also see a return of 404 in the logs from hlog, which is attached to /hello:
[2022-06-27][18:57:59][warp::server][INFO] Server::run; addr=127.0.0.1:8080
[2022-06-27][18:57:59][warp::server][INFO] listening on http://127.0.0.1:8080
[2022-06-27][18:58:04][hyper::proto::h1::io][DEBUG] parsed 12 headers
[2022-06-27][18:58:04][hyper::proto::h1::conn][DEBUG] incoming body is empty
[2022-06-27][18:58:04][hlog][INFO] 127.0.0.1:62698 "GET /goodbye HTTP/1.1" 404 "-" "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0" 68µs
[2022-06-27][18:58:04][glog][INFO] 127.0.0.1:62698 "GET /goodbye HTTP/1.1" 200 "-" "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0" 25.3µs
[2022-06-27][18:58:04][hyper::proto::h1::io][DEBUG] flushed 123 bytes
[2022-06-27][18:58:04][hyper::proto::h1::io][DEBUG] parsed 11 headers
[2022-06-27][18:58:04][hyper::proto::h1::conn][DEBUG] incoming body is empty
[2022-06-27][18:58:04][hyper::server::server::new_svc][DEBUG] connection error: connection closed before message completed
Similar to this example, if I attach a function in a .with() that increments a counter for every 404 I generate, every good request matched by a route is also sending back multiple 404s from every other route that it didn't match with.
Is there a good way to weed out these requests so they either do not execute, or is there some functionality I'm missing that allows me to not log and not observe metrics on unmatched routes?
As cdhowie pointed out in the comment, you can just add the log filter at the top level pipeline:
extern crate warp;
extern crate tokio;
extern crate fern;
use warp::Filter;
#[tokio::main]
async fn main() {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
))
})
.level(log::LevelFilter::Debug)
.chain(std::io::stdout())
.apply();
let access_log = warp::log("access_log");
// GET /hello/warp => 200 OK with body "Hello, warp!"
let hello = warp::path!("hello" / String)
.map(|name| format!("Hello, {}!", name));
let goodbye = warp::path!("goodbye")
.map(|| format!("goodbye"));
let routes = hello.or(goodbye).with(access_log);
warp::serve(routes)
.run(([127, 0, 0, 1], 8080))
.await;
}
Related
I have a code to check a network connection like this
fn network_connection() {
loop {
let output = Command::new("ping")
.arg("-c")
.arg("1")
.arg("google.com")
.output()
.expect("[ ERR ] Failed to execute network check process");
let network_status = output.status;
if network_status.success() == false {
Command::new("init")
.arg("6");
}
thread::sleep(Duration::from_millis(60000));
}
}
fn main() {
thread::spawn(|| {
network_connection();
});
...
this should ping google every 60 seconds. But when I'm looking at number of request sent to router its like 200 requests per 10 minutes.
Is this spamming more threads than one?
main() is running only once.
I'm making a proxy server using tokio and hyper. Depending on the request, there can be multiple proxy servers(can send request). I have the addresses of the servers to proxy as a vector and try to connect in turn.
For example, when a GET /test HTTP/1.1 request is received, there are proxy server candidates named ["aaa.com", "bbb.com", "ccc.com"]. My server tries to connect in the order aaa -> bbb -> ccc and sends a proxy request to the server where the connection succeeds. If it cannot connect to all servers, a 500 error is returned.
The problem is that the same request must be sent until the connection to the server is successful, but if use hyper's method, the request is consumed and a problem occurs in the next loop. But I couldn't find a way to copy hyper::Request (clone, etc.) How can I copy a hyper::Request in Rust?
fn proxy(
mut req: Request<hyper::Body>,
) -> BoxFuture<'static, Response<hyper::Body>> {
let route = req
.extensions_mut()
.remove::<RoutingInfo>()
.expect("route not found.");
let size = route.api_config.target_servers.len();
Box::pin(async move {
let mut index = 1;
let resp = loop {
let target_server = schedule_server(&route);// get "aaa.com" or "bbb.com" or "ccc.com"
let https = HttpsConnectorBuilder::new()
.with_native_roots()
.https_or_http()
.enable_http1()
.build();
let client = Client::builder().build::<_, RawBody>(https);
let destination = format!("{}{}", target_server, route.target_uri);
*req.uri_mut() = destination.parse::<Uri>().unwrap();
match client.request(req).await {
Ok(resp) => {
break resp;
},
Err(_e) => {//TODO: only connection error
if index == size {
break Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(hyper::Body::empty())
.unwrap();
}
index++;
}
};
};
resp
})
}
error[E0382]: borrow of moved value: `req`
--> src/service/proxy.rs:83:14
|
83 | *req.uri_mut() = destination.parse::<Uri>().unwrap();
| ^^^^^^^^^^^^^ value borrowed here after move
...
87 | match client.request(req).await {
| --- value moved here, in previous iteration of loop
I am trying to run two app (one to admin on port 3006 and another to serve data on port 8080).
They shared database pool, cache...
For actix 1.0 i had this working (i don't know if it's the best way to do that) :
let server = Server::build()
// FIRST APP
.bind("", "0.0.0.0:3006", move || {
HttpService::build().finish({
App::new()
.wrap(actix_Logger::new(
"WMTS %a %r %s %b %{Referer}i %{User-Agent}i %T",
))
.data(pool.clone())
.data(cache.clone())
.service(
web::scope("/utils")
.route(
"one",
web::get().to(api::one),
)
.route("two", web::get().to(api::two))
)
.service(
web::scope("/data")
.route("get_data", web::get().to(api::get_data)),
)
})
})
.unwrap()
// SECOND APP
.bind("", "0.0.0.0:8080", move || {
HttpService::build().finish(
App::new()
.wrap(actix_Logger::new(
"API %a %r %s %b %{Referer}i %{User-Agent}i %T",
))
.data(pool.clone())
.data(cache.clone())
.service(web::resource("/graphql").route(web::post().to(api::graphql)))
.service(web::resource("/health").route(web::get().to(api::health)))
.service(web::resource("/metrics").route(web::get().to(api::metrics))),
)
})
.unwrap();
server.run()?;
But how to make it work with actix 2.0 ?
As far as actix-web's own API is concerned, there really isn't much changed between 1.0 and 2.0. That's a good thing since you still have the familiar API at your disposal to configure routes, application data, logger, etc.
One thing that does change is actix-web has moved to async / await. Your application needs to adapt to it as well:
//# actix-rt = "1.0"
//# actix-web = "2.0"
//# futures = "0.3"
use actix_web::{web, App, HttpServer, Responder};
use futures::future;
async fn utils_one() -> impl Responder {
"Utils one reached\n"
}
async fn health() -> impl Responder {
"All good\n"
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
let s1 = HttpServer::new(move || {
App::new().service(web::scope("/utils").route("/one", web::get().to(utils_one)))
})
.bind("0.0.0.0:3006")?
.run();
let s2 = HttpServer::new(move || {
App::new().service(web::resource("/health").route(web::get().to(health)))
})
.bind("0.0.0.0:8080")?
.run();
future::try_join(s1, s2).await?;
Ok(())
}
I suppose you can still use Server::build API to build up multiple bindings, but the approach showed here is more modular. HttpServer::run just returns a Future now. You then join the two and await on both of them.
It works as expected:
$ curl http://127.0.0.1:3006/utils/one
Utils one reached
$ curl http://127.0.0.1:8080/health
All good
I am writing a web service with Rust 2018 Stable and Actix-Web. Using Reqwest, I am making an HTTP request to a diffent site from within one route handler function. Simplyfied it looks like this
extern crate reqwest;
use actix_web;
use reqwest::Url;
pub fn testing(req: actix_web::HttpRequest) -> actix_web::Result<actix_web::HttpResponse> {
println!(">>> testing request begin");
let url = Url::parse("https://example.com/").unwrap();
println!(">>> testing url built");
let req = reqwest::Client::new().post(url);
println!(">>> testing req prepared");
let res_struct = req.send();
println!(">>> testing res_struct received");
let res = res_struct.unwrap();
println!(">>> testing res unwrapped");
Ok(format!("done.").into())
}
That doesn't work, and I am getting the following error message (the error is printed 8 times, "worker:1" to "worker:8", despite calling the function only once):
thread 'actix-rt:worker:1' panicked at 'called `Result::unwrap()`
on an `Err` value: Error(BlockingClientInFutureContext,
"https://www.example.com/")', src/libcore/result.rs:999:5
Panic in Arbiter thread, shutting down system.
Google didn't find anything useful on "BlockingClientInFutureContext", but I am guessing it is somehow related to async/await or maybe Tokio's own futures?
Thanks for any pointers about what to read up on. Also, I am new to Rust.
The handler function is called from the Actix-Web HTttpServer:
HttpServer::new(|| App::new().service(
web::resource("/testing").route(
web::get().to(views::testing)
)
)).bind("127.0.0.1:8001")?.run()
I had a similar issue. The resolution for me was to lock the Reqwest crate version at 0.9.17 in your cargo file then rebuild.
reqwest = "=0.9.17"
It appears that newer version of Reqwest are broken with Actix-web unless you're using the async functionality on both. For reference: https://github.com/seanmonstar/reqwest/issues/541
Turns out, actix_web::web::block() was the correct guess. Using it makes it possible to make blocking calls. block() returns a Future that resolves once the network request returns data. Very close to Promises in JS, plus the .from_err() in there.
pub fn testing(_req: actix_web::HttpRequest)
-> impl Future<Item = HttpResponse, Error = Error>
{
println!(">>> testing request begin");
let url = Url::parse("https://example.com/").unwrap();
println!(">>> testing url built");
let req = reqwest::Client::new().get(url);
println!(">>> testing req prepared");
actix_web::web::block(move || {
println!(">>> testing res received");
req.send()
})
.from_err()
.and_then(|res| {
println!(">>> testing res: {:?}", &res);
HttpResponse::Ok().content_type("text/html").body("Hello!")
})
}
Additionally, in main.rs the route must be called using .to_async() instead of simply .to():
HttpServer::new(|| App::new().service(
web::resource("/testing").route(
web::get().to_async(views::testing)
)
)).bind("127.0.0.1:8001")?.run()
TL;DR: Upgrade to reqwest 0.9.22 or newer.
The error is indicating that you're attempting to make a blocking network call from inside an asynchronous context (Actix Web handlers are called asynchronously). This is not supported in reqwest versions 0.9.17 - 0.9.21.
As of 0.9.22, the author has removed this error in favor of a warning. For more information:
https://github.com/seanmonstar/reqwest/pull/670
https://github.com/seanmonstar/reqwest/issues/541
The call to unwrap() is failing because an error is returned. It is best to avoid unwrap() in production code because it usually means we are trying to look at the 'desired' value (often called the 'happy path') while ignoring the error path.
This code works:
use actix_web;
use reqwest::Url;
fn main() {
println!(">>> testing request begin");
let url = Url::parse("http:/example.com/").unwrap();
println!(">>> testing url built");
let req = reqwest::Client::new().post(url);
println!(">>> testing req prepared");
let res_struct = req.send();
println!(">>> testing res_struct received");
match res_struct {
Ok(r)=> println!("response: {:?}", r),
Err(e)=> println!("error: {}", e),
}
// let res = res_struct.unwrap();
println!("done.");
}
The output is:
Finished dev [unoptimized + debuginfo] target(s) in 2.63s
Running `target/debug/untitled`
>>> testing request begin
>>> testing url built
>>> testing req prepared
>>> testing res_struct received
error: http://example.com/: error trying to connect: failed to lookup address information: nodename nor servname provided, or not known
>>> testing res unwrapped
done.
The above code works without a panic, but the server at example.com is not providing a good response.
If I re-run this using a valid URL, e.g. https://cisco.com, I get no errors:
>>> testing request begin
>>> testing url built
>>> testing req prepared
>>> testing res_struct received
response: Response { url: "https://www.cisco.com/", status: 200, headers: {"server": "Apache", "etag": "\"1732e-59058880c8465\"", "accept-ranges": "bytes", "strict-transport-security": "max-age=31536000", "cdchost": "wemxweb-publish-prod2-02", "x-xss-protection": "1; mode=block", "x-test-debug": "nURL=www.cisco.com,realm=0,isRealm=0,realmDomain=0,shortrealm=0", "content-security-policy": "upgrade-insecure-requests; frame-ancestors *.cisco.com *.jasper.com *.ciscospark.com *.ciscolive.com http://cisco.lookbookhq.com https://cisco.lookbookhq.com testcisco.marketing.adobe.com cisco.marketing.adobe.com ciscosales.my.salesforce.com test.salesforce.com zedo.com hindustantimes.com economictimes.indiatimes.com *.webex.com *.cdw.com *.cdwg.com *.cdw.ca *.meraki-go.com http://ciscopartners.lookbookhq.com https://ciscopartners.lookbookhq.com ciscolearningsystem.com ciscocustomer.lookbookhq.com cisco.lookbookhq.com;", "content-type": "text/html", "expires": "Sun, 18 Aug 2019 12:10:23 GMT", "cache-control": "max-age=0, no-cache, no-store", "pragma": "no-cache", "date": "Sun, 18 Aug 2019 12:10:23 GMT", "connection": "keep-alive", "vary": "Accept-Encoding"} }
>>> testing res unwrapped
done.
I'm not able to create a client that tries to connect to a server and:
if the server is down it has to try again in an infinite loop
if the server is up and connection is successful, when the connection is lost (i.e. server disconnects the client) the client has to restart the infinite loop to try to connect to the server
Here's the code to connect to a server; currently when the connection is lost the program exits. I'm not sure what the best way to implement it is; maybe I have to create a Future with an infinite loop?
extern crate tokio_line;
use tokio_line::LineCodec;
fn get_connection(handle: &Handle) -> Box<Future<Item = (), Error = io::Error>> {
let remote_addr = "127.0.0.1:9876".parse().unwrap();
let tcp = TcpStream::connect(&remote_addr, handle);
let client = tcp.and_then(|stream| {
let (sink, from_server) = stream.framed(LineCodec).split();
let reader = from_server.for_each(|message| {
println!("{}", message);
Ok(())
});
reader.map(|_| {
println!("CLIENT DISCONNECTED");
()
}).map_err(|err| err)
});
let client = client.map_err(|_| { panic!()});
Box::new(client)
}
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let client = get_connection(&handle);
let client = client.and_then(|c| {
println!("Try to reconnect");
get_connection(&handle);
Ok(())
});
core.run(client).unwrap();
}
Add the tokio-line crate with:
tokio-line = { git = "https://github.com/tokio-rs/tokio-line" }
The key question seems to be: how do I implement an infinite loop using Tokio? By answering this question, we can tackle the problem of reconnecting infinitely upon disconnection. From my experience writing asynchronous code, recursion seems to be a straightforward solution to this problem.
UPDATE: as pointed out by Shepmaster (and the folks of the Tokio Gitter), my original answer leaks memory since we build a chain of futures that grows on each iteration. Here follows a new one:
Updated answer: use loop_fn
There is a function in the futures crate that does exactly what you need. It is called loop_fn. You can use it by changing your main function to the following:
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let client = future::loop_fn((), |_| {
// Run the get_connection function and loop again regardless of its result
get_connection(&handle).map(|_| -> Loop<(), ()> {
Loop::Continue(())
})
});
core.run(client).unwrap();
}
The function resembles a for loop, which can continue or break depending on the result of get_connection (see the documentation for the Loop enum). In this case, we choose to always continue, so it will infinitely keep reconnecting.
Note that your version of get_connection will panic if there is an error (e.g. if the client cannot connect to the server). If you also want to retry after an error, you should remove the call to panic!.
Old answer: use recursion
Here follows my old answer, in case anyone finds it interesting.
WARNING: using the code below results in unbounded memory growth.
Making get_connection loop infinitely
We want to call the get_connection function each time the client is disconnected, so that is exactly what we are going to do (look at the comment after reader.and_then):
fn get_connection(handle: &Handle) -> Box<Future<Item = (), Error = io::Error>> {
let remote_addr = "127.0.0.1:9876".parse().unwrap();
let tcp = TcpStream::connect(&remote_addr, handle);
let handle_clone = handle.clone();
let client = tcp.and_then(|stream| {
let (sink, from_server) = stream.framed(LineCodec).split();
let reader = from_server.for_each(|message| {
println!("{}", message);
Ok(())
});
reader.and_then(move |_| {
println!("CLIENT DISCONNECTED");
// Attempt to reconnect in the future
get_connection(&handle_clone)
})
});
let client = client.map_err(|_| { panic!()});
Box::new(client)
}
Remember that get_connection is non-blocking. It just constructs a Box<Future>. This means that when calling it recursively, we still don't block. Instead, we get a new future, which we can link to the previous one by using and_then. As you can see, this is different to normal recursion since the stack doesn't grow on each iteration.
Note that we need to clone the handle (see handle_clone), and move it into the closure passed to reader.and_then. This is necessary because the closure is going to live longer than the function (it will be contained in the future we are returning).
Handling errors
The code you provided doesn't handle the case in which the client is unable to connect to the server (nor any other errors). Following the same principle shown above, we can handle errors by changing the end of get_connection to the following:
let handle_clone = handle.clone();
let client = client.or_else(move |err| {
// Note: this code will infinitely retry, but you could pattern match on the error
// to retry only on certain kinds of error
println!("Error connecting to server: {}", err);
get_connection(&handle_clone)
});
Box::new(client)
Note that or_else is like and_then, but it operates on the error produced by the future.
Removing unnecessary code from main
Finally, it is not necessary to use and_then in the main function. You can replace your main by the following code:
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let client = get_connection(&handle);
core.run(client).unwrap();
}