Browse Source

fix: handle binary responses in shoprenter-proxy to prevent decompression errors #55

Claude 5 months ago
parent
commit
fa5a60c48f
1 changed files with 58 additions and 19 deletions
  1. 58 19
      supabase/functions/shoprenter-proxy/index.ts

+ 58 - 19
supabase/functions/shoprenter-proxy/index.ts

@@ -34,7 +34,7 @@ async function makeHttp11Request(
   method: string,
   headers: Record<string, string>,
   body?: string
-): Promise<{ status: number; statusText: string; headers: Record<string, string>; body: string }> {
+): Promise<{ status: number; statusText: string; headers: Record<string, string>; body: Uint8Array }> {
   // Open TLS connection
   const conn = await Deno.connectTls({
     hostname: hostname,
@@ -74,16 +74,21 @@ async function makeHttp11Request(
     const encoder = new TextEncoder()
     await conn.write(encoder.encode(request))
 
-    // Read response
-    const decoder = new TextDecoder()
-    const buffer = new Uint8Array(1024 * 1024) // 1MB buffer
-    let responseData = ''
+    // Read response as binary data to preserve gzip/compressed content
+    const chunks: Uint8Array[] = []
+    const buffer = new Uint8Array(64 * 1024) // 64KB buffer per chunk
+    let totalBytes = 0
 
     while (true) {
       try {
         const n = await conn.read(buffer)
         if (n === null) break
-        responseData += decoder.decode(buffer.subarray(0, n))
+
+        // Copy the chunk to preserve it
+        const chunk = new Uint8Array(n)
+        chunk.set(buffer.subarray(0, n))
+        chunks.push(chunk)
+        totalBytes += n
       } catch (e) {
         // Handle UnexpectedEof during read - connection closed by server
         // This is expected when server sends Connection: close
@@ -96,26 +101,57 @@ async function makeHttp11Request(
       }
     }
 
-    console.log('[HTTP/1.0] Received response length:', responseData.length)
-    console.log('[HTTP/1.0] First 500 chars:', responseData.substring(0, 500))
+    console.log('[HTTP/1.0] Received response bytes:', totalBytes)
+
+    // Concatenate all chunks into a single Uint8Array
+    const responseData = new Uint8Array(totalBytes)
+    let offset = 0
+    for (const chunk of chunks) {
+      responseData.set(chunk, offset)
+      offset += chunk.length
+    }
+
+    // Parse HTTP response headers - we need to find the header/body separator
+    // Headers are always ASCII text, so we can decode them safely
+    const decoder = new TextDecoder()
 
-    // Parse HTTP response - try both \r\n\r\n and \n\n separators
-    let headerEndIndex = responseData.indexOf('\r\n\r\n')
-    let headerSeparator = '\r\n\r\n'
+    // Search for \r\n\r\n or \n\n in the binary data
+    let headerEndIndex = -1
+    let headerSeparatorLength = 0
+
+    // Look for \r\n\r\n (most common)
+    for (let i = 0; i < responseData.length - 3; i++) {
+      if (responseData[i] === 13 && responseData[i + 1] === 10 &&
+          responseData[i + 2] === 13 && responseData[i + 3] === 10) {
+        headerEndIndex = i
+        headerSeparatorLength = 4
+        break
+      }
+    }
 
+    // If not found, look for \n\n
     if (headerEndIndex === -1) {
-      // Try Unix-style line endings
-      headerEndIndex = responseData.indexOf('\n\n')
-      headerSeparator = '\n\n'
+      for (let i = 0; i < responseData.length - 1; i++) {
+        if (responseData[i] === 10 && responseData[i + 1] === 10) {
+          headerEndIndex = i
+          headerSeparatorLength = 2
+          break
+        }
+      }
     }
 
     if (headerEndIndex === -1) {
-      console.error('[HTTP/1.0] Could not find header separator. Response data:', responseData.substring(0, 200))
+      const preview = decoder.decode(responseData.subarray(0, Math.min(200, responseData.length)))
+      console.error('[HTTP/1.0] Could not find header separator. Response preview:', preview)
       throw new Error('Invalid HTTP response: no header/body separator found')
     }
 
-    const headerSection = responseData.substring(0, headerEndIndex)
-    const bodySection = responseData.substring(headerEndIndex + headerSeparator.length)
+    // Decode headers only (they're ASCII)
+    const headerBytes = responseData.subarray(0, headerEndIndex)
+    const headerSection = decoder.decode(headerBytes)
+
+    // Body is everything after the separator (keep as binary)
+    const bodyBytes = responseData.subarray(headerEndIndex + headerSeparatorLength)
 
     // Parse status line
     const lines = headerSection.split(/\r?\n/)
@@ -142,12 +178,14 @@ async function makeHttp11Request(
     }
 
     console.log('[HTTP/1.0] Response received:', status, statusText)
+    console.log('[HTTP/1.0] Content-Encoding:', responseHeaders['Content-Encoding'] || 'none')
+    console.log('[HTTP/1.0] Body size:', bodyBytes.length, 'bytes')
 
     return {
       status,
       statusText,
       headers: responseHeaders,
-      body: bodySection,
+      body: bodyBytes,
     }
   } finally {
     try {
@@ -310,7 +348,8 @@ serve(async (req) => {
       }
     }
 
-    // Return proxied response
+    // Return proxied response with binary body
+    // The body is returned as Uint8Array to preserve gzip/compressed content
     return new Response(
       shopRenterResponse.body,
       {