Ad-hoc HTTP server for Oauth2 Flow #9483
-
I am trying to spin up an HTTP server to handle Oauth redirects, and then shut down the web server when complete. I have a working example where I can start the HTTP server when the app starts, but this isn't ideal since it will always be running - I am trying to spin up/down the HTTP server as needed. I am extremely close, but I'm just running into a few issues, and I am stuck. The basic idea I am trying is this:
My problem is around storing the server handle correctly in Tauri state so that I can shut down the server. The Here is the full snippet: use std::net::TcpListener;
use std::sync::Mutex;
use std::thread;
use tauri::{AppHandle, Manager, State};
use actix_web::dev::ServerHandle;
use actix_web::{middleware, App, HttpServer, web};
use actix_web::get;
// An app handle is stored in actix web data so that it can emit events to the frontend
// when it recieves the callback request
struct TauriAppState {
app: Mutex<AppHandle>,
}
// This is the struct stored in Tauri state that holds the server handle
#[derive(Default)]
struct Server {
handle: Option<ServerHandle>,
}
#[derive(Default)]
pub struct ServerState(tokio::sync::Mutex<Server>);
// State for Tauri to manage
pub fn managed_state() -> ServerState {
ServerState(Default::default())
}
// Start server command. Starts the Actix web server in a new thread
#[tauri::command]
pub fn start_server(handle: tauri::AppHandle) -> Result<String, String> {
let port = get_available_port().ok_or("Failed to find empty port")?;
let boxed = Box::new(handle.clone());
thread::spawn(move || {
run(*boxed, port).unwrap();
});
Ok(port.to_string())
}
// Command to stop the server. Gets the handle from state and shuts down the server
#[tauri::command]
pub async fn stop_server(server_state: State<'_, ServerState>) -> Result<(), String> {
// This line blocks!!! :(
let mut server = server_state.0.lock().await;
if let Some(handle) = &server.handle {
handle.stop(false).await;
server.handle = None;
}
Ok(())
}
#[actix_web::main]
async fn run(app_handle: AppHandle, port: u16) -> std::io::Result<()> {
// Get the state to place the handle in
let app = app_handle.clone();
let server_state: State<'_, ServerState> = app.state();
let mut svr = server_state.0.lock().await;
// The app handle is placed in actix web data
// so that "emit" can be called in the callback
let tauri_app = web::Data::new(TauriAppState {
app: Mutex::new(app_handle),
});
let server = HttpServer::new(move || {
App::new()
.app_data(tauri_app.clone())
.wrap(middleware::Logger::default())
.service(test_handle)
})
.bind(("127.0.0.1", port))
.expect("failed to bind")
.workers(1)
.shutdown_timeout(1)
.run();
// put the handle in state
svr.handle = Some(server.handle());
server.await
}
// Message to send to frontend
#[derive(Clone, serde::Serialize)]
struct Payload {
message: String,
}
// Actix web query data
#[derive(serde::Deserialize)]
struct Info {
token: String,
}
#[get("/oauth/callback")]
pub async fn test_handle(info: web::Query<Info>, data: web::Data<TauriAppState>) -> actix_web::Result<String> {
let app = data.app.lock().unwrap();
match app.emit("oauth", Payload { message: format!("{}", info.token) }) {
Err(err) => println!("{:?}",err),
Ok(_) => (),
};
Ok("auth flow complete! can now close this tab".to_string())
}
fn get_available_port() -> Option<u16> {
(8000..9000)
.find(|port| port_is_available(*port))
}
fn port_is_available(port: u16) -> bool {
match TcpListener::bind(("127.0.0.1", port)) {
Ok(_) => true,
Err(_) => false,
}
} Relevant dependencies
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
The |
Beta Was this translation helpful? Give feedback.
The
let mut svr = server_state.0.lock().await;
line will keep ServerState locked forever currently. Try addingdrop(svr);
after.run();
but beforeserver.await
(because the function will block on server.await until the server shuts down, thereforesvr
will never go out of scope == will never be unlocked)