import { connect } from "cloudflare:sockets"; // Forward request via raw TCP socket (bypasses CF auto headers) export async function handleForwardRaw(request) { try { const { targetUrl, headers = {}, body } = await request.json(); if (!targetUrl) { return new Response(JSON.stringify({ error: "targetUrl is required" }), { status: 400, headers: { "Content-Type": "application/json" } }); } const url = new URL(targetUrl); const host = url.hostname; const port = url.port || (url.protocol === "https:" ? 443 : 80); const path = url.pathname + url.search; const isHttps = url.protocol === "https:"; console.log("[FORWARD_RAW] Connecting to:", host, port, isHttps ? "(TLS)" : ""); // Connect to target server let secureSocket; if (isHttps) { // For HTTPS, connect directly with TLS enabled console.log("[FORWARD_RAW] Creating TLS socket..."); secureSocket = connect({ hostname: host, port: parseInt(port), secureTransport: "on" }); console.log("[FORWARD_RAW] TLS socket created"); } else { secureSocket = connect({ hostname: host, port: parseInt(port) }); } console.log("[FORWARD_RAW] Socket object:", secureSocket); console.log("[FORWARD_RAW] Socket opened:", secureSocket.opened); // Wait for socket to be ready try { console.log("[FORWARD_RAW] Waiting for socket to open..."); await secureSocket.opened; console.log("[FORWARD_RAW] Socket opened successfully"); } catch (openError) { console.error("[FORWARD_RAW] Socket open error:", openError.message); throw openError; } console.log("[FORWARD_RAW] Getting writer and reader..."); const writer = secureSocket.writable.getWriter(); const reader = secureSocket.readable.getReader(); console.log("[FORWARD_RAW] Writer and reader obtained"); // Build raw HTTP request const bodyStr = JSON.stringify(body); const requestHeaders = { "Host": host, "Content-Type": "application/json", "Content-Length": new TextEncoder().encode(bodyStr).length.toString(), "Connection": "close", ...headers }; // Build HTTP request string let httpRequest = `POST ${path} HTTP/1.1\r\n`; for (const [key, value] of Object.entries(requestHeaders)) { httpRequest += `${key}: ${value}\r\n`; } httpRequest += `\r\n${bodyStr}`; console.log("[FORWARD_RAW] Sending request:", httpRequest.substring(0, 300)); console.log("[FORWARD_RAW] Full request length:", httpRequest.length); // Send request try { console.log("[FORWARD_RAW] Writing to socket..."); await writer.write(new TextEncoder().encode(httpRequest)); console.log("[FORWARD_RAW] Write complete, closing writer..."); await writer.close(); console.log("[FORWARD_RAW] Writer closed"); } catch (writeError) { console.error("[FORWARD_RAW] Write error:", writeError.message); throw writeError; } // Read response with timeout console.log("[FORWARD_RAW] Starting to read response..."); let responseData = new Uint8Array(0); let attempts = 0; const maxAttempts = 100; // 10 seconds max while (attempts < maxAttempts) { console.log("[FORWARD_RAW] Reading attempt:", attempts); const { done, value } = await reader.read(); console.log("[FORWARD_RAW] Read result - done:", done, "value length:", value?.length); if (done) break; if (value) { const newData = new Uint8Array(responseData.length + value.length); newData.set(responseData); newData.set(value, responseData.length); responseData = newData; // Check if we have complete response (has headers end marker) const text = new TextDecoder().decode(responseData); if (text.includes("\r\n\r\n")) { // Check if we have Content-Length and received all body const headerEnd = text.indexOf("\r\n\r\n"); const headers = text.substring(0, headerEnd).toLowerCase(); const contentLengthMatch = headers.match(/content-length:\s*(\d+)/); if (contentLengthMatch) { const expectedLength = parseInt(contentLengthMatch[1]); const bodyReceived = text.length - headerEnd - 4; if (bodyReceived >= expectedLength) { console.log("[FORWARD_RAW] Complete response received"); break; } } } } attempts++; } console.log("[FORWARD_RAW] Read loop finished, total bytes:", responseData.length); const responseText = new TextDecoder().decode(responseData); console.log("[FORWARD_RAW] Response received:", responseText.substring(0, 500)); // Parse HTTP response const headerEndIndex = responseText.indexOf("\r\n\r\n"); if (headerEndIndex === -1) { console.log("[FORWARD_RAW] Full response data:", responseText); throw new Error("Invalid HTTP response - no header end found"); } const headerPart = responseText.substring(0, headerEndIndex); const bodyPart = responseText.substring(headerEndIndex + 4); // Parse status line const statusLine = headerPart.split("\r\n")[0]; const statusMatch = statusLine.match(/HTTP\/[\d.]+ (\d+)/); const status = statusMatch ? parseInt(statusMatch[1]) : 200; // Parse headers const responseHeaders = {}; const headerLines = headerPart.split("\r\n").slice(1); for (const line of headerLines) { const colonIndex = line.indexOf(":"); if (colonIndex > 0) { const key = line.substring(0, colonIndex).trim(); const value = line.substring(colonIndex + 1).trim(); responseHeaders[key.toLowerCase()] = value; } } return new Response(bodyPart, { status, headers: { "Content-Type": responseHeaders["content-type"] || "application/json", "Access-Control-Allow-Origin": "*" } }); } catch (error) { console.error("[FORWARD_RAW] Error:", error.message, error.stack); return new Response(JSON.stringify({ error: error.message }), { status: 500, headers: { "Content-Type": "application/json" } }); } }