求助社区的朋友们,我在使用 Axum 改写 《Zero To Production》 第三章 “SIGN UP A NEW SUBSCRIBER” 的代码时,碰到了一个问题,使用 cargo run
运行正常, 在浏览器中请求 http://127.0.0.1:3333/health_check 没有问题,使用 curl -v http://127.0.0.1:3333/health_check
请求 health_check 路由时也能正常返回。
但是使用 cargo test health_check_works
进行测试时,却把测试程序卡住了:
test health_check_works has been running for over 60 seconds
可能是哪里用的不对把程序阻塞了。我觉得这个问题蛮值得研究一下的,用 Claude/DeepSeek 帮助分析也没有解决。请大家帮助看看问题出在哪里。(可以克隆 https://github.com/ohmycloud/zero2prod-axum.git, 执行 cargo test health_check_works 复现该问题。)
tests/health_check.rs 的代码如下:
// `tokio::test` is the testing equivalent of `tokio::main`.
// Is also spares you from having to specify the `#[test]` attribute.
//
// You can inspect what code gets generated using
// `cargo expand --test health_check`
#[tokio::test]
async fn health_check_works() {
// Arrange
spawn_app();
// We need to bring in `reqwest`
// to perform HTTP requsts against our application.
let client = reqwest::Client::new();
// Act
let response = client
.get("http://127.0.0.1:3333/health_check")
.send()
.await
.expect("Falied to execute reqwest.");
// Assert
assert!(response.status().is_success());
assert_eq!(Some(0), response.content_length());
}
// Launch our application in the background
fn spawn_app() {
let server = zero2prod::run()
.expect("Failed to bind address")
.into_future();
let _ = tokio::spawn(server);
println!("app spawned.");
}
src/lib.rs 中的代码如下:
pub mod health_check;
use crate::health_check::health_check;
use axum::{
Router,
extract::Path,
response::IntoResponse,
routing::{IntoMakeService, get},
serve::Serve,
};
use tokio::net::TcpListener;
async fn index() -> impl IntoResponse {
"Rust Rocks!"
}
async fn greet(Path(name): Path<String>) -> impl IntoResponse {
format!("Hello, {}", name)
}
pub fn run() -> Result<Serve<TcpListener, IntoMakeService<Router>, Router>, std::io::Error> {
let app = Router::new()
.route("/health_check", get(health_check))
.route("/", get(index))
.route("/{name}", get(greet));
let listener = std::net::TcpListener::bind("0.0.0.0:3333")?;
let listener = TcpListener::from_std(listener)?;
println!("Listening on {:?}", listener.local_addr());
let server = axum::serve(listener, app.into_make_service());
Ok(server)
}
src/health_check.rs 中的代码如下:
use axum::{http::Response, response::IntoResponse};
pub async fn health_check() -> impl IntoResponse {
let response = Response::new("hello world");
response.status()
}
src/main.rs 的代码如下:
use zero2prod::run;
#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
run()?.await
}
Ext Link: https://github.com/ohmycloud/zero2prod-axum.git
评论区
写评论我以前有个类似的bug,当时是flutter调用rust,我的逻辑是先创建std的listener,然后启动服务的时候用tokio listener from std,当时axum就在android正常,windows上卡住,不创建std 直接用tokio listener就没问题,原来是我没仔细看文档设置socket为non blocking。。。
--
👇
ohmycloud: 我还以为一个人哈哈哈哈哈,问题虽然小,很有帮助。谢谢烙铁。
--
👇
Lazy: 确实啊老铁仓库跟你贴出来的代码不一样,不过我看了一下,出现的问题都是一样的:
你可能没注意到,标准库的
TcpListener
默认是阻塞的,所以服务的监听会直接阻塞线程,即使包装成异步的也不会改变其阻塞属性,后续的操作都无法执行的。所以你需要:
如果你有注意到
tokio::net::TcpListener::from_std
文档说明的话,应该也是能看到以下内容:所以在我拉下你的代码库的时候,即使是执行的
cargo run [-r]
,在浏览器里我也是访问阻塞的我还以为一个人哈哈哈哈哈,问题虽然小,很有帮助。谢谢烙铁。
--
👇
Lazy: 确实啊老铁仓库跟你贴出来的代码不一样,不过我看了一下,出现的问题都是一样的:
你可能没注意到,标准库的
TcpListener
默认是阻塞的,所以服务的监听会直接阻塞线程,即使包装成异步的也不会改变其阻塞属性,后续的操作都无法执行的。所以你需要:
如果你有注意到
tokio::net::TcpListener::from_std
文档说明的话,应该也是能看到以下内容:所以在我拉下你的代码库的时候,即使是执行的
cargo run [-r]
,在浏览器里我也是访问阻塞的感谢老铁,跳转到 tokio 的 TcpListener::from_std 文档,里面还特意标注了
Notes
, 写的明明白白: Non-blocking mode can be set using [set_nonblocking
].--
👇
Nayaka: 正如我所说:"用右手手指给右手手背抓痒",还有啊,2024下月才发布,咱先用2021吧
--
👇
Lazy: 确实啊老铁仓库跟你贴出来的代码不一样,不过我看了一下,出现的问题都是一样的:
你可能没注意到,标准库的
TcpListener
默认是阻塞的,所以服务的监听会直接阻塞线程,即使包装成异步的也不会改变其阻塞属性,后续的操作都无法执行的。所以你需要:
如果你有注意到
tokio::net::TcpListener::from_std
文档说明的话,应该也是能看到以下内容:所以在我拉下你的代码库的时候,即使是执行的
cargo run [-r]
,在浏览器里我也是访问阻塞的正如我所说:"用右手手指给右手手背抓痒",还有啊,2024下月才发布,咱先用2021吧
--
👇
Lazy: 确实啊老铁仓库跟你贴出来的代码不一样,不过我看了一下,出现的问题都是一样的:
你可能没注意到,标准库的
TcpListener
默认是阻塞的,所以服务的监听会直接阻塞线程,即使包装成异步的也不会改变其阻塞属性,后续的操作都无法执行的。所以你需要:
如果你有注意到
tokio::net::TcpListener::from_std
文档说明的话,应该也是能看到以下内容:所以在我拉下你的代码库的时候,即使是执行的
cargo run [-r]
,在浏览器里我也是访问阻塞的确实啊老铁仓库跟你贴出来的代码不一样,不过我看了一下,出现的问题都是一样的:
你可能没注意到,标准库的
TcpListener
默认是阻塞的,所以服务的监听会直接阻塞线程,即使包装成异步的也不会改变其阻塞属性,后续的操作都无法执行的。所以你需要:
如果你有注意到
tokio::net::TcpListener::from_std
文档说明的话,应该也是能看到以下内容:所以在我拉下你的代码库的时候,即使是执行的
cargo run [-r]
,在浏览器里我也是访问阻塞的你这代码克隆下来的跟上面贴的都不一样
省流:"你在用右手指给自己右手背抓痒"