From 9f06802a501e6437701363a621876d0308d9ae90 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 9 Jul 2023 23:23:50 +0100 Subject: [PATCH] Updates lambda functions with more robust err handling --- server/lambda/check-ports.js | 25 ++++++---- server/lambda/dns-sec.js | 24 +++++----- server/lambda/find-url-ip.js | 29 ++++++++---- server/lambda/follow-redirects.js | 12 +++-- server/lambda/get-carbon.js | 4 +- server/lambda/get-cookies.js | 2 +- server/lambda/get-headers.js | 4 +- server/lambda/get-txt.js | 2 +- server/lambda/read-robots-txt.js | 17 +++++-- server/lambda/server-status.js | 64 ++++++++++++++++--------- server/lambda/site-features.js | 54 +++++++++++++-------- server/lambda/ssl-check.js | 54 +++++++++++---------- server/lambda/trace-route.js | 79 +++++++++---------------------- 13 files changed, 203 insertions(+), 167 deletions(-) diff --git a/server/lambda/check-ports.js b/server/lambda/check-ports.js index ae698bb..4e94c77 100644 --- a/server/lambda/check-ports.js +++ b/server/lambda/check-ports.js @@ -9,7 +9,7 @@ const PORTS = [ ]; async function checkPort(port, domain) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { const socket = new net.Socket(); socket.setTimeout(1500); // you may want to adjust the timeout @@ -21,22 +21,23 @@ async function checkPort(port, domain) { socket.once('timeout', () => { socket.destroy(); + reject(new Error(`Timeout at port: ${port}`)); }); socket.once('error', (e) => { socket.destroy(); + reject(e); }); + socket.connect(port, domain); }); } exports.handler = async (event, context) => { const domain = event.queryStringParameters.url; + if (!domain) { - return { - statusCode: 400, - body: JSON.stringify({ message: "Missing 'domain' parameter" }), - }; + return errorResponse('Missing domain parameter.'); } const delay = ms => new Promise(res => setTimeout(res, ms)); @@ -71,11 +72,19 @@ exports.handler = async (event, context) => { } } + if(timeoutReached){ + return errorResponse('The function timed out before completing.'); + } + return { statusCode: 200, - body: JSON.stringify({ timeout: timeoutReached, openPorts, failedPorts }), + body: JSON.stringify({ openPorts, failedPorts }), }; }; - - +const errorResponse = (message, statusCode = 444) => { + return { + statusCode: statusCode, + body: JSON.stringify({ error: message }), + }; +}; diff --git a/server/lambda/dns-sec.js b/server/lambda/dns-sec.js index 0a5b54d..a8ff986 100644 --- a/server/lambda/dns-sec.js +++ b/server/lambda/dns-sec.js @@ -1,14 +1,10 @@ const https = require('https'); -const urlModule = require('url'); exports.handler = async function(event, context) { let { url } = event.queryStringParameters; if (!url) { - return { - statusCode: 400, - body: JSON.stringify({ error: 'url query parameter is required' }), - }; + return errorResponse('URL query parameter is required.'); } // Extract hostname from URL @@ -40,10 +36,10 @@ exports.handler = async function(event, context) { res.on('end', () => { resolve(JSON.parse(data)); }); - }); - req.on('error', error => { - reject(error); + res.on('error', error => { + reject(error); + }); }); req.end(); @@ -55,10 +51,7 @@ exports.handler = async function(event, context) { records[type] = { isFound: false, answer: null, response: dnsResponse}; } } catch (error) { - return { - statusCode: 500, - body: JSON.stringify({ error: `Error fetching ${type} record: ${error.message}` }), - }; + return errorResponse(`Error fetching ${type} record: ${error.message}`); } } @@ -67,3 +60,10 @@ exports.handler = async function(event, context) { body: JSON.stringify(records), }; }; + +const errorResponse = (message, statusCode = 444) => { + return { + statusCode: statusCode, + body: JSON.stringify({ error: message }), + }; +}; diff --git a/server/lambda/find-url-ip.js b/server/lambda/find-url-ip.js index 437c3ca..7172b6a 100644 --- a/server/lambda/find-url-ip.js +++ b/server/lambda/find-url-ip.js @@ -2,21 +2,32 @@ const dns = require('dns'); /* Lambda function to fetch the IP address of a given URL */ exports.handler = function (event, context, callback) { - const addressParam = event.queryStringParameters.address; + const addressParam = event.queryStringParameters.url; + + if (!addressParam) { + callback(null, errorResponse('Address parameter is missing.')); + return; + } + const address = decodeURIComponent(addressParam) - .replaceAll('https://', '') - .replaceAll('http://', ''); + .replaceAll('https://', '') + .replaceAll('http://', ''); + dns.lookup(address, (err, ip, family) => { if (err) { - callback(null, { - statusCode: 405, - body: JSON.stringify(err), - }) + callback(null, errorResponse(err.message)); } else { - callback(err, { + callback(null, { statusCode: 200, - body: JSON.stringify({ip, family}), + body: JSON.stringify({ ip, family }), }); } }); }; + +const errorResponse = (message, statusCode = 444) => { + return { + statusCode: statusCode, + body: JSON.stringify({ error: message }), + }; +}; diff --git a/server/lambda/follow-redirects.js b/server/lambda/follow-redirects.js index 0ff4cb1..45301ad 100644 --- a/server/lambda/follow-redirects.js +++ b/server/lambda/follow-redirects.js @@ -23,9 +23,13 @@ exports.handler = async (event) => { }), }; } catch (error) { - return { - statusCode: 500, - body: `Error: ${error.message}`, - }; + return errorResponse(`Error: ${error.message}`); } }; + +const errorResponse = (message, statusCode = 444) => { + return { + statusCode: statusCode, + body: JSON.stringify({ error: message }), + }; +}; diff --git a/server/lambda/get-carbon.js b/server/lambda/get-carbon.js index 16c001c..c11605b 100644 --- a/server/lambda/get-carbon.js +++ b/server/lambda/get-carbon.js @@ -6,7 +6,7 @@ exports.handler = async (event, context) => { if (!url) { return { statusCode: 400, - body: JSON.stringify({ message: 'url query parameter is required' }), + body: JSON.stringify({ error: 'url query parameter is required' }), }; } @@ -49,7 +49,7 @@ exports.handler = async (event, context) => { } catch (error) { return { statusCode: 500, - body: JSON.stringify({ message: `Error: ${error.message}` }), + body: JSON.stringify({ error: `Error: ${error.message}` }), }; } }; diff --git a/server/lambda/get-cookies.js b/server/lambda/get-cookies.js index ac12ae8..a9b0e23 100644 --- a/server/lambda/get-cookies.js +++ b/server/lambda/get-cookies.js @@ -21,7 +21,7 @@ exports.handler = async function(event, context) { } catch (error) { return { statusCode: 500, - body: JSON.stringify({ message: error.message }), + body: JSON.stringify({ error: error.message }), }; } }; diff --git a/server/lambda/get-headers.js b/server/lambda/get-headers.js index f07eacc..d6cad39 100644 --- a/server/lambda/get-headers.js +++ b/server/lambda/get-headers.js @@ -6,7 +6,7 @@ exports.handler = async function(event, context) { if (!url) { return { statusCode: 400, - body: JSON.stringify({ message: 'url query string parameter is required' }), + body: JSON.stringify({ error: 'url query string parameter is required' }), }; } @@ -21,7 +21,7 @@ exports.handler = async function(event, context) { } catch (error) { return { statusCode: 500, - body: JSON.stringify({ message: error.message }), + body: JSON.stringify({ error: error.message }), }; } }; diff --git a/server/lambda/get-txt.js b/server/lambda/get-txt.js index a296b71..08eebc2 100644 --- a/server/lambda/get-txt.js +++ b/server/lambda/get-txt.js @@ -24,7 +24,7 @@ exports.handler = async (event) => { console.error('Error:', error); return { statusCode: 500, - body: JSON.stringify({ message: error.message }), + body: JSON.stringify({ error: error.message }), }; } }; diff --git a/server/lambda/read-robots-txt.js b/server/lambda/read-robots-txt.js index 2b977fe..599b2f9 100644 --- a/server/lambda/read-robots-txt.js +++ b/server/lambda/read-robots-txt.js @@ -6,11 +6,20 @@ exports.handler = async function(event, context) { if (!siteURL) { return { statusCode: 400, - body: 'Missing URL parameter', + body: JSON.stringify({ error: 'Missing url query parameter' }), + }; + } + + let parsedURL; + try { + parsedURL = new URL(siteURL); + } catch (error) { + return { + statusCode: 400, + body: JSON.stringify({ error: 'Invalid url query parameter' }), }; } - const parsedURL = new URL(siteURL); const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`; try { @@ -25,13 +34,13 @@ exports.handler = async function(event, context) { } else { return { statusCode: response.status, - body: `Failed to fetch robots.txt`, + body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }), }; } } catch (error) { return { statusCode: 500, - body: `Error fetching robots.txt: ${error.toString()}`, + body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }), }; } }; diff --git a/server/lambda/server-status.js b/server/lambda/server-status.js index a8b5e40..6d0dbbf 100644 --- a/server/lambda/server-status.js +++ b/server/lambda/server-status.js @@ -7,13 +7,13 @@ exports.handler = async function(event, context) { if (!url) { return { statusCode: 400, - body: JSON.stringify({ message: 'You must provide a URL query parameter!' }), + body: JSON.stringify({ error: 'You must provide a URL query parameter!' }), }; } let dnsLookupTime; let responseCode; - let startTime = performance.now(); + let startTime; const obs = new PerformanceObserver((items) => { dnsLookupTime = items.getEntries()[0].duration; @@ -24,28 +24,46 @@ exports.handler = async function(event, context) { performance.mark('A'); - return new Promise((resolve) => { - https.get(url, (res) => { - let data = ''; - responseCode = res.statusCode; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - performance.mark('B'); - performance.measure('A to B', 'A', 'B'); - let responseTime = performance.now() - startTime; - obs.disconnect(); - resolve({ - statusCode: 200, - body: JSON.stringify({ isUp: true, dnsLookupTime, responseTime, responseCode }), + try { + startTime = performance.now(); + const response = await new Promise((resolve, reject) => { + const req = https.get(url, res => { + let data = ''; + responseCode = res.statusCode; + res.on('data', chunk => { + data += chunk; + }); + res.on('end', () => { + resolve(res); }); }); - }).on('error', (e) => { - resolve({ - statusCode: 500, - body: JSON.stringify({ isUp: false, error: e.message }), - }); + + req.on('error', reject); + req.end(); }); - }); + + if (responseCode < 200 || responseCode >= 400) { + return { + statusCode: 200, + body: JSON.stringify({ error: `Received non-success response code: ${responseCode}` }), + }; + } + + performance.mark('B'); + performance.measure('A to B', 'A', 'B'); + let responseTime = performance.now() - startTime; + obs.disconnect(); + + return { + statusCode: 200, + body: JSON.stringify({ isUp: true, dnsLookupTime, responseTime, responseCode }), + }; + + } catch (error) { + obs.disconnect(); + return { + statusCode: 200, + body: JSON.stringify({ error: `Error during operation: ${error.message}` }), + }; + } }; diff --git a/server/lambda/site-features.js b/server/lambda/site-features.js index 12196b6..f38bb3a 100644 --- a/server/lambda/site-features.js +++ b/server/lambda/site-features.js @@ -4,7 +4,7 @@ exports.handler = async function (event, context) { const { url } = event.queryStringParameters; const apiKey = process.env.BUILT_WITH_API_KEY; - const errorResponse = (message, statusCode = 444) => { + const errorResponse = (message, statusCode = 500) => { return { statusCode: statusCode, body: JSON.stringify({ error: message }), @@ -12,33 +12,45 @@ exports.handler = async function (event, context) { }; if (!url) { - return errorResponse('url query parameter is required', 400); + return errorResponse('URL query parameter is required', 400); + } + + if (!apiKey) { + return errorResponse('Missing BuiltWith API key in environment variables', 500); } const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`; - return new Promise((resolve, reject) => { - https.get(apiUrl, (res) => { - let data = ''; + try { + const response = await new Promise((resolve, reject) => { + const req = https.get(apiUrl, res => { + let data = ''; - // A chunk of data has been received. - res.on('data', (chunk) => { - data += chunk; + res.on('data', chunk => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode <= 299) { + resolve(data); + } else { + reject(new Error(`Request failed with status code: ${res.statusCode}`)); + } + }); }); - // The whole response has been received. - res.on('end', () => { - if(res.statusCode !== 200){ - resolve(errorResponse(`Request failed with status code: ${res.statusCode}`)); - } else { - resolve({ - statusCode: 200, - body: data, - }); - } + req.on('error', error => { + reject(error); }); - }).on('error', (err) => { - resolve(errorResponse(`Error making request: ${err.message}`, 500)); + + req.end(); }); - }); + + return { + statusCode: 200, + body: response, + }; + } catch (error) { + return errorResponse(`Error making request: ${error.message}`); + } }; diff --git a/server/lambda/ssl-check.js b/server/lambda/ssl-check.js index c37efa1..8f139c0 100644 --- a/server/lambda/ssl-check.js +++ b/server/lambda/ssl-check.js @@ -3,7 +3,7 @@ const https = require('https'); exports.handler = async function (event, context) { const { url } = event.queryStringParameters; - const errorResponse = (message, statusCode = 444) => { + const errorResponse = (message, statusCode = 500) => { return { statusCode: statusCode, body: JSON.stringify({ error: message }), @@ -11,34 +11,40 @@ exports.handler = async function (event, context) { }; if (!url) { - return errorResponse('url query parameter is required'); + return errorResponse('URL query parameter is required', 400); } - return new Promise((resolve, reject) => { - const req = https.request(url, res => { + try { + const response = await new Promise((resolve, reject) => { + const req = https.request(url, res => { - // Check if the SSL handshake was authorized - if (!res.socket.authorized) { - resolve(errorResponse(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`)); - } else { - let cert = res.socket.getPeerCertificate(true); - if (!cert || Object.keys(cert).length === 0) { - resolve(errorResponse("No certificate presented by the server.")); + // Check if the SSL handshake was authorized + if (!res.socket.authorized) { + resolve(errorResponse(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`)); } else { - // omit the raw and issuerCertificate fields - const { raw, issuerCertificate, ...certWithoutRaw } = cert; - resolve({ - statusCode: 200, - body: JSON.stringify(certWithoutRaw), - }); + let cert = res.socket.getPeerCertificate(true); + if (!cert || Object.keys(cert).length === 0) { + resolve(errorResponse("No certificate presented by the server.")); + } else { + // omit the raw and issuerCertificate fields + const { raw, issuerCertificate, ...certWithoutRaw } = cert; + resolve({ + statusCode: 200, + body: JSON.stringify(certWithoutRaw), + }); + } } - } + }); + + req.on('error', error => { + resolve(errorResponse(`Error fetching site certificate: ${error.message}`)); + }); + + req.end(); }); - req.on('error', (error) => { - resolve(errorResponse(`Error fetching site certificate: ${error.message}`, 500)); - }); - - req.end(); - }); + return response; + } catch (error) { + return errorResponse(`Unexpected error occurred: ${error.message}`); + } }; diff --git a/server/lambda/trace-route.js b/server/lambda/trace-route.js index 8720c65..08f8cbf 100644 --- a/server/lambda/trace-route.js +++ b/server/lambda/trace-route.js @@ -3,19 +3,10 @@ const url = require('url'); exports.handler = async function(event, context) { const urlString = event.queryStringParameters.url; - const startTime = Date.now(); try { if (!urlString) { - - return { - statusCode: 400, - body: JSON.stringify({ - - - error: 'URL parameter is missing!' - }), - }; + throw new Error('URL parameter is missing!'); } // Parse the URL and get the hostname @@ -23,66 +14,42 @@ exports.handler = async function(event, context) { const host = urlObject.hostname; if (!host) { - - return { - statusCode: 400, - body: JSON.stringify({ - error: 'Invalid URL provided' - }), - }; + throw new Error('Invalid URL provided'); } // Traceroute with callback - return await new Promise((resolve, reject) => { + const result = await new Promise((resolve, reject) => { traceroute.trace(host, (err, hops) => { - if (err) { - reject(err); - } else if (hops) { + if (err || !hops) { + reject(err || new Error('No hops found')); + } else { resolve(hops); } }); + // Check if remaining time is less than 8.8 seconds, then reject promise if (context.getRemainingTimeInMillis() < 8800) { reject(new Error('Lambda is about to timeout')); } - }).then((result) => { - const timeTaken = Date.now() - startTime; - - return { - statusCode: 200, - body: JSON.stringify({ - message: "Traceroute completed!", - result, - timeTaken - }), - }; - }).catch((err) => { - return { - statusCode: 500, - body: JSON.stringify({ - error: err.message - }), - }; }); - + return { + statusCode: 200, + body: JSON.stringify({ + message: "Traceroute completed!", + result, + }), + }; } catch (err) { - if (err.code === 'ENOENT') { + const message = err.code === 'ENOENT' + ? 'Traceroute command is not installed on the host.' + : err.message; - return { - statusCode: 400, - body: JSON.stringify({ - error: 'Traceroute command is not installed on the host.' - }), - }; - } else { - - return { - statusCode: 500, - body: JSON.stringify({ - error: err.message - }), - }; - } + return { + statusCode: 500, + body: JSON.stringify({ + error: message, + }), + }; } };