Simplify proxy.js: Moderate cleanup for better maintainability
Section 1: Authentication (135 → 62 lines, 54% reduction) - Inlined createServiceAccountJWT into initializeServiceAccount - Inlined getAccessToken axios call (simple wrapper removed) - Removed clearAuthCache (3-line test helper) - Added constants: TOKEN_EXPIRY_MS, TOKEN_BUFFER_MS (no magic numbers) - Simplified error handling (removed redundant try-catch) - Condensed JWT signing to single jwt.sign() call - Optional chaining: settings?.serviceAccount?.client_email Section 2: Request Queue (85 → 39 lines, 54% reduction) - Removed verbose debug logging (enqueue, _processNext) - Simplified enqueue: inline if statement - Simplified _processNext: direct await in try block - Removed redundant JSDoc comments - Kept essential structure and error handling Section 3: Drive API Client (109 → 52 lines, 52% reduction) - Removed debug logging (query start, each page) - Removed verbose error logging (already bubbles with stack) - Removed unnecessary try-catch (let errors bubble naturally) - Moved accessToken outside loop for clarity - Removed DocumentCountExceededError re-throw check (unnecessary) - Inline params.append when pageToken exists Section 4: Request Handling (124 → 88 lines, 29% reduction) - Removed redundant JSDoc @param tags - Simplified handleSitemapRequest comments - Removed 'successfully' from log messages (redundant) - Removed duplicate comment about empty body - Inline async wrapper in requestQueue.enqueue - Condensed route not found logic Benefits: ✅ 40% fewer lines to read and maintain ✅ Named constants instead of magic numbers ✅ Cleaner error handling (natural bubbling) ✅ Less noise from debug logging (info/error only) ✅ Same functionality, clearer code ✅ Easier to understand core business logic Testing: ✓ Syntax validated ✓ Server starts successfully ✓ Request handling works ✓ Route parsing functional ✓ All core logic preserved Philosophy: - Remove verbosity that obscures intent - Inline simple wrappers (1-2 line functions) - Let errors bubble naturally (fewer try-catch) - Use constants for magic numbers - Keep essential structure and logic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -38,147 +38,78 @@
|
|||||||
// Section 1: Authentication (Service Account JWT)
|
// Section 1: Authentication (Service Account JWT)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
const TOKEN_EXPIRY_MS = 3600000; // 1 hour
|
||||||
* Cached access token for Drive API
|
const TOKEN_BUFFER_MS = 300000; // 5 minute buffer
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
let accessTokenCache = null;
|
let accessTokenCache = null;
|
||||||
let tokenExpiryTime = null;
|
let tokenExpiryTime = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create JWT token for Google Service Account authentication
|
* Create JWT and exchange for access token
|
||||||
* Uses RS256 algorithm with service account private key
|
|
||||||
*
|
|
||||||
* @param {Object} credentials - Service account credentials
|
|
||||||
* @returns {string} Signed JWT token
|
|
||||||
*/
|
*/
|
||||||
function createServiceAccountJWT(credentials, scopes) {
|
async function initializeServiceAccount() {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const settings = google_drive_settings;
|
||||||
const expiry = now + 3600; // 1 hour
|
|
||||||
|
|
||||||
const payload = {
|
if (!settings?.serviceAccount?.client_email || !settings?.serviceAccount?.private_key) {
|
||||||
iss: credentials.client_email,
|
throw new Error("Invalid service account credentials in google_drive_settings");
|
||||||
scope: scopes.join(" "),
|
|
||||||
aud: "https://oauth2.googleapis.com/token",
|
|
||||||
exp: expiry,
|
|
||||||
iat: now,
|
|
||||||
};
|
|
||||||
|
|
||||||
return jwt.sign(payload, credentials.private_key, { algorithm: "RS256" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const scopes = settings.scopes || ["https://www.googleapis.com/auth/drive.readonly"];
|
||||||
* Exchange JWT for access token
|
const now = Math.floor(Date.now() / 1000);
|
||||||
*
|
|
||||||
* @param {string} jwtToken - Signed JWT token
|
// Create and sign JWT
|
||||||
* @returns {Promise<string>} Access token
|
const jwtToken = jwt.sign(
|
||||||
*/
|
{
|
||||||
async function getAccessToken(jwtToken) {
|
iss: settings.serviceAccount.client_email,
|
||||||
|
scope: scopes.join(" "),
|
||||||
|
aud: "https://oauth2.googleapis.com/token",
|
||||||
|
exp: now + 3600,
|
||||||
|
iat: now,
|
||||||
|
},
|
||||||
|
settings.serviceAccount.private_key,
|
||||||
|
{ algorithm: "RS256" }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Exchange for access token
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
"https://oauth2.googleapis.com/token",
|
"https://oauth2.googleapis.com/token",
|
||||||
{
|
{
|
||||||
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||||
assertion: jwtToken,
|
assertion: jwtToken,
|
||||||
},
|
},
|
||||||
{
|
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.info("Service account authenticated", {
|
||||||
|
email: settings.serviceAccount.client_email,
|
||||||
|
});
|
||||||
|
|
||||||
return response.data.access_token;
|
return response.data.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Google OAuth Service Account client
|
* Get or create cached access token (with 5min buffer before expiry)
|
||||||
* Uses credentials from global object (loaded by server.js from global/ directory)
|
|
||||||
*
|
|
||||||
* @returns {Promise<string>} Access token for Drive API
|
|
||||||
* @throws {Error} If credentials are invalid or not loaded
|
|
||||||
*/
|
|
||||||
async function initializeServiceAccount() {
|
|
||||||
try {
|
|
||||||
// Load settings from consolidated global object
|
|
||||||
const settings = google_drive_settings;
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
throw new Error(
|
|
||||||
'Google Drive settings not found in `google_drive_settings`. Ensure server.js loaded global/google_drive_settings.json',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate service account structure
|
|
||||||
if (
|
|
||||||
!settings.serviceAccount ||
|
|
||||||
!settings.serviceAccount.client_email ||
|
|
||||||
!settings.serviceAccount.private_key
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
"Invalid service account key format - missing serviceAccount.client_email or serviceAccount.private_key",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default scopes if not specified
|
|
||||||
const scopes = settings.scopes || [
|
|
||||||
"https://www.googleapis.com/auth/drive.readonly",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create JWT token
|
|
||||||
const jwtToken = createServiceAccountJWT(settings.serviceAccount, scopes);
|
|
||||||
|
|
||||||
// Exchange JWT for access token
|
|
||||||
const accessToken = await getAccessToken(jwtToken);
|
|
||||||
|
|
||||||
console.info("Service account authenticated successfully", {
|
|
||||||
email: settings.serviceAccount.client_email,
|
|
||||||
});
|
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Service account authentication failed", {
|
|
||||||
error: error.message,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or create cached access token
|
|
||||||
* Singleton pattern to avoid multiple authentications
|
|
||||||
*
|
|
||||||
* @returns {Promise<string>} Access token for Drive API
|
|
||||||
*/
|
*/
|
||||||
async function getAccessTokenCached() {
|
async function getAccessTokenCached() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Return cached token if still valid (with 5 minute buffer)
|
if (accessTokenCache && tokenExpiryTime && now < tokenExpiryTime - TOKEN_BUFFER_MS) {
|
||||||
if (accessTokenCache && tokenExpiryTime && now < tokenExpiryTime - 300000) {
|
|
||||||
return accessTokenCache;
|
return accessTokenCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get new token
|
|
||||||
accessTokenCache = await initializeServiceAccount();
|
accessTokenCache = await initializeServiceAccount();
|
||||||
tokenExpiryTime = now + 3600000; // 1 hour from now
|
tokenExpiryTime = now + TOKEN_EXPIRY_MS;
|
||||||
|
|
||||||
return accessTokenCache;
|
return accessTokenCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear cached access token (for testing)
|
|
||||||
*/
|
|
||||||
function clearAuthCache() {
|
|
||||||
accessTokenCache = null;
|
|
||||||
tokenExpiryTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Section 2: Request Queue (FIFO)
|
// Section 2: Request Queue (FIFO)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIFO Queue for request processing
|
* FIFO Queue for sequential request processing
|
||||||
*
|
* Prevents concurrent Drive API operations per specification
|
||||||
* Ensures sequential processing - only one request executes at a time.
|
|
||||||
* Prevents concurrent Drive API operations per specification clarification #7.
|
|
||||||
*/
|
*/
|
||||||
class RequestQueue {
|
class RequestQueue {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -186,69 +117,35 @@ class RequestQueue {
|
|||||||
this.processing = false;
|
this.processing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add request to queue and start processing
|
|
||||||
*
|
|
||||||
* @param {Function} handler - Async function to execute
|
|
||||||
* @returns {Promise} Resolves when handler completes
|
|
||||||
*/
|
|
||||||
async enqueue(handler) {
|
async enqueue(handler) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.queue.push({ handler, resolve, reject });
|
this.queue.push({ handler, resolve, reject });
|
||||||
|
if (!this.processing) this._processNext();
|
||||||
console.debug("Request enqueued", {
|
|
||||||
queueLength: this.queue.length,
|
|
||||||
processing: this.processing,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start processing if not already processing
|
|
||||||
if (!this.processing) {
|
|
||||||
this._processNext();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Process next request in queue
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _processNext() {
|
async _processNext() {
|
||||||
if (this.queue.length === 0) {
|
if (this.queue.length === 0) {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
console.debug("Queue empty, stopping processing");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
const { handler, resolve, reject } = this.queue.shift();
|
const { handler, resolve, reject } = this.queue.shift();
|
||||||
|
|
||||||
console.debug("Processing next request", {
|
|
||||||
remainingInQueue: this.queue.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await handler();
|
resolve(await handler());
|
||||||
resolve(result);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
} finally {
|
} finally {
|
||||||
// Process next request
|
|
||||||
this._processNext();
|
this._processNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current queue length
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get length() {
|
get length() {
|
||||||
return this.queue.length;
|
return this.queue.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if queue is processing
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get isProcessing() {
|
get isProcessing() {
|
||||||
return this.processing;
|
return this.processing;
|
||||||
}
|
}
|
||||||
@@ -263,18 +160,7 @@ const requestQueue = new RequestQueue();
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Query documents from Google Drive with pagination
|
* Query documents from Google Drive with pagination
|
||||||
*
|
* Throws DocumentCountExceededError if count > maxDocuments
|
||||||
* Enforces 50k document limit per sitemap protocol specification.
|
|
||||||
* If count exceeds limit, throws DocumentCountExceededError.
|
|
||||||
*
|
|
||||||
* @param {Object} options - Query options
|
|
||||||
* @param {string} options.query - Drive API query filter
|
|
||||||
* @param {string} options.fields - Fields to retrieve
|
|
||||||
* @param {number} options.pageSize - Page size for pagination
|
|
||||||
* @param {number} options.maxDocuments - Maximum documents allowed (default: 50000)
|
|
||||||
* @returns {Promise<Array>} Array of document objects
|
|
||||||
* @throws {DocumentCountExceededError} If document count > maxDocuments
|
|
||||||
* @throws {Error} If Drive API request fails
|
|
||||||
*/
|
*/
|
||||||
async function queryDocuments(options = {}) {
|
async function queryDocuments(options = {}) {
|
||||||
const {
|
const {
|
||||||
@@ -286,31 +172,18 @@ async function queryDocuments(options = {}) {
|
|||||||
|
|
||||||
const allFiles = [];
|
const allFiles = [];
|
||||||
let pageToken = null;
|
let pageToken = null;
|
||||||
|
|
||||||
console.debug("Starting Drive API query", {
|
|
||||||
query,
|
|
||||||
pageSize,
|
|
||||||
maxDocuments,
|
|
||||||
});
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
|
||||||
const accessToken = await getAccessTokenCached();
|
const accessToken = await getAccessTokenCached();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Build query parameters
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
q: query,
|
q: query,
|
||||||
pageSize: pageSize.toString(),
|
pageSize: pageSize.toString(),
|
||||||
fields,
|
fields,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pageToken) {
|
if (pageToken) params.append("pageToken", pageToken);
|
||||||
params.append("pageToken", pageToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make direct HTTP call to Drive API
|
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`https://www.googleapis.com/drive/v3/files?${params.toString()}`,
|
`https://www.googleapis.com/drive/v3/files?${params.toString()}`,
|
||||||
{
|
{
|
||||||
@@ -318,53 +191,26 @@ async function queryDocuments(options = {}) {
|
|||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const files = response.data.files || [];
|
const files = response.data.files || [];
|
||||||
allFiles.push(...files);
|
allFiles.push(...files);
|
||||||
|
|
||||||
console.debug("Drive API page retrieved", {
|
|
||||||
pageFiles: files.length,
|
|
||||||
totalFiles: allFiles.length,
|
|
||||||
hasMore: !!response.data.nextPageToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if we've exceeded the limit BEFORE fetching more
|
// Check if we've exceeded the limit BEFORE fetching more
|
||||||
if (allFiles.length > maxDocuments) {
|
if (allFiles.length > maxDocuments) {
|
||||||
console.error("Document count exceeds limit", {
|
|
||||||
count: allFiles.length,
|
|
||||||
limit: maxDocuments,
|
|
||||||
});
|
|
||||||
throw new helpers.DocumentCountExceededError(allFiles.length, maxDocuments);
|
throw new helpers.DocumentCountExceededError(allFiles.length, maxDocuments);
|
||||||
}
|
}
|
||||||
|
|
||||||
pageToken = response.data.nextPageToken;
|
pageToken = response.data.nextPageToken;
|
||||||
} while (pageToken);
|
} while (pageToken);
|
||||||
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
|
|
||||||
console.info("Drive API query completed", {
|
console.info("Drive API query completed", {
|
||||||
documentCount: allFiles.length,
|
documentCount: allFiles.length,
|
||||||
duration,
|
duration: Date.now() - startTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
return allFiles;
|
return allFiles;
|
||||||
} catch (error) {
|
|
||||||
// Re-throw DocumentCountExceededError as-is
|
|
||||||
if (error instanceof helpers.DocumentCountExceededError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log and re-throw other errors
|
|
||||||
console.error("Drive API query failed", {
|
|
||||||
error: error.message,
|
|
||||||
code: error.code,
|
|
||||||
statusCode: error.response?.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -372,70 +218,42 @@ async function queryDocuments(options = {}) {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle sitemap generation request
|
* Handle sitemap generation request (wrapped in FIFO queue)
|
||||||
* Wrapped in FIFO queue to ensure sequential processing.
|
|
||||||
*
|
|
||||||
* @param {Object} res - HTTP response object
|
|
||||||
* @param {string} requestId - Request ID for tracing
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
*/
|
||||||
async function handleSitemapRequest(res, requestId) {
|
async function handleSitemapRequest(res, requestId) {
|
||||||
try {
|
try {
|
||||||
// Get configuration from consolidated global settings
|
|
||||||
const settings = google_drive_settings || {};
|
const settings = google_drive_settings || {};
|
||||||
const maxUrls = settings.sitemap?.maxUrls || 50000;
|
const maxUrls = settings.sitemap?.maxUrls || 50000;
|
||||||
const query = settings.driveQuery || "trashed = false";
|
const query = settings.driveQuery || "trashed = false";
|
||||||
|
|
||||||
// Query documents from Drive API
|
const documents = await queryDocuments({ query, maxDocuments: maxUrls });
|
||||||
// This will throw DocumentCountExceededError if exceeds maxUrls limit
|
|
||||||
const documents = await queryDocuments({
|
|
||||||
query: query,
|
|
||||||
maxDocuments: maxUrls,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate sitemap XML with RESTful URLs
|
|
||||||
const xml = helpers.generateSitemap(documents, settings.proxyScriptEndPoint);
|
const xml = helpers.generateSitemap(documents, settings.proxyScriptEndPoint);
|
||||||
|
|
||||||
// Send successful response
|
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader("Content-Type", "application/xml; charset=utf-8");
|
res.setHeader("Content-Type", "application/xml; charset=utf-8");
|
||||||
res.setHeader("X-Request-Id", requestId);
|
res.setHeader("X-Request-Id", requestId);
|
||||||
res.setHeader("X-Document-Count", documents.length.toString());
|
res.setHeader("X-Document-Count", documents.length.toString());
|
||||||
res.end(xml);
|
res.end(xml);
|
||||||
|
|
||||||
console.info("Sitemap generated successfully", {
|
console.info("Sitemap generated", { requestId, documentCount: documents.length });
|
||||||
requestId,
|
|
||||||
documentCount: documents.length,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Map Drive API error to HTTP status code
|
|
||||||
const errorResponse = helpers.mapDriveErrorToHttp(error);
|
const errorResponse = helpers.mapDriveErrorToHttp(error);
|
||||||
|
|
||||||
res.statusCode = errorResponse.statusCode;
|
res.statusCode = errorResponse.statusCode;
|
||||||
|
|
||||||
// Add Retry-After header for rate limiting (429)
|
|
||||||
if (errorResponse.retryAfter) {
|
if (errorResponse.retryAfter) {
|
||||||
res.setHeader("Retry-After", errorResponse.retryAfter.toString());
|
res.setHeader("Retry-After", errorResponse.retryAfter.toString());
|
||||||
}
|
}
|
||||||
|
res.end(); // Empty body per spec
|
||||||
// Per specification: error responses have NO body
|
|
||||||
res.end();
|
|
||||||
|
|
||||||
console.error("Sitemap generation failed", {
|
console.error("Sitemap generation failed", {
|
||||||
requestId,
|
requestId,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
statusCode: errorResponse.statusCode,
|
statusCode: errorResponse.statusCode,
|
||||||
retryAfter: errorResponse.retryAfter,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle all HTTP requests
|
* Main HTTP request handler
|
||||||
* Main entry point called by server.js
|
|
||||||
*
|
|
||||||
* @param {Object} req - HTTP request object
|
|
||||||
* @param {Object} res - HTTP response object
|
|
||||||
*/
|
*/
|
||||||
(async () => {
|
(async () => {
|
||||||
const requestId = helpers.generateRequestId();
|
const requestId = helpers.generateRequestId();
|
||||||
@@ -448,46 +266,33 @@ async function handleSitemapRequest(res, requestId) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse route
|
|
||||||
const routeResult = helpers.parseRoute(req.method, req.url);
|
const routeResult = helpers.parseRoute(req.method, req.url);
|
||||||
|
|
||||||
if (!routeResult.route) {
|
if (!routeResult.route) {
|
||||||
res.statusCode = routeResult.statusCode;
|
res.statusCode = routeResult.statusCode;
|
||||||
res.end(); // Empty body per spec
|
res.end();
|
||||||
|
console.error("Route not found", { requestId, url: req.url });
|
||||||
console.error("Route not found", {
|
|
||||||
requestId,
|
|
||||||
url: req.url,
|
|
||||||
statusCode: routeResult.statusCode,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle sitemap route with FIFO queue
|
// Handle sitemap route with FIFO queue (sequential processing)
|
||||||
// Per specification: queue concurrent requests, process sequentially
|
|
||||||
if (routeResult.route === "sitemap") {
|
if (routeResult.route === "sitemap") {
|
||||||
await requestQueue.enqueue(async () => {
|
await requestQueue.enqueue(() => handleSitemapRequest(res, requestId));
|
||||||
await handleSitemapRequest(res, requestId);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.end();
|
res.end();
|
||||||
|
|
||||||
console.error("Request handler error", {
|
console.error("Request handler error", {
|
||||||
requestId,
|
requestId,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
|
|
||||||
console.info("Request completed", {
|
console.info("Request completed", {
|
||||||
requestId,
|
requestId,
|
||||||
statusCode: res.statusCode,
|
statusCode: res.statusCode,
|
||||||
duration,
|
duration: Date.now() - startTime,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user