refactor(utils): Extract utilities from server.js
- Create src/utils/http.js - fetchT and HTTP helpers - Create src/utils/logging.js - Structured logging and error logging - Create src/utils/responses.js - Standard API responses - Create src/utils/async-handler.js - Async wrapper with error handling - Create src/utils/index.js - Consolidated exports Removes scattered helper functions from server.js
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* HTTP utilities - Fetch helpers and HTTP operations
|
||||
*/
|
||||
const http = require('http');
|
||||
const { TIMEOUTS } = require('../../constants');
|
||||
|
||||
/**
|
||||
* Fetch with automatic timeout
|
||||
* Drop-in replacement for fetch() with AbortSignal timeout
|
||||
*/
|
||||
function fetchT(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
|
||||
// Caddy admin API rejects Node.js undici fetch - use raw http.request
|
||||
if (url.includes(':2019')) {
|
||||
return _httpFetch(url, opts, timeoutMs);
|
||||
}
|
||||
|
||||
if (!opts.signal) {
|
||||
opts = { ...opts, signal: AbortSignal.timeout(timeoutMs) };
|
||||
}
|
||||
delete opts.timeout;
|
||||
return fetch(url, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw http.request wrapper for Caddy admin API
|
||||
*/
|
||||
function _httpFetch(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parsed = new URL(url);
|
||||
const options = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port || 2019,
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: (opts.method || 'GET').toUpperCase(),
|
||||
headers: { ...opts.headers },
|
||||
timeout: timeoutMs,
|
||||
};
|
||||
|
||||
if (opts.body) {
|
||||
options.headers['Content-Length'] = Buffer.byteLength(opts.body);
|
||||
}
|
||||
|
||||
const MAX_RESPONSE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
let size = 0;
|
||||
|
||||
res.on('data', chunk => {
|
||||
size += chunk.length;
|
||||
if (size > MAX_RESPONSE_SIZE) {
|
||||
res.destroy();
|
||||
reject(new Error(`Response from ${url} exceeded ${MAX_RESPONSE_SIZE} bytes`));
|
||||
return;
|
||||
}
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
ok: res.statusCode >= 200 && res.statusCode < 300,
|
||||
status: res.statusCode,
|
||||
statusText: res.statusMessage,
|
||||
json: () => Promise.resolve(JSON.parse(data)),
|
||||
text: () => Promise.resolve(data),
|
||||
headers: { get: (k) => res.headers[k.toLowerCase()] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error(`Request to ${url} timed out after ${timeoutMs}ms`));
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (opts.body) req.write(opts.body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { fetchT };
|
||||
Reference in New Issue
Block a user