|
@@ -23,6 +23,124 @@ const corsHeaders = {
|
|
|
* Request and response bodies are proxied as-is.
|
|
* 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)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
serve(async (req) => {
|
|
serve(async (req) => {
|
|
|
// Handle CORS preflight
|
|
// Handle CORS preflight
|
|
|
if (req.method === 'OPTIONS') {
|
|
if (req.method === 'OPTIONS') {
|
|
@@ -124,13 +242,14 @@ serve(async (req) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Make request to ShopRenter API using fetch
|
|
|
|
|
- // Deno's fetch implementation will use HTTP/1.1 by default
|
|
|
|
|
- const shopRenterResponse = await fetch(shopRenterUrl, {
|
|
|
|
|
- method: req.method,
|
|
|
|
|
- headers: shopRenterHeaders,
|
|
|
|
|
- body: body,
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ // 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
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
console.log(`[ShopRenter Proxy] ShopRenter API responded with status: ${shopRenterResponse.status}`)
|
|
console.log(`[ShopRenter Proxy] ShopRenter API responded with status: ${shopRenterResponse.status}`)
|
|
|
|
|
|
|
@@ -138,7 +257,7 @@ serve(async (req) => {
|
|
|
const responseHeaders = new Headers(corsHeaders)
|
|
const responseHeaders = new Headers(corsHeaders)
|
|
|
|
|
|
|
|
// Copy relevant headers from ShopRenter response
|
|
// Copy relevant headers from ShopRenter response
|
|
|
- for (const [key, value] of shopRenterResponse.headers.entries()) {
|
|
|
|
|
|
|
+ for (const [key, value] of Object.entries(shopRenterResponse.headers)) {
|
|
|
const lowerKey = key.toLowerCase()
|
|
const lowerKey = key.toLowerCase()
|
|
|
// Copy headers but skip transfer-encoding and connection as they're handled by the Edge Function
|
|
// Copy headers but skip transfer-encoding and connection as they're handled by the Edge Function
|
|
|
if (lowerKey !== 'transfer-encoding' && lowerKey !== 'connection') {
|
|
if (lowerKey !== 'transfer-encoding' && lowerKey !== 'connection') {
|
|
@@ -146,12 +265,9 @@ serve(async (req) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Read response body
|
|
|
|
|
- const responseBody = await shopRenterResponse.text()
|
|
|
|
|
-
|
|
|
|
|
// Return proxied response
|
|
// Return proxied response
|
|
|
return new Response(
|
|
return new Response(
|
|
|
- responseBody,
|
|
|
|
|
|
|
+ shopRenterResponse.body,
|
|
|
{
|
|
{
|
|
|
status: shopRenterResponse.status,
|
|
status: shopRenterResponse.status,
|
|
|
statusText: shopRenterResponse.statusText,
|
|
statusText: shopRenterResponse.statusText,
|