Updates lambda functions with more robust err handling

This commit is contained in:
Alicia Sykes 2023-07-09 23:23:50 +01:00
parent e37474e0d4
commit 9f06802a50
13 changed files with 203 additions and 167 deletions

View file

@ -9,7 +9,7 @@ const PORTS = [
]; ];
async function checkPort(port, domain) { async function checkPort(port, domain) {
return new Promise(resolve => { return new Promise((resolve, reject) => {
const socket = new net.Socket(); const socket = new net.Socket();
socket.setTimeout(1500); // you may want to adjust the timeout socket.setTimeout(1500); // you may want to adjust the timeout
@ -21,22 +21,23 @@ async function checkPort(port, domain) {
socket.once('timeout', () => { socket.once('timeout', () => {
socket.destroy(); socket.destroy();
reject(new Error(`Timeout at port: ${port}`));
}); });
socket.once('error', (e) => { socket.once('error', (e) => {
socket.destroy(); socket.destroy();
reject(e);
}); });
socket.connect(port, domain); socket.connect(port, domain);
}); });
} }
exports.handler = async (event, context) => { exports.handler = async (event, context) => {
const domain = event.queryStringParameters.url; const domain = event.queryStringParameters.url;
if (!domain) { if (!domain) {
return { return errorResponse('Missing domain parameter.');
statusCode: 400,
body: JSON.stringify({ message: "Missing 'domain' parameter" }),
};
} }
const delay = ms => new Promise(res => setTimeout(res, ms)); 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 { return {
statusCode: 200, 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 }),
};
};

View file

@ -1,14 +1,10 @@
const https = require('https'); const https = require('https');
const urlModule = require('url');
exports.handler = async function(event, context) { exports.handler = async function(event, context) {
let { url } = event.queryStringParameters; let { url } = event.queryStringParameters;
if (!url) { if (!url) {
return { return errorResponse('URL query parameter is required.');
statusCode: 400,
body: JSON.stringify({ error: 'url query parameter is required' }),
};
} }
// Extract hostname from URL // Extract hostname from URL
@ -40,10 +36,10 @@ exports.handler = async function(event, context) {
res.on('end', () => { res.on('end', () => {
resolve(JSON.parse(data)); resolve(JSON.parse(data));
}); });
});
req.on('error', error => { res.on('error', error => {
reject(error); reject(error);
});
}); });
req.end(); req.end();
@ -55,10 +51,7 @@ exports.handler = async function(event, context) {
records[type] = { isFound: false, answer: null, response: dnsResponse}; records[type] = { isFound: false, answer: null, response: dnsResponse};
} }
} catch (error) { } catch (error) {
return { return errorResponse(`Error fetching ${type} record: ${error.message}`);
statusCode: 500,
body: JSON.stringify({ error: `Error fetching ${type} record: ${error.message}` }),
};
} }
} }
@ -67,3 +60,10 @@ exports.handler = async function(event, context) {
body: JSON.stringify(records), body: JSON.stringify(records),
}; };
}; };
const errorResponse = (message, statusCode = 444) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};

View file

@ -2,21 +2,32 @@ const dns = require('dns');
/* Lambda function to fetch the IP address of a given URL */ /* Lambda function to fetch the IP address of a given URL */
exports.handler = function (event, context, callback) { 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) const address = decodeURIComponent(addressParam)
.replaceAll('https://', '') .replaceAll('https://', '')
.replaceAll('http://', ''); .replaceAll('http://', '');
dns.lookup(address, (err, ip, family) => { dns.lookup(address, (err, ip, family) => {
if (err) { if (err) {
callback(null, { callback(null, errorResponse(err.message));
statusCode: 405,
body: JSON.stringify(err),
})
} else { } else {
callback(err, { callback(null, {
statusCode: 200, 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 }),
};
};

View file

@ -23,9 +23,13 @@ exports.handler = async (event) => {
}), }),
}; };
} catch (error) { } catch (error) {
return { return errorResponse(`Error: ${error.message}`);
statusCode: 500,
body: `Error: ${error.message}`,
};
} }
}; };
const errorResponse = (message, statusCode = 444) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};

View file

