mirror of
https://github.com/openai/codex.git
synced 2026-04-27 16:15:09 +00:00
codex-rs/app-server: add health endpoints for --listen websocket server (#13782)
Healthcheck endpoints for the websocket server - serve `GET /readyz` and `GET /healthz` from the same listener used for `--listen ws://...` - switch the websocket listener over to `axum` upgrade handling instead of manual socket parsing - add websocket transport coverage for the health endpoints and document the new behavior Testing - integration tests - built and tested e2e ``` > curl -i http://127.0.0.1:9234/readyz HTTP/1.1 200 OK content-length: 0 date: Fri, 06 Mar 2026 19:20:23 GMT > curl -i http://127.0.0.1:9234/healthz HTTP/1.1 200 OK content-length: 0 date: Fri, 06 Mar 2026 19:20:24 GMT ```
This commit is contained in:
@@ -12,6 +12,7 @@ use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use reqwest::StatusCode;
|
||||
use serde_json::json;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
@@ -79,6 +80,34 @@ async fn websocket_transport_routes_per_connection_handshake_and_responses() ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_transport_serves_health_endpoints_on_same_listener() -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri(), "never")?;
|
||||
|
||||
let bind_addr = reserve_local_addr()?;
|
||||
let mut process = spawn_websocket_server(codex_home.path(), bind_addr).await?;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let readyz = http_get(&client, bind_addr, "/readyz").await?;
|
||||
assert_eq!(readyz.status(), StatusCode::OK);
|
||||
|
||||
let healthz = http_get(&client, bind_addr, "/healthz").await?;
|
||||
assert_eq!(healthz.status(), StatusCode::OK);
|
||||
|
||||
let mut ws = connect_websocket(bind_addr).await?;
|
||||
send_initialize_request(&mut ws, 1, "ws_health_client").await?;
|
||||
let init = read_response_for_id(&mut ws, 1).await?;
|
||||
assert_eq!(init.id, RequestId::Integer(1));
|
||||
|
||||
process
|
||||
.kill()
|
||||
.await
|
||||
.context("failed to stop websocket app-server process")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn spawn_websocket_server(
|
||||
codex_home: &Path,
|
||||
bind_addr: SocketAddr,
|
||||
@@ -133,6 +162,30 @@ pub(super) async fn connect_websocket(bind_addr: SocketAddr) -> Result<WsClient>
|
||||
}
|
||||
}
|
||||
|
||||
async fn http_get(
|
||||
client: &reqwest::Client,
|
||||
bind_addr: SocketAddr,
|
||||
path: &str,
|
||||
) -> Result<reqwest::Response> {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
match client
|
||||
.get(format!("http://{bind_addr}{path}"))
|
||||
.send()
|
||||
.await
|
||||
.with_context(|| format!("failed to GET http://{bind_addr}{path}"))
|
||||
{
|
||||
Ok(response) => return Ok(response),
|
||||
Err(err) => {
|
||||
if Instant::now() >= deadline {
|
||||
bail!("failed to GET http://{bind_addr}{path}: {err}");
|
||||
}
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn send_initialize_request(
|
||||
stream: &mut WsClient,
|
||||
id: i64,
|
||||
|
||||
Reference in New Issue
Block a user