export default defineEventHandler(async (event) => { try { const requestData = await readBody(event); if (!requestData.url) { return { statusCode: 400, message: "URL is required", }; } let { url, method = 'GET', headers = {}, params = [], auth = {}, requestBody = {}, timeout = 30000 } = requestData; // Fix URL if it doesn't have protocol if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } const startTime = Date.now(); // Build final URL with query parameters let urlObj; try { urlObj = new URL(url); } catch (error) { return { statusCode: 400, message: "Invalid URL format", data: { status: 400, statusText: 'Bad Request', headers: {}, data: { error: 'Invalid URL format. Please check the URL and try again.' }, time: 0, size: 0 } }; } // Add active query parameters params.forEach(param => { if (param.active && param.key && param.value) { urlObj.searchParams.set(param.key, param.value); } }); // Add API key to query if specified if (auth.type === 'apiKey' && auth.apiKey?.key && auth.apiKey?.value && auth.apiKey?.addTo === 'query') { urlObj.searchParams.set(auth.apiKey.key, auth.apiKey.value); } const finalUrl = urlObj.toString(); // Build headers const requestHeaders = {}; // Add custom headers headers.forEach(header => { if (header.active && header.key && header.value) { requestHeaders[header.key] = header.value; } }); // Add authentication headers if (auth.type === 'bearer' && auth.bearer) { requestHeaders['Authorization'] = `Bearer ${auth.bearer}`; } else if (auth.type === 'basic' && auth.basic?.username && auth.basic?.password) { const credentials = Buffer.from(`${auth.basic.username}:${auth.basic.password}`).toString('base64'); requestHeaders['Authorization'] = `Basic ${credentials}`; } else if (auth.type === 'apiKey' && auth.apiKey?.key && auth.apiKey?.value && auth.apiKey?.addTo === 'header') { requestHeaders[auth.apiKey.key] = auth.apiKey.value; } else if (auth.type === 'oauth2' && auth.oauth2?.accessToken) { requestHeaders['Authorization'] = `${auth.oauth2.tokenType || 'Bearer'} ${auth.oauth2.accessToken}`; } // Build request body let requestBodyData = null; let contentType = null; if (method !== 'GET' && method !== 'HEAD' && requestBody.type && requestBody.type !== 'none') { if (requestBody.type === 'raw' && requestBody.raw) { requestBodyData = requestBody.raw; // Try to parse as JSON to set appropriate content type try { JSON.parse(requestBody.raw); contentType = 'application/json'; } catch (e) { contentType = 'text/plain'; } } else if (requestBody.type === 'x-www-form-urlencoded' && requestBody.urlEncoded) { const urlEncodedData = new URLSearchParams(); requestBody.urlEncoded.forEach(item => { if (item.active && item.key && item.value) { urlEncodedData.append(item.key, item.value); } }); requestBodyData = urlEncodedData.toString(); contentType = 'application/x-www-form-urlencoded'; } else if (requestBody.type === 'form-data' && requestBody.formData) { // For form-data, we'll handle files as base64 for now // In a real implementation, you'd want multipart/form-data support const formData = new URLSearchParams(); let hasFiles = false; requestBody.formData.forEach(item => { if (item.active && item.key) { if (item.type === 'text' && item.value) { formData.append(item.key, item.value); } else if (item.type === 'file' && item.file) { // For now, we'll send file name as value // Real file upload would need special handling formData.append(item.key, `[FILE: ${item.file.name}]`); hasFiles = true; } } }); if (hasFiles) { // Log a warning that file uploads aren't fully supported yet console.warn('File uploads in form-data are not fully supported in the backend proxy yet'); } requestBodyData = formData.toString(); contentType = 'application/x-www-form-urlencoded'; // Fallback to URL-encoded for now } } // Set content type if not already set if (contentType && !requestHeaders['Content-Type'] && !requestHeaders['content-type']) { requestHeaders['Content-Type'] = contentType; } // Create fetch options const fetchOptions = { method, headers: requestHeaders, signal: AbortSignal.timeout(timeout) }; if (requestBodyData) { fetchOptions.body = requestBodyData; } // Make the actual HTTP request const response = await fetch(finalUrl, fetchOptions); const endTime = Date.now(); const responseTime = endTime - startTime; // Get response data const responseHeaders = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); let responseData; const contentTypeHeader = response.headers.get('content-type') || ''; if (contentTypeHeader.includes('application/json')) { try { responseData = await response.json(); } catch (e) { responseData = await response.text(); } } else if (contentTypeHeader.includes('text/') || contentTypeHeader.includes('application/xml')) { responseData = await response.text(); } else { // For binary data, convert to text representation try { responseData = await response.text(); } catch (e) { responseData = '[Binary data]'; } } // Calculate response size (approximate) const responseSize = typeof responseData === 'string' ? new Blob([responseData]).size : new Blob([JSON.stringify(responseData)]).size; return { statusCode: 200, message: "Request completed", data: { status: response.status, statusText: response.statusText, headers: responseHeaders, data: responseData, time: responseTime, size: responseSize, url: finalUrl } }; } catch (error) { console.error('API Platform Request Error:', error); const endTime = Date.now(); // Handle different types of errors if (error.name === 'AbortError') { return { statusCode: 408, message: "Request timeout", data: { status: 408, statusText: 'Request Timeout', headers: {}, data: { error: 'Request timed out' }, time: 30000, size: 0 } }; } if (error.name === 'TypeError' && error.message.includes('fetch')) { return { statusCode: 400, message: "Invalid URL or network error", data: { status: 400, statusText: 'Bad Request', headers: {}, data: { error: 'Invalid URL format or network error' }, time: endTime - Date.now() + 1000, size: 0 } }; } if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') { return { statusCode: 502, message: "Connection error", data: { status: 502, statusText: 'Bad Gateway', headers: {}, data: { error: 'Failed to connect to the server' }, time: endTime - Date.now() + 1000, size: 0 } }; } return { statusCode: 500, message: "Internal server error", data: { status: 500, statusText: 'Internal Server Error', headers: {}, data: { error: error.message || 'Something went wrong' }, time: endTime - Date.now() + 1000, size: 0 } }; } });