测试会话中Curl无法连接Ntex服务器,外部Shell可正常访问
Ntex服务器测试异常问题解析
问题场景
测试简单Ntex服务器时,两个测试用例均执行通过,但测试代码中调用的curl命令返回连接拒绝;在测试函数中加入睡眠后,外部Shell可成功访问服务器,但测试会话内的curl仍失败。
测试代码
use ntex::{web, web::test}; use shopp::config; #[ntex::test] async fn health_check_works() { let app = test::init_service(web::App::new().configure(config)).await; let req = test::TestRequest::get() .uri("/health_check") .header("content-type", "text/plain") .to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_success()); } #[ntex::test] async fn spawn_server_works() { spawn_server(); let output = std::process::Command::new("curl") .arg("-vvv") .arg("http://127.0.0.1:8000/health_check") .spawn() .expect("Failed to execute command."); } fn spawn_server() { let server = shopp::run().expect("Failed to start server."); let _ = async_std::task::spawn(server); }
错误输出
* Trying 127.0.0.1:8000... * connect to 127.0.0.1 port 8000 failed: Connection refused * Failed to connect to 127.0.0.1 port 8000 after 0 ms: Couldn't connect to server * Closing connection 0 curl: (7) Failed to connect to 127.0.0.1 port 8000 after 0 ms: Couldn't connect to server
服务器代码
lib.rs
use ntex::server::Server; use ntex::web; #[web::get("/health_check")] async fn health_check() -> impl web::Responder { web::HttpResponse::Ok() } pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(health_check); } pub fn run() -> Result<Server, std::io::Error> { let server = web::HttpServer::new(|| web::App::new().configure(config)) .bind(("127.0.0.1", 8000))? .run(); Ok(server) }
main.rs
use ntex::server::Server; use ntex::web; #[web::get("/health_check")] async fn health_check() -> impl web::Responder { web::HttpResponse::Ok() } pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(health_check); } pub fn run() -> Result<Server, std::io::Error> { let server = web::HttpServer::new(|| web::App::new().configure(config)) .bind(("127.0.0.1", 8000))? .run(); Ok(server) }
原因分析
- 异步任务时序不匹配:调用
spawn_server()后立刻执行curl,此时服务器的异步启动任务尚未完成端口绑定和监听,自然返回连接拒绝。外部Shell在睡眠后发起请求时,服务器已完成启动流程,因此能正常响应。 - Runtime调度差异:
ntex::test使用Ntex自带的异步Runtime,而spawn_server中用async_std::task::spawn启动服务器,两个Runtime的任务调度存在延迟,导致服务器启动任务还未被调度到监听状态,curl请求就已发出。 - 测试函数无等待逻辑:
spawn_server_works函数在启动curl后直接结束,测试框架不会等待异步任务完成,进一步加剧了时序问题。
解决方案
- 使用Ntex原生测试工具:像第一个测试用例那样,用
test::init_service和test::call_service直接测试服务逻辑,无需启动真实服务器和外部进程,这是更可靠的集成测试方式,避免了进程间通信的时序问题。 - 等待服务器启动完成:若必须使用外部进程测试,需确保服务器完全启动后再发起请求。可以修改测试逻辑,等待服务器绑定端口后再执行
curl:
#[ntex::test] async fn spawn_server_works() { // 后台启动服务器 ntex::rt::spawn(async { shopp::run().expect("Failed to start server.").await.expect("Server failed"); }); // 短暂等待服务器完成端口绑定(或用更可靠的端口检测逻辑) ntex::time::sleep(std::time::Duration::from_millis(100)).await; let output = std::process::Command::new("curl") .arg("-vvv") .arg("http://127.0.0.1:8000/health_check") .output() .expect("Failed to execute command."); assert!(output.status.success()); }
- 统一异步Runtime:避免混用
async_std和Ntex的Runtime,使用Ntex自带的ntex::rt::spawn启动服务器任务,保持Runtime一致,减少调度延迟。
内容的提问来源于stack exchange,提问作者Peter Vajda
相关产品推荐
相关产品推荐

