use super::stream_sse;
use crate::ai::provider::Decoder;
use crate::ai::sse::SseEvent;
use crate::ai::types::{AiError, Event};
use crate::core::CancelToken;
use std::io::{Read, Write};
use std::net::TcpListener;
fn gzip(data: &[u8]) -> Vec<u8> {
let mut out = vec![0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 0, 0xff];
let chunks: Vec<&[u8]> = data.chunks(0xFFFF).collect();
for (index, chunk) in chunks.iter().enumerate() {
let length = chunk.len() as u16;
out.push(u8::from(index + 1 == chunks.len()));
out.extend(length.to_le_bytes());
out.extend((!length).to_le_bytes());
out.extend_from_slice(chunk);
}
out.extend(crc32(data).to_le_bytes());
out.extend((data.len() as u32).to_le_bytes());
out
}
fn crc32(data: &[u8]) -> u32 {
!data.iter().fold(!0u32, |crc, byte| {
(0..8).fold(crc ^ u32::from(*byte), |crc, _| if crc & 1 == 1 { (crc >> 1) ^ 0xEDB8_8320 } else { crc >> 1 })
})
}
fn serve_gzip(body: &str) -> (String, std::sync::mpsc::Receiver<String>) {
let listener = TcpListener::bind("127.0.0.1:0").expect("puerto libre");
let url = format!("http://{}/v1/stream", listener.local_addr().expect("dirección"));
let compressed = gzip(body.as_bytes());
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let (mut socket, _) = listener.accept().expect("conexión");
let (mut received, mut buffer) = (Vec::new(), [0u8; 8192]);
loop {
let read = socket.read(&mut buffer).expect("lectura");
received.extend_from_slice(&buffer[..read]);
let text = String::from_utf8_lossy(&received).to_string();
let Some(head) = text.find("\r\n\r\n") else { continue };
let length = text
.lines()
.find_map(|line| {
line.to_lowercase().strip_prefix("content-length:").map(|value| value.trim().to_owned())
})
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(0);
if read == 0 || received.len() >= head + 4 + length {
let _ = sender.send(text[..head].to_owned());
break;
}
}
let head = format!(
"HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\ncontent-encoding: gzip\r\ncontent-length: {}\r\n\
connection: close\r\n\r\n",
compressed.len()
);
let _ = socket.write_all(head.as_bytes());
let _ = socket.write_all(&compressed);
});
(url, receiver)
}
#[derive(Default)]
struct Lines {
done: bool,
}
impl Decoder for Lines {
fn feed(&mut self, event: &SseEvent, out: &mut Vec<Event>) -> Result<(), AiError> {
if event.data == "[DONE]" {
self.done = true;
} else {
out.push(Event::Text(event.data.clone()));
}
Ok(())
}
fn finish(&mut self, _out: &mut Vec<Event>) {}
fn is_complete(&self) -> bool {
self.done
}
}
#[test]
fn pide_gzip_y_descomprime_el_stream() {
let (url, headers) = serve_gzip("data: hola\n\ndata: mundo\n\ndata: [DONE]\n\n");
let mut texts = Vec::new();
let body = serde_json::json!({ "prompt": "hola" });
stream_sse(&url, &[], &body, &CancelToken::default(), &mut Lines::default(), &mut |event| {
if let Event::Text(text) = event {
texts.push(text);
}
})
.expect("el stream comprimido se lee entero");
assert_eq!(texts, ["hola", "mundo"]);
let headers = headers.recv().expect("cabeceras").to_lowercase();
assert!(headers.lines().any(|line| line.starts_with("accept-encoding:") && line.contains("gzip")), "{headers}");
}