use anyhow::{anyhow, Result}; use gnostr_relay::config; use gnostr_relay::server::start_server; //use http::{Request, Response}; use hyper::{Client, StatusCode, Uri}; use std::net::TcpListener; use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::mpsc as syncmpsc; use std::sync::mpsc::{Receiver as MpscReceiver, Sender as MpscSender}; use std::thread; use std::thread::JoinHandle; use std::time::Duration; use tracing::{debug, info}; pub struct Relay { pub port: u16, pub handle: JoinHandle<()>, pub shutdown_tx: MpscSender<()>, } pub fn start_relay() -> Result { // setup tracing let _trace_sub = tracing_subscriber::fmt::try_init(); info!("Starting a new relay"); // replace default settings let mut settings = config::Settings::default(); // identify open port info!("Checking for address..."); let port = get_available_port().unwrap(); info!("Found open port: {}", port); // bind to local interface only settings.network.address = "127.0.0.1".to_owned(); settings.network.port = port; // create an in-memory DB with multiple readers settings.database.in_memory = true; settings.database.min_conn = 4; settings.database.max_conn = 8; let (shutdown_tx, shutdown_rx): (MpscSender<()>, MpscReceiver<()>) = syncmpsc::channel(); let handle = thread::spawn(move || { // server will block the thread it is run on. let _ = start_server(&settings, shutdown_rx); }); // how do we know the relay has finished starting up? Ok(Relay { port, handle, shutdown_tx, }) } // check if the server is healthy via HTTP request async fn server_ready(relay: &Relay) -> Result { let uri: String = format!("http://127.0.0.1:{}/", relay.port); let client = Client::new(); let uri: Uri = uri.parse().unwrap(); let res = client.get(uri).await?; Ok(res.status() == StatusCode::OK) } pub async fn wait_for_healthy_relay(relay: &Relay) -> Result<()> { // TODO: maximum time to wait for server to become healthy. // give it a little time to start up before we start polling tokio::time::sleep(Duration::from_millis(10)).await; loop { let server_check = server_ready(relay).await; match server_check { Ok(true) => { // server responded with 200-OK. break; } Ok(false) => { // server responded with an error, we're done. return Err(anyhow!("Got non-200-OK from relay")); } Err(_) => { // server is not yet ready, probably connection refused... debug!("Relay not ready, will try again..."); tokio::time::sleep(Duration::from_millis(10)).await; } } } info!("relay is ready"); Ok(()) // simple message sent to web browsers //let mut request = Request::builder() // .uri("https://www.rust-lang.org/") // .header("User-Agent", "my-awesome-agent/1.0"); } // from https://elliotekj.com/posts/2017/07/25/find-available-tcp-port-rust/ // This needed some modification; if multiple tasks all ask for open ports, they will tend to get the same one. // instead we should try to try these incrementally/globally. static PORT_COUNTER: AtomicU16 = AtomicU16::new(4030); fn get_available_port() -> Option { let startsearch = PORT_COUNTER.fetch_add(10, Ordering::SeqCst); if startsearch >= 20000 { // wrap around PORT_COUNTER.store(4030, Ordering::Relaxed); } (startsearch..20000).find(|port| port_is_available(*port)) } pub fn port_is_available(port: u16) -> bool { info!("checking on port {}", port); match TcpListener::bind(("127.0.0.1", port)) { Ok(_) => true, Err(_) => false, } }