@ -6,7 +6,7 @@ exports.handler = async (event, context) => {
if (!url) { if (!url) {
return { return {
statusCode: 400, 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) { } catch (error) {
return { return {
statusCode: 500, statusCode: 500,
body: JSON.stringify({ message: `Error: ${error.message}` }), body: JSON.stringify({ error: `Error: ${error.message}` }),
}; };
} }
}; };

View file

@ -21,7 +21,7 @@ exports.handler = async function(event, context) {
} catch (error) { } catch (error) {
return { return {
statusCode: 500, statusCode: 500,
body: JSON.stringify({ message: error.message }), body: JSON.stringify({ error: error.message }),
}; };
} }
}; };

View file

@ -6,7 +6,7 @@ exports.handler = async function(event, context) {
if (!url) { if (!url) {
return { return {
statusCode: 400, 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) { } catch (error) {
return { return {
statusCode: 500, statusCode: 500,
body: JSON.stringify({ message: error.message }), body: JSON.stringify({ error: error.message }),
}; };
} }
}; };

View file

@ -24,7 +24,7 @@ exports.handler = async (event) => {
console.error('Error:', error); console.error('Error:', error);
return { return {
statusCode: 500, statusCode: 500,
body: JSON.stringify({ message: error.message }), body: JSON.stringify({ error: error.message }),
}; };
} }
}; };

View file

@ -6,11 +6,20 @@ exports.handler = async function(event, context) {
if (!siteURL) { if (!siteURL) {
return { return {
statusCode: 400, 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`; const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`;
try { try {
@ -25,13 +34,13 @@ exports.handler = async function(event, context) {
} else { } else {
return { return {
statusCode: response.status, statusCode: response.status,
body: `Failed to fetch robots.txt`, body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }),
}; };
} }
} catch (error) { } catch (error) {
return { return {
statusCode: 500, statusCode: 500,
body: `Error fetching robots.txt: ${error.toString()}`, body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }),
}; };
} }
}; };

View file

@ -7,13 +7,13 @@ exports.handler = async function(event, context) {
if (!url) { if (!url) {
return { return {
statusCode: 400, 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 dnsLookupTime;
let responseCode; let responseCode;
let startTime = performance.now(); let startTime;
const obs = new PerformanceObserver((items) => { const obs = new PerformanceObserver((items) => {
dnsLookupTime = items.getEntries()[0].duration; dnsLookupTime = items.getEntries()[0].duration;
@ -24,28 +24,46 @@ exports.handler = async function(event, context) {
performance.mark('A'); performance.mark('A');
return new Promise((resolve) => { try {
https.get(url, (res) => { startTime = performance.now();
let data = ''; const response = await new Promise((resolve, reject) => {
responseCode = res.statusCode; const req = https.get(url, res => {
res.on('data', (chunk) => { let data = '';
data += chunk; responseCode = res.statusCode;
}); res.on('data', chunk => {
res.on('end', () => { data += chunk;
performance.mark('B'); });
performance.measure('A to B', 'A', 'B'); res.on('end', () => {
let responseTime = performance.now() - startTime; resolve(res);
obs.disconnect();
resolve({
statusCode: 200,
body: JSON.stringify({ isUp: true, dnsLookupTime, responseTime, responseCode }),
}); });
}); });
}).on('error', (e) => {
resolve({ req.on('error', reject);
statusCode: 500, req.end();
body: JSON.stringify({ isUp: false, error: e.message }),
});
}); });
});
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}` }),
};
}
}; };

View file

@ -4,7 +4,7 @@ exports.handler = async function (event, context) {
const { url } = event.queryStringParameters; const { url } = event.queryStringParameters;
const apiKey = process.env.BUILT_WITH_API_KEY; const apiKey = process.env.BUILT_WITH_API_KEY;
const errorResponse = (message, statusCode = 444) => { const errorResponse = (message, statusCode = 500) => {
return { return {
statusCode: statusCode, statusCode: statusCode,
body: JSON.stringify({ error: message }), body: JSON.stringify({ error: message }),
@ -12,33 +12,45 @@ exports.handler = async function (event, context) {
}; };
if (!url) { 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)}`; const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`;
return new Promise((resolve, reject) => { try {
https.get(apiUrl, (res) => { const response = await new Promise((resolve, reject) => {
let data = ''; const req = https.get(apiUrl, res => {
let data = '';
// A chunk of data has been received. res.on('data', chunk => {
res.on('data', (chunk) => { 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. req.on('error', error => {
res.on('end', () => { reject(error);
if(res.statusCode !== 200){
resolve(errorResponse(`Request failed with status code: ${res.statusCode}`));
} else {
resolve({
statusCode: 200,
body: data,
});
}
}); });
}).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}`);
}
}; };

View file

@ -3,7 +3,7 @@ const https = require('https');
exports.handler = async function (event, context) { exports.handler = async function (event, context) {
const { url } = event.queryStringParameters; const { url } = event.queryStringParameters;
const errorResponse = (message, statusCode = 444) => { const errorResponse = (message, statusCode = 500) => {
return { return {
statusCode: statusCode, statusCode: statusCode,
body: JSON.stringify({ error: message }), body: JSON.stringify({ error: message }),
@ -11,34 +11,40 @@ exports.handler = async function (event, context) {
}; };
if (!url) { if (!url) {
return errorResponse('url query parameter is required'); return errorResponse('URL query parameter is required', 400);
} }
return new Promise((resolve, reject) => { try {
const req = https.request(url, res => { const response = await new Promise((resolve, reject) => {
const req = https.request(url, res => {
// Check if the SSL handshake was authorized // Check if the SSL handshake was authorized
if (!res.socket.authorized) { if (!res.socket.authorized) {
resolve(errorResponse(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`)); 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."));
} else { } else {
// omit the raw and issuerCertificate fields let cert = res.socket.getPeerCertificate(true);
const { raw, issuerCertificate, ...certWithoutRaw } = cert; if (!cert || Object.keys(cert).length === 0) {
resolve({ resolve(errorResponse("No certificate presented by the server."));
statusCode: 200, } else {
body: JSON.stringify(certWithoutRaw), // 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) => { return response;
resolve(errorResponse(`Error fetching site certificate: ${error.message}`, 500)); } catch (error) {
}); return errorResponse(`Unexpected error occurred: ${error.message}`);
}
req.end();
});
}; };

View file

@ -3,19 +3,10 @@ const url = require('url');
exports.handler = async function(event, context) { exports.handler = async function(event, context) {
const urlString = event.queryStringParameters.url; const urlString = event.queryStringParameters.url;
const startTime = Date.now();
try { try {
if (!urlString) { if (!urlString) {
throw new Error('URL parameter is missing!');
return {
statusCode: 400,
body: JSON.stringify({
error: 'URL parameter is missing!'
}),
};
} }
// Parse the URL and get the hostname // Parse the URL and get the hostname
@ -23,66 +14,42 @@ exports.handler = async function(event, context) {
const host = urlObject.hostname; const host = urlObject.hostname;
if (!host) { if (!host) {
throw new Error('Invalid URL provided');
return {
statusCode: 400,
body: JSON.stringify({
error: 'Invalid URL provided'
}),
};
} }
// Traceroute with callback // Traceroute with callback
return await new Promise((resolve, reject) => { const result = await new Promise((resolve, reject) => {
traceroute.trace(host, (err, hops) => { traceroute.trace(host, (err, hops) => {
if (err) { if (err || !hops) {
reject(err); reject(err || new Error('No hops found'));
} else if (hops) { } else {
resolve(hops); resolve(hops);
} }
}); });
// Check if remaining time is less than 8.8 seconds, then reject promise // Check if remaining time is less than 8.8 seconds, then reject promise
if (context.getRemainingTimeInMillis() < 8800) { if (context.getRemainingTimeInMillis() < 8800) {
reject(new Error('Lambda is about to timeout')); 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) { } catch (err) {
if (err.code === 'ENOENT') { const message = err.code === 'ENOENT'
? 'Traceroute command is not installed on the host.'
: err.message;
return { return {
statusCode: 400, statusCode: 500,
body: JSON.stringify({ body: JSON.stringify({
error: 'Traceroute command is not installed on the host.' error: message,
}), }),
}; };
} else {
return {
statusCode: 500,
body: JSON.stringify({
error: err.message
}),
};
}
} }
}; };