fix(server): fix TOCTOU race condition in tests
This commit is contained in:
+3
-6
@@ -24,7 +24,7 @@ pub mod startup;
|
|||||||
/// Logging and tracing setup
|
/// Logging and tracing setup
|
||||||
pub mod telemetry;
|
pub mod telemetry;
|
||||||
|
|
||||||
type MaybeListener = Option<poem::listener::TcpListener<String>>;
|
type MaybeListener = Option<std::net::TcpListener>;
|
||||||
|
|
||||||
fn prepare(listener: MaybeListener) -> startup::Application {
|
fn prepare(listener: MaybeListener) -> startup::Application {
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
@@ -70,11 +70,8 @@ pub async fn run(listener: MaybeListener) -> Result<(), std::io::Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn make_random_tcp_listener() -> poem::listener::TcpListener<String> {
|
fn make_random_tcp_listener() -> std::net::TcpListener {
|
||||||
let tcp_listener =
|
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind a random TCP listener")
|
||||||
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind a random TCP listener");
|
|
||||||
let port = tcp_listener.local_addr().unwrap().port();
|
|
||||||
poem::listener::TcpListener::bind(format!("127.0.0.1:{port}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
+36
-23
@@ -6,6 +6,7 @@
|
|||||||
//! - Configuring CORS
|
//! - Configuring CORS
|
||||||
//! - Starting the HTTP server
|
//! - Starting the HTTP server
|
||||||
|
|
||||||
|
use poem::listener::{Listener, TcpAcceptor};
|
||||||
use poem::middleware::{AddDataEndpoint, Cors, CorsEndpoint};
|
use poem::middleware::{AddDataEndpoint, Cors, CorsEndpoint};
|
||||||
use poem::{EndpointExt, Route};
|
use poem::{EndpointExt, Route};
|
||||||
use poem_openapi::OpenApiService;
|
use poem_openapi::OpenApiService;
|
||||||
@@ -19,10 +20,21 @@ use crate::{
|
|||||||
|
|
||||||
use crate::middleware::rate_limit::RateLimitEndpoint;
|
use crate::middleware::rate_limit::RateLimitEndpoint;
|
||||||
|
|
||||||
type Server = poem::Server<poem::listener::TcpListener<String>, std::convert::Infallible>;
|
type Server = poem::Server<poem::listener::BoxListener, std::convert::Infallible>;
|
||||||
|
|
||||||
/// The configured application with rate limiting, CORS, and settings data.
|
/// The configured application with rate limiting, CORS, and settings data.
|
||||||
pub type App = AddDataEndpoint<CorsEndpoint<RateLimitEndpoint<Route>>, Settings>;
|
pub type App = AddDataEndpoint<CorsEndpoint<RateLimitEndpoint<Route>>, Settings>;
|
||||||
|
|
||||||
|
struct PreBoundListener(std::net::TcpListener);
|
||||||
|
|
||||||
|
impl Listener for PreBoundListener {
|
||||||
|
type Acceptor = TcpAcceptor;
|
||||||
|
|
||||||
|
async fn into_acceptor(self) -> std::io::Result<Self::Acceptor> {
|
||||||
|
TcpAcceptor::from_std(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Application builder that holds the server configuration before running.
|
/// Application builder that holds the server configuration before running.
|
||||||
pub struct Application {
|
pub struct Application {
|
||||||
server: Server,
|
server: Server,
|
||||||
@@ -135,30 +147,33 @@ impl Application {
|
|||||||
|
|
||||||
fn setup_server(
|
fn setup_server(
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
tcp_listener: Option<poem::listener::TcpListener<String>>,
|
tcp_listener: Option<std::net::TcpListener>,
|
||||||
) -> Server {
|
) -> (Server, u16, String) {
|
||||||
let tcp_listener = tcp_listener.unwrap_or_else(|| {
|
tcp_listener.map_or_else(
|
||||||
let address = format!(
|
|| {
|
||||||
"{}:{}",
|
let port = settings.application.port;
|
||||||
settings.application.host, settings.application.port
|
let host = settings.application.host.clone();
|
||||||
);
|
let address = format!("{host}:{port}");
|
||||||
poem::listener::TcpListener::bind(address)
|
let server = poem::Server::new(poem::listener::TcpListener::bind(address).boxed());
|
||||||
});
|
(server, port, host)
|
||||||
poem::Server::new(tcp_listener)
|
},
|
||||||
|
|listener| {
|
||||||
|
let addr = listener.local_addr().expect("Failed to get bound address");
|
||||||
|
let port = addr.port();
|
||||||
|
let host = addr.ip().to_string();
|
||||||
|
let server = poem::Server::new(PreBoundListener(listener).boxed());
|
||||||
|
(server, port, host)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a new application with the given settings and optional TCP listener.
|
/// Builds a new application with the given settings and optional TCP listener.
|
||||||
///
|
///
|
||||||
/// If no listener is provided, one will be created based on the settings.
|
/// If no listener is provided, one will be created based on the settings.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn build(
|
pub fn build(settings: Settings, tcp_listener: Option<std::net::TcpListener>) -> Self {
|
||||||
settings: Settings,
|
let (server, port, host) = Self::setup_server(&settings, tcp_listener);
|
||||||
tcp_listener: Option<poem::listener::TcpListener<String>>,
|
|
||||||
) -> Self {
|
|
||||||
let port = settings.application.port;
|
|
||||||
let host = settings.application.host.clone();
|
|
||||||
let app = Self::setup_app(&settings);
|
let app = Self::setup_app(&settings);
|
||||||
let server = Self::setup_server(&settings, tcp_listener);
|
|
||||||
Self {
|
Self {
|
||||||
server,
|
server,
|
||||||
app,
|
app,
|
||||||
@@ -243,13 +258,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn application_with_custom_listener() {
|
fn application_with_custom_listener() {
|
||||||
let settings = create_test_settings();
|
let settings = create_test_settings();
|
||||||
let tcp_listener =
|
let listener =
|
||||||
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
|
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
|
||||||
let port = tcp_listener.local_addr().unwrap().port();
|
let expected_port = listener.local_addr().unwrap().port();
|
||||||
let listener = poem::listener::TcpListener::bind(format!("127.0.0.1:{port}"));
|
|
||||||
|
|
||||||
let app = Application::build(settings, Some(listener));
|
let app = Application::build(settings, Some(listener));
|
||||||
assert_eq!(app.host(), "127.0.0.1");
|
assert_eq!(app.host(), "127.0.0.1");
|
||||||
assert_eq!(app.port(), 8080);
|
assert_eq!(app.port(), expected_port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user