diff options
Diffstat (limited to 'crates/asist-core/src/http.rs')
| -rw-r--r-- | crates/asist-core/src/http.rs | 66 |
1 files changed, 34 insertions, 32 deletions
diff --git a/crates/asist-core/src/http.rs b/crates/asist-core/src/http.rs index 32b6680..3273bb8 100644 --- a/crates/asist-core/src/http.rs +++ b/crates/asist-core/src/http.rs @@ -1,12 +1,12 @@ -//! Cliente HTTP/1.1 mínimo para hablar con los servidores locales. +//! Minimal HTTP/1.1 client to talk to the local servers. //! -//! Los tres motores (llama-server, tts-server) escuchan en localhost sobre HTTP -//! plano, así que no hace falta TLS ni un runtime asíncrono. A cambio de las -//! ~300 líneas de aquí se gana lo único que ninguna librería genérica da -//! cómodo y que este pipeline necesita de verdad: **abortar una respuesta a -//! media descarga**. Cuando el usuario interrumpe al asistente hay que dejar de -//! leer audio ya generado y cerrar el socket en ese mismo instante; con un -//! cliente que sólo expone `read_to_end` eso no se puede hacer. +//! The engines (llama-server, tts-server) listen on localhost over plain +//! HTTP, so neither TLS nor an async runtime is needed. In exchange for the +//! ~300 lines here we get the one thing no generic library makes easy and +//! this pipeline really needs: **aborting a response mid-download**. When the +//! user interrupts the assistant, reading already-generated audio must stop +//! and the socket must close that very instant; a client that only exposes +//! `read_to_end` cannot do that. use std::io::{BufRead, BufReader, Read, Write}; use std::net::TcpStream; @@ -16,11 +16,11 @@ use std::time::Duration; use crate::error::{Error, Result}; -/// Bandera compartida que aborta una lectura en curso. +/// Shared flag that aborts a read in progress. /// -/// Se comprueba entre bloques, así que el corte ocurre como muy tarde un -/// `read` después de activarla —del orden de milisegundos con los tamaños de -/// bloque que usan los servidores. +/// It is checked between blocks, so the cut happens at most one `read` after +/// it is set, in the order of milliseconds with the block sizes the servers +/// use. #[derive(Clone, Default)] pub struct Cancel(Arc<AtomicBool>); @@ -44,11 +44,11 @@ impl Cancel { #[derive(Clone, Debug)] pub struct HttpClient { - /// `host:puerto` del servidor local. + /// `host:port` of the local server. pub authority: String, pub connect_timeout: Duration, - /// Tiempo máximo sin recibir un solo byte. No es el tiempo total: una - /// síntesis larga puede tardar minutos siempre que siga fluyendo. + /// Maximum time without receiving a single byte. It is not the total time: + /// a long synthesis can take minutes as long as it keeps flowing. pub read_timeout: Duration, } @@ -75,7 +75,7 @@ impl HttpClient { } })?; stream.set_read_timeout(Some(self.read_timeout))?; - // Los cuerpos son pequeños y la latencia manda: nada de Nagle. + // Bodies are small and latency rules: no Nagle. let _ = stream.set_nodelay(true); Ok(stream) } @@ -106,22 +106,22 @@ impl HttpClient { Response::read_head(BufReader::new(stream), url) } - /// GET que devuelve el cuerpo completo ya decodificado. + /// GET returning the whole, already decoded body. pub fn get(&self, path: &str) -> Result<Vec<u8>> { self.send("GET", path, None)?.into_body() } - /// POST de JSON que devuelve el cuerpo completo. + /// JSON POST returning the whole body. pub fn post_json(&self, path: &str, body: &serde_json::Value) -> Result<Vec<u8>> { let payload = serde_json::to_vec(body)?; self.send("POST", path, Some(&payload))?.into_body() } - /// POST de JSON cuya respuesta se consume a trozos según llega. + /// JSON POST whose response is consumed in chunks as it arrives. /// - /// `on_chunk` recibe cada bloque en cuanto está disponible y devuelve - /// `false` para cortar; junto a `cancel` son las dos vías por las que el - /// barge-in detiene una síntesis a medias. + /// `on_chunk` gets each block as soon as it is available and returns + /// `false` to stop; together with `cancel` they are the two ways barge-in + /// stops a synthesis midway. pub fn post_json_streaming( &self, path: &str, @@ -135,7 +135,7 @@ impl HttpClient { response.stream(cancel, &mut on_chunk) } - /// POST de JSON que entrega la respuesta línea a línea (SSE de llama-server). + /// JSON POST that delivers the response line by line (llama-server SSE). pub fn post_json_lines( &self, path: &str, @@ -157,7 +157,7 @@ impl HttpClient { }) } - /// Sondea `/health` y devuelve `true` si el servidor está listo. + /// Polls `/health` and returns `true` if the server is ready. pub fn healthy(&self, path: &str) -> bool { self.get(path).is_ok() } @@ -172,14 +172,14 @@ fn resolve(authority: &str, path: &str) -> Result<std::net::SocketAddr> { source: e, })? .next() - .ok_or_else(|| Error::Config(format!("no se pudo resolver «{authority}»"))) + .ok_or_else(|| Error::Config(format!("could not resolve «{authority}»"))) } -/// Codificación del cuerpo anunciada por el servidor. +/// Body encoding announced by the server. enum Body { Chunked, Length(usize), - /// Sin longitud ni chunked: el cuerpo acaba cuando cierra el socket. + /// No length and not chunked: the body ends when the socket closes. UntilClose, } @@ -203,7 +203,9 @@ impl Response { .split_whitespace() .nth(1) .and_then(|s| s.parse::<u16>().ok()) - .ok_or_else(|| Error::Config(format!("respuesta HTTP ilegible de {url}: {line:?}")))?; + .ok_or_else(|| { + Error::Config(format!("unreadable HTTP response from {url}: {line:?}")) + })?; let mut body = Body::UntilClose; loop { @@ -246,7 +248,7 @@ impl Response { } let status = self.status; let url = self.url.clone(); - // Leer el cuerpo del error es lo que convierte un «500» en un mensaje útil. + // Reading the error body is what turns a «500» into a useful message. let body = self.drain().unwrap_or_default(); Err(Error::Http { status, @@ -291,11 +293,11 @@ impl Response { if size_line.is_empty() { continue; } - // Las extensiones de chunk van tras un ';' y aquí no interesan. + // Chunk extensions come after a ';' and do not matter here. let size = usize::from_str_radix(size_line.split(';').next().unwrap_or("0").trim(), 16) .map_err(|_| { - Error::Config(format!("tamaño de chunk ilegible: {size_line:?}")) + Error::Config(format!("unreadable chunk size: {size_line:?}")) })?; if size == 0 { return Ok(()); @@ -312,7 +314,7 @@ impl Response { } left -= want; } - // CRLF de cierre del chunk. + // Closing CRLF of the chunk. let mut crlf = [0u8; 2]; self.reader.read_exact(&mut crlf).map_err(io)?; }, |