Codex Handbook
exec-server/src/server/transport.rs 187 lines
use axum::Router;use axum::body::Body;use axum::extract::ConnectInfo;use axum::extract::State;use axum::extract::ws::WebSocketUpgrade;use axum::http::Request;use axum::http::StatusCode;use axum::http::header::ORIGIN;use axum::middleware;use axum::middleware::Next;use axum::response::IntoResponse;use axum::response::Response;use axum::routing::any;use axum::routing::get;use std::io::Write as _;use std::net::SocketAddr;use tokio::io;use tokio::io::AsyncRead;use tokio::io::AsyncWrite;use tokio::net::TcpListener;use tracing::info;use tracing::warn;use crate::ExecServerRuntimePaths;use crate::connection::JsonRpcConnection;use crate::server::processor::ConnectionProcessor;pub const DEFAULT_LISTEN_URL: &str = "ws://127.0.0.1:0";#[derive(Debug, Clone, Eq, PartialEq)]pub(crate) enum ExecServerListenTransport {    WebSocket(SocketAddr),    Stdio,}#[derive(Debug, Clone, Eq, PartialEq)]pub enum ExecServerListenUrlParseError {    UnsupportedListenUrl(String),    InvalidWebSocketListenUrl(String),}impl std::fmt::Display for ExecServerListenUrlParseError {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            ExecServerListenUrlParseError::UnsupportedListenUrl(listen_url) => write!(                f,                "unsupported --listen URL `{listen_url}`; expected `ws://IP:PORT` or `stdio`"            ),            ExecServerListenUrlParseError::InvalidWebSocketListenUrl(listen_url) => write!(                f,                "invalid websocket --listen URL `{listen_url}`; expected `ws://IP:PORT`"            ),        }    }}impl std::error::Error for ExecServerListenUrlParseError {}pub(crate) fn parse_listen_url(    listen_url: &str,) -> Result<ExecServerListenTransport, ExecServerListenUrlParseError> {    if matches!(listen_url, "stdio" | "stdio://") {        return Ok(ExecServerListenTransport::Stdio);    }    if let Some(socket_addr) = listen_url.strip_prefix("ws://") {        return socket_addr            .parse::<SocketAddr>()            .map(ExecServerListenTransport::WebSocket)            .map_err(|_| {                ExecServerListenUrlParseError::InvalidWebSocketListenUrl(listen_url.to_string())            });    }    Err(ExecServerListenUrlParseError::UnsupportedListenUrl(        listen_url.to_string(),    ))}pub(crate) async fn run_transport(    listen_url: &str,    runtime_paths: ExecServerRuntimePaths,) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    match parse_listen_url(listen_url)? {        ExecServerListenTransport::WebSocket(bind_address) => {            run_websocket_listener(bind_address, runtime_paths).await        }        ExecServerListenTransport::Stdio => run_stdio_connection(runtime_paths).await,    }}async fn run_stdio_connection(    runtime_paths: ExecServerRuntimePaths,) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    run_stdio_connection_with_io(io::stdin(), io::stdout(), runtime_paths).await}async fn run_stdio_connection_with_io<R, W>(    reader: R,    writer: W,    runtime_paths: ExecServerRuntimePaths,) -> Result<(), Box<dyn std::error::Error + Send + Sync>>where    R: AsyncRead + Unpin + Send + 'static,    W: AsyncWrite + Unpin + Send + 'static,{    let processor = ConnectionProcessor::new(runtime_paths);    tracing::info!("codex-exec-server listening on stdio");    processor        .run_connection(JsonRpcConnection::from_stdio(            reader,            writer,            "exec-server stdio".to_string(),        ))        .await;    Ok(())}async fn run_websocket_listener(    bind_address: SocketAddr,    runtime_paths: ExecServerRuntimePaths,) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    let listener = TcpListener::bind(bind_address).await?;    let local_addr = listener.local_addr()?;    let processor = ConnectionProcessor::new(runtime_paths);    info!("codex-exec-server listening on ws://{local_addr}");    println!("ws://{local_addr}");    std::io::stdout().flush()?;    let router = Router::new()        .route("/", any(websocket_upgrade_handler))        .route("/readyz", get(readiness_handler))        .layer(middleware::from_fn(reject_requests_with_origin_header))        .with_state(ExecServerWebSocketState { processor });    axum::serve(        listener,        router.into_make_service_with_connect_info::<SocketAddr>(),    )    .await?;    Ok(())}#[derive(Clone)]struct ExecServerWebSocketState {    processor: ConnectionProcessor,}async fn readiness_handler() -> StatusCode {    StatusCode::OK}async fn reject_requests_with_origin_header(    request: Request<Body>,    next: Next,) -> Result<Response, StatusCode> {    if request.headers().contains_key(ORIGIN) {        warn!(            method = %request.method(),            uri = %request.uri(),            "rejecting exec-server websocket listener request with Origin header"        );        Err(StatusCode::FORBIDDEN)    } else {        Ok(next.run(request).await)    }}async fn websocket_upgrade_handler(    websocket: WebSocketUpgrade,    ConnectInfo(peer_addr): ConnectInfo<SocketAddr>,    State(state): State<ExecServerWebSocketState>,) -> impl IntoResponse {    info!(%peer_addr, "exec-server websocket client connected");    websocket.on_upgrade(move |stream| async move {        state            .processor            .run_connection(JsonRpcConnection::from_axum_websocket(                stream,                format!("exec-server websocket {peer_addr}"),            ))            .await;    })}#[cfg(test)]#[path = "transport_tests.rs"]mod transport_tests;