Peter Vajda
Peter Vajda

Reputation: 29

Ntex server unreachable to cUrl from test session, but reachable from other shell...at the same time

I am trying to test a simple ntex server. This is the whole code inside the test:

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);
}

The results are all green, 2/2 working. However! The output from the verbose curl is

*   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

But when I insert some sleep into spawn_server_works function and I start the test, I switch to another shell and I can successfully curl:

*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> GET /health_check HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< content-length: 0
< date: Thu, 22 Feb 2024 21:02:27 GMT
< 
* Connection #0 to host 127.0.0.1 left intact

So at the same time, curl from inside the cargo test fails, while curl from outside cargo test sessions works as expected.

Any idea?

Whole code for the 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)
}

Upvotes: -1

Views: 90

Answers (1)

Peter Vajda
Peter Vajda

Reputation: 29

EDIT: This behavior is somehow documented here https://doc.rust-lang.org/rust-by-example/std_misc/process/wait.html

Apparently, std::process::Command spawns a child. This child needs to have method wait() invoked in curl case. No idea about that, nowhere in documentation. So the resulting, working code is:

#[ntex::test]
async fn spawn_server_works() {
    spawn_server();
    let _ = std::process::Command::new("curl")
        .arg("http://127.0.0.1:8000/health_check")
        .arg("-vvv")
        .spawn()
        .expect("Failed to execute command.")
        .wait()
        .expect("Failed to wait");
}

Hope that helps somebody in the future!

Upvotes: 0

Related Questions