Browse Source

fix: use Deno.createHttpClient with HTTP/1.1-only for ShopRenter proxy #55

Claude 5 months ago
parent
commit
acba014
1 changed files with 22 additions and 127 deletions
  1. 22 127
      supabase/functions/shoprenter-proxy/index.ts

+ 22 - 127
supabase/functions/shoprenter-proxy/index.ts

@@ -23,123 +23,12 @@ const corsHeaders = {
  * Request and response bodies are proxied as-is.
  */
 
-/**
- * Makes an HTTP/1.0 request to ShopRenter API
- * This is required because ShopRenter has a bug where it only accepts HTTP/1.0
- */
-async function makeHttp10Request(
-  urlString: string,
-  method: string,
-  headers: Headers,
-  body: string | null
-): Promise<{ status: number; statusText: string; headers: Record<string, string>; body: string }> {
-  const url = new URL(urlString)
-  const hostname = url.hostname
-  const port = url.port || '443'
-  const path = url.pathname + url.search
-
-  // Build HTTP/1.0 request
-  let request = `${method} ${path} HTTP/1.0\r\n`
-  request += `Host: ${hostname}\r\n`
-
-  // Add all headers
-  for (const [key, value] of headers.entries()) {
-    request += `${key}: ${value}\r\n`
-  }
-
-  // Add Connection: close for HTTP/1.0
-  request += `Connection: close\r\n`
-  request += `\r\n`
-
-  // Add body if present
-  if (body) {
-    request += body
-  }
-
-  console.log(`[HTTP/1.0] Connecting to ${hostname}:${port}`)
-
-  // Connect to server (TLS for HTTPS)
-  const conn = await Deno.connectTls({
-    hostname: hostname,
-    port: parseInt(port),
-  })
-
-  try {
-    // Send request
-    const encoder = new TextEncoder()
-    await conn.write(encoder.encode(request))
-
-    console.log('[HTTP/1.0] Request sent, reading response...')
-
-    // Read response
-    const decoder = new TextDecoder()
-    const chunks: Uint8Array[] = []
-    const buffer = new Uint8Array(4096)
-
-    while (true) {
-      const n = await conn.read(buffer)
-      if (n === null) break // EOF
-      chunks.push(buffer.slice(0, n))
-    }
-
-    // Combine all chunks
-    const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
-    const responseData = new Uint8Array(totalLength)
-    let offset = 0
-    for (const chunk of chunks) {
-      responseData.set(chunk, offset)
-      offset += chunk.length
-    }
-
-    const responseText = decoder.decode(responseData)
-
-    // Parse HTTP response
-    const lines = responseText.split('\r\n')
-    const statusLine = lines[0]
-    const statusMatch = statusLine.match(/HTTP\/1\.[01] (\d+) (.*)/)
-
-    if (!statusMatch) {
-      throw new Error(`Invalid HTTP response: ${statusLine}`)
-    }
-
-    const status = parseInt(statusMatch[1])
-    const statusText = statusMatch[2]
-
-    // Parse headers
-    const responseHeaders: Record<string, string> = {}
-    let i = 1
-    for (; i < lines.length; i++) {
-      const line = lines[i]
-      if (line === '') break // End of headers
-
-      const colonIndex = line.indexOf(':')
-      if (colonIndex > 0) {
-        const key = line.substring(0, colonIndex).trim()
-        const value = line.substring(colonIndex + 1).trim()
-        responseHeaders[key] = value
-      }
-    }
-
-    // Get body (everything after empty line)
-    const bodyStartIndex = responseText.indexOf('\r\n\r\n')
-    const responseBody = bodyStartIndex >= 0 ? responseText.substring(bodyStartIndex + 4) : ''
-
-    console.log(`[HTTP/1.0] Response received: ${status} ${statusText}`)
-
-    return {
-      status,
-      statusText,
-      headers: responseHeaders,
-      body: responseBody,
-    }
-  } finally {
-    try {
-      conn.close()
-    } catch (e) {
-      console.error('[HTTP/1.0] Error closing connection:', e)
-    }
-  }
-}
+// Create HTTP client that forces HTTP/1.1 only (no HTTP/2)
+// This is required because ShopRenter has issues with HTTP/2
+const httpClient = Deno.createHttpClient({
+  http1: true,  // Enable HTTP/1.1
+  http2: false, // Disable HTTP/2
+})
 
 serve(async (req) => {
   // Handle CORS preflight
@@ -242,14 +131,17 @@ serve(async (req) => {
       }
     }
 
-    // Make request to ShopRenter API using HTTP/1.0
-    // ShopRenter API requires HTTP/1.0 specifically - higher versions cause auth errors
-    const shopRenterResponse = await makeHttp10Request(
-      shopRenterUrl,
-      req.method,
-      shopRenterHeaders,
-      body
-    )
+    // Make request to ShopRenter API using HTTP/1.1 only (HTTP/2 disabled)
+    // ShopRenter API requires HTTP/1.x - higher versions cause auth errors
+    console.log(`[ShopRenter Proxy] Making request with HTTP/1.1 to: ${shopRenterUrl}`)
+    console.log(`[ShopRenter Proxy] Headers:`, Object.fromEntries(shopRenterHeaders.entries()))
+
+    const shopRenterResponse = await fetch(shopRenterUrl, {
+      method: req.method,
+      headers: shopRenterHeaders,
+      body: body,
+      client: httpClient, // Use HTTP/1.1-only client
+    })
 
     console.log(`[ShopRenter Proxy] ShopRenter API responded with status: ${shopRenterResponse.status}`)
 
@@ -257,7 +149,7 @@ serve(async (req) => {
     const responseHeaders = new Headers(corsHeaders)
 
     // Copy relevant headers from ShopRenter response
-    for (const [key, value] of Object.entries(shopRenterResponse.headers)) {
+    for (const [key, value] of shopRenterResponse.headers.entries()) {
       const lowerKey = key.toLowerCase()
       // Copy headers but skip transfer-encoding and connection as they're handled by the Edge Function
       if (lowerKey !== 'transfer-encoding' && lowerKey !== 'connection') {
@@ -265,9 +157,12 @@ serve(async (req) => {
       }
     }
 
+    // Get response body
+    const responseBody = await shopRenterResponse.text()
+
     // Return proxied response
     return new Response(
-      shopRenterResponse.body,
+      responseBody,
       {
         status: shopRenterResponse.status,
         statusText: shopRenterResponse.statusText,