Skip to content

Commit

Permalink
#54: Added rate limiting and general non-fatal HTTP error handling su…
Browse files Browse the repository at this point in the history
…pport
  • Loading branch information
darnjo committed Oct 5, 2023
1 parent d63c478 commit 0cbf39f
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 15 deletions.
7 changes: 5 additions & 2 deletions common.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,11 @@ const createResoScriptClientCredentialsConfig = ({ serviceRootUri, clientCredent
' </ClientSettings>' +
'</OutputScript>';

const sleep = async (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));

module.exports = {
CURRENT_DATA_DICTIONARY_VERSION,
CURRENT_WEB_API_CORE_VERSION,
endorsements,
availableVersions,
isValidEndorsement,
Expand All @@ -290,6 +294,5 @@ module.exports = {
archiveEndorsement,
getCurrentVersion,
getPreviousVersion,
CURRENT_DATA_DICTIONARY_VERSION,
CURRENT_WEB_API_CORE_VERSION
sleep
};
6 changes: 2 additions & 4 deletions lib/misc/data-access/cert-api-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const axios = require('axios');
require('dotenv').config();
const { CERTIFICATION_API_KEY, ORGS_DATA_URL, SYSTEMS_DATA_URL } = process.env;
const { sleep } = require('../../../common');

const API_DEBOUNCE_SECONDS = 0.1;

Expand Down Expand Up @@ -72,8 +73,6 @@ const postDataAvailabilityResultsToApi = async ({ url, reportId, dataAvailabilit
}
};

const sleep = async ms => new Promise(resolve => setTimeout(resolve, ms));

const processDataDictionaryResults = async ({
url,
providerUoi,
Expand Down Expand Up @@ -161,6 +160,5 @@ module.exports = {
processDataDictionaryResults,
getOrgsMap,
getOrgSystemsMap,
findDataDictionaryReport,
sleep
findDataDictionaryReport
};
2 changes: 1 addition & 1 deletion lib/orgs-exporter/data-access.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const https = require('https');

const { sleep } = require('./utils');
const { sleep } = require('../../common');

const REQUEST_TIMEOUT_MS = (process.env.REQUEST_TIMEOUT_S || 30) * 1000;
const { API_KEY, SERVER_URL, ENDORSEMENTS_PATH, UOI_GOOGLE_SHEET_URL } = process.env;
Expand Down
5 changes: 1 addition & 4 deletions lib/orgs-exporter/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ const writeDataToS3 = async ({
await s3.putObject(params).promise();
};

const sleep = async (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));

module.exports = {
writeDataToS3,
sleep
writeDataToS3
};
8 changes: 6 additions & 2 deletions lib/replication/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ const replicate = async ({
pathToMetadataReportJson = '',
filter,
top,
orderby
orderby,
rateLimitedWaitTimeMinutes = 60
}) => {
if (!Object.values(REPLICATION_STRATEGIES).includes(strategy)) {
throw new Error(`Unknown strategy: '${strategy}'!`);
Expand All @@ -57,9 +58,11 @@ const replicate = async ({
shouldSaveResults = !!outputPath,
resourceAvailabilityMap = {};

// Each resource and expansion will have its separate set of requests
for await (const request of requests) {
const { requestUri: initialRequestUri } = request;

// each item queried has its own set of requests
try {
for await (const {
hasResults = false,
Expand All @@ -76,7 +79,8 @@ const replicate = async ({
try {
//handle errors
if (hasError) {
handleError(error);
// some errors, like HTTP 429, might be able to be handled
await handleError({ error, rateLimitedWaitTimeMinutes });
}

//process results
Expand Down
21 changes: 19 additions & 2 deletions lib/replication/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { readFile, writeFile } = require('fs/promises');
const { join } = require('path');
const humanizeDuration = require('humanize-duration');
const { sleep } = require('../../common');

const REPLICATION_DIRECTORY_NAME = 'reso-replication-output';

Expand All @@ -11,6 +12,12 @@ const ERROR_TYPES = {
GENERAL: 'general'
};

const ERROR_CODES = {
HTTP: {
RATE_LIMITED: 429
}
};

const NOT_OK = 1;

const DEFAULT_DD_VERSION = '1.7',
Expand Down Expand Up @@ -478,11 +485,21 @@ const calculateMedian = (numbers = []) => {
* Handles error objects
* @param {Object} error the error to process
*/
const handleError = error => {
const handleError = async ({ error = {}, rateLimitedWaitTimeMinutes = 60 }) => {
const { statusCode, message, errorType, error: errorData } = error;
if (errorType === ERROR_TYPES.HTTP) {
// some HTTP errors can potentially be handled
console.error(`HTTP request error! Status code: ${statusCode}, message: '${message}'`);

if (parseInt(statusCode) === ERROR_CODES.HTTP.RATE_LIMITED) {
console.warn(`${Date.now().toISOString()} - HTTP ${ERROR_CODES.HTTP.RATE_LIMITED} error!`);
console.warn(`\t -> Waiting ${rateLimitedWaitTimeMinutes}m before making the next request...`);
await sleep(rateLimitedWaitTimeMinutes * 1000);
}

return { errorType, statusCode, message };
} else {
// throw an error for all other errors
let errorString = null;
try {
errorString = JSON.stringify(JSON.parse(errorData));
Expand Down Expand Up @@ -519,7 +536,7 @@ const createRequestsFromMetadataReport = ({ serviceRootUri, metadataReportJson =

return requests.map(({ resourceName, fieldName, isExpansion, typeName }) => {
const expansions = isExpansion ? [fieldName] : undefined;
const expandedFields = isExpansion ? [ { fieldName, typeName } ] : undefined;
const expandedFields = isExpansion ? [{ fieldName, typeName }] : undefined;
return {
requestUri: createODataRequestUri({ serviceRootUri, resourceName, expansions, filter, top, orderby }),
resourceName,
Expand Down

0 comments on commit 0cbf39f

Please sign in to comment.