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
Related
I am trying to run a streamlit server behind the scenes of a Tauri application.
The streamlit server is packaged with PyInstaller into a single binary file and works as expected when standalone.
I have a rust main.rs file that needs to run a streamlit binary using a Command (in this case its a tauri::api::process::Command using a new_sidecar).
It spawns the server, but when the app closes, my streamlit server is not disposed off.
On window exit, I want to send a kill command to kill the server instance in the child.
Here is an example of my code:
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use async_std::task;
use std::sync::mpsc::sync_channel;
use std::thread;
use std::time::Duration;
use tauri::api::process::{Command, CommandEvent};
use tauri::{Manager, WindowEvent};
fn main() {
let (tx_kill, rx_kill) = sync_channel(1);
tauri::Builder::default()
.setup(|app| {
println!("App Setup Start");
let t = Command::new_sidecar("streamlit").expect("failed to create sidecar");
let (mut rx, child) = t.spawn().expect("Failed to spawn server");
let splashscreen_window = app.get_window("splashscreen").unwrap();
let main_window = app.get_window("main").unwrap();
// Listen for server port then refresh main window
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
if let CommandEvent::Stdout(output) = event {
if output.contains("Network URL:") {
let tokens: Vec<&str> = output.split(":").collect();
let port = tokens.last().unwrap();
println!("Connect to port {}", port);
main_window.eval(&format!(
"window.location.replace('http://localhost:{}')",
port
));
task::sleep(Duration::from_secs(2)).await;
splashscreen_window.close().unwrap();
main_window.show().unwrap();
}
}
}
});
// Listen for kill command
thread::spawn(move || loop {
let event = rx_kill.recv();
if event.unwrap() == -1 {
child.kill().expect("Failed to close API");
}
});
Ok(())
})
.on_window_event(move |event| match event.event() {
WindowEvent::Destroyed => {
println!("Window destroyed");
tx_kill.send(-1).expect("Failed to send close signal");
}
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running application");
}
But I am getting an error, when I try to kill the child instance:
thread::spawn(move || loop {
let event = rx_kill.recv();
if event.unwrap() == -1 {
child.kill().expect("Failed to close API");
}
});
use of moved value: `child`
move occurs because `child` has type `CommandChild`, which does not implement the `Copy` trait
Any ideas?
I'm new to rust and encountered an issue while building an API with warp. I'm trying to pass some requests to another thread with a channel(trying to avoid using arc/mutex). Still, I noticed that when I pass an mpsc::sync::Sender to a warp handler, I get this error.
"std::sync::mpsc::Sender cannot be shared between threads safely"
and
"the trait Sync is not implemented for `std::sync::mpsc::Sender"
Can someone lead me in the right direction?
use std::sync::mpsc::Sender;
pub async fn init_server(run_tx: Sender<Packet>) {
let store = Store::new();
let store_filter = warp::any().map(move || store.clone());
let run_tx_filter = warp::any().map(move || run_tx.clone());
let update_item = warp::get()
.and(warp::path("v1"))
.and(warp::path("auth"))
.and(warp::path::end())
.and(post_json())
.and(store_filter.clone())
.and(run_tx_filter.clone()) //where I'm trying to send "Sender"
.and_then(request_token);
let routes = update_item;
println!("HTTP server started on port 8080");
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
pub async fn request_token(
req: TokenRequest,
store: Store,
run_tx: Sender<Packet>,
) -> Result<impl warp::Reply, warp::Rejection> {
let (tmp_tx, tmp_rx) = std::sync::mpsc::channel();
run_tx
.send(Packet::IsPlayerLoggedIn(req.address, tmp_tx))
.unwrap();
let logged_in = tmp_rx.recv().unwrap();
if logged_in {
return Ok(warp::reply::with_status(
"Already logged in",
http::StatusCode::BAD_REQUEST,
));
}
Ok(warp::reply::with_status("some token", http::StatusCode::OK))
}
I've looked through some of the examples for warp, and was also wondering what are some good resources to get knowledgable of the crate. Thank you!
This is because you're using std::sync::mpsc::Sender. std::sync implements !Sync, so you won't be able to use that. You don't want to use that anyway since it's blocking.
When you use async functionality in rust, you need to provide a runtime for it. The good news is that warp runs on the tokio runtime, so you should have access to tokio::sync::mpsc If you take a look at that link, the Sender for that mpsc implementation implements Sync and Send so it's safe to share across threads.
TLDR:
Use tokio::sync::mpsc instead of std::sync::mpsc and this won't be an issue.
I'm using the recommended rocket_db_pools crate to create a database pool and attach it to Rocket e.g.
#[derive(Database)]
#[database("birthdays")]
struct DB(sqlx::SqlitePool);
// in main
rocket::build()
.attach(DB::init())
I would like to access the same database pool outside of the context of Rocket. I'm attempting to spawn a separate tokio task that runs alongside Rocket and does some additional background work e.g.
async fn main() -> _ {
rocket::tokio::spawn(async {
// access DB pool here
})
rocket::build()
// ...
}
Is there a way to initialize a sqlx::SqlitePool and easily provide it to both the background task and Rocket so I can still leverage the automatic #[derive(Database)] goodies that rocket_db_pools provides?
As a relative rust beginner I'm having a hard time reading through the traits and understanding if there is a way to do so that doesn't require doing it fully custom by creating a pool, writing my own impl FromRequest, etc.
I found an alternative based on the example from rocket_sync_db_pools which I adapted for the differences with rocket_db_pools.
It uses a fairing to get access to the Database after it has been initialized and then clones the wrapped SqlitePool to move into the task.
#[derive(Database)]
#[database("birthdays")]
struct DB(sqlx::SqlitePool);
// in main
rocket::build()
.attach(DB::init())
.attach(AdHoc::try_on_ignite("Background job", |rocket| async {
let pool = match DB::fetch(&rocket) {
Some(pool) => pool.0.clone(), // clone the wrapped pool
None => return Err(rocket),
};
rocket::tokio::task::spawn(async move {
loop {
if let Ok(mut conn) = pool.acquire().await {
let results = sqlx::query_as::<_, Birthday>(
"SELECT name, birthday, account_id FROM birthdays",
)
.fetch_all(&mut conn)
.await
.expect("query should succeed");
debug!("selected from birthdays: {:?}", results);
}
sleep(Duration::from_secs(10)).await;
}
});
Ok(rocket)
}))
I'm using actix framework to build a server that should support an opportunity to show both age/balance to a user given a user_id:
fn show_balance(req: &HttpRequest) -> HttpResponse {
let client = create_client();
let user_id = req.match_info().get("user_id").unwrap();
let balance = client.load_grade(user_id); // Returns a balance as a String
HttpResponse::Ok()
.content_type("text/plain")
.body(format!("Hello! Your balance is {}", balance))
}
fn show_age(req: &HttpRequest) -> HttpResponse {
let client = create_client();
let user_id = req.match_info().get("user_id").unwrap();
let age = client.load_grade(user_id); // Returns an age as a String
HttpResponse::Ok()
.content_type("text/plain")
.body(format!("Hello! Your balance is {}", age))
}
fn main() {
env::set_var("RUST_LOG", "actix_web=debug");
env::set_var("RUST_BACKTRACE", "1");
env_logger::init();
let sys = actix::System::new("basic-example");
let addr = server::new(
|| App::new()
// enable logger
.middleware(middleware::Logger::default())
.resource("/balance/{user_id}", |r| r.method(Method::GET).f(show_balance))
.resource("/age/{user_id}", |r| r.method(Method::GET).f(show_age))
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
}
fn create_client() -> UserDataClient {
let enviroment = grpcio::EnvBuilder::new().build();
let channel = grpcio::ChannelBuilder::new(enviroment)
.connect(API_URL);
UserDataClient::new(channel)
}
This code works, but my concern is that I have to create a client (and open a channel) for every incoming request which is inefficient and readable, I think it's a good idea to make sort of a singleton instead (since I can reuse it). I looked through the example folder and found that todo example is kinda similar to what I'm doing. So I found the following two options to inject my client object (after I create a single instance of it in main():
Put it in an app state
Inject it as a middleware (1, 2)?
What's the best/correct one to implement?
I thought about just passing a client object to every handler as an argument but I didn't manage to make it work (and doesn't look good anyway).
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();
}