You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试会话中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)
}

原因分析

  1. 异步任务时序不匹配:调用spawn_server()后立刻执行curl,此时服务器的异步启动任务尚未完成端口绑定和监听,自然返回连接拒绝。外部Shell在睡眠后发起请求时,服务器已完成启动流程,因此能正常响应。
  2. Runtime调度差异:ntex::test使用Ntex自带的异步Runtime,而spawn_server中用async_std::task::spawn启动服务器,两个Runtime的任务调度存在延迟,导致服务器启动任务还未被调度到监听状态,curl请求就已发出。
  3. 测试函数无等待逻辑:spawn_server_works函数在启动curl后直接结束,测试框架不会等待异步任务完成,进一步加剧了时序问题。

解决方案

  1. 使用Ntex原生测试工具:像第一个测试用例那样,用test::init_service和test::call_service直接测试服务逻辑,无需启动真实服务器和外部进程,这是更可靠的集成测试方式,避免了进程间通信的时序问题。
  2. 等待服务器启动完成:若必须使用外部进程测试,需确保服务器完全启动后再发起请求。可以修改测试逻辑,等待服务器绑定端口后再执行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());
}
  1. 统一异步Runtime:避免混用async_std和Ntex的Runtime,使用Ntex自带的ntex::rt::spawn启动服务器任务,保持Runtime一致,减少调度延迟。

内容的提问来源于stack exchange,提问作者Peter Vajda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 16:44:51