From 6cee466f52a95be45a34bbedcc1eea457c38872e Mon Sep 17 00:00:00 2001 From: Sam Hoang Van Date: Wed, 18 Jun 2025 15:30:31 +0700 Subject: [PATCH] fix(server): add gzip decompression support for /models endpoint filtering (#5349) - Add gzip detection using magic number check (0x1f 0x8b) - Implement gzip decompression before JSON parsing - Add gzip re-compression for filtered responses - Fix "invalid utf-8 sequence" error when upstream returns gzipped content - Maintain Content-Encoding consistency for compressed responses - Add comprehensive gzip handling with flate2 library Resolves issue where filtering failed on gzip-compressed model responses. --- src-tauri/src/core/server.rs | 48 +++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/core/server.rs b/src-tauri/src/core/server.rs index f4f270106..627ec6a7c 100644 --- a/src-tauri/src/core/server.rs +++ b/src-tauri/src/core/server.rs @@ -7,6 +7,8 @@ use std::net::SocketAddr; use std::sync::LazyLock; use tokio::sync::Mutex; use tokio::task::JoinHandle; +use flate2::read::GzDecoder; +use std::io::Read; /// Server handle type for managing the proxy server lifecycle type ServerHandle = JoinHandle>>; @@ -435,9 +437,42 @@ async fn proxy_request( } } +/// Checks if the byte array starts with gzip magic number +fn is_gzip_encoded(bytes: &[u8]) -> bool { + bytes.len() >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b +} + +/// Decompresses gzip-encoded bytes +fn decompress_gzip(bytes: &[u8]) -> Result, Box> { + let mut decoder = GzDecoder::new(bytes); + let mut decompressed = Vec::new(); + decoder.read_to_end(&mut decompressed)?; + Ok(decompressed) +} + +/// Compresses bytes using gzip +fn compress_gzip(bytes: &[u8]) -> Result, Box> { + use flate2::write::GzEncoder; + use flate2::Compression; + use std::io::Write; + + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(bytes)?; + let compressed = encoder.finish()?; + Ok(compressed) +} + /// Filters models response to keep only models with status "downloaded" fn filter_models_response(bytes: &[u8]) -> Result, Box> { - let response_text = std::str::from_utf8(bytes)?; + // Try to decompress if it's gzip-encoded + let decompressed_bytes = if is_gzip_encoded(bytes) { + log::debug!("Response is gzip-encoded, decompressing..."); + decompress_gzip(bytes)? + } else { + bytes.to_vec() + }; + + let response_text = std::str::from_utf8(&decompressed_bytes)?; let mut response_json: Value = serde_json::from_str(response_text)?; // Check if this is a ListModelsResponseDto format with data array @@ -475,8 +510,15 @@ fn filter_models_response(bytes: &[u8]) -> Result, Box