UNPKG

nsyslog

Version:

Modular new generation log agent. Reads, transform, aggregate, correlate and send logs from sources to destinations

246 lines (217 loc) 6.42 kB
const logger = require('../logger'), extend = require('extend'), request = require('request'), Watermark = require('../watermark'), Queue = require('../queue'), jsexpr = require('jsexpr'), {timer,immediate} = require('../util'), TLS = require('../tls'), Input = require('./'), URL = require('url'); const DEFAULTS = { url : "http://localhost", options : {}, tls : { rejectUnauthorized : false } }; /** * HTTPInput class for handling HTTP-based input. * Extends the base Input class. */ class HTTPInput extends Input { /** * Constructor for HTTPInput. * @param {string} id - Unique identifier for the input. * @param {string} type - Type of the input. */ constructor(id, type) { super(id, type); } /** * Returns the mode of the input. * @returns {string} The mode of the input (push or pull). */ get mode() { return this.ival ? Input.MODE.push : Input.MODE.pull; } /** * Configures the HTTPInput with the provided settings. * @param {Object} config - Configuration object. * @param {Function} callback - Callback function to signal completion. */ async configure(config, callback) { // Merge default and provided configurations this.config = config = extend(true, {}, DEFAULTS, config); let url = URL.parse(config.url || 'false'); let options = config.options; let tls = config.tls; // Validate URL protocol switch (url.protocol) { case 'http:': case 'https:': break; default: return callback(new Error(`Unknown protocol ${url.protocol}`)); } // Configure TLS if provided if (tls) this.tls = await TLS.configure(tls, config.$path); // Initialize configuration properties this.url = jsexpr.expr(config.url || 'http://localhost'); this.options = jsexpr.expr(options); this.method = jsexpr.expr(config.method || 'GET'); this.ival = typeof config.interval === 'string' ? jsexpr.eval(config.interval) : (parseInt(config.interval) || null); this.retry = typeof config.retry === 'string' ? jsexpr.eval(config.retry) : (parseInt(config.retry) || null); this.options.agentOptions = tls ? this.tls : {}; this.queue = new Queue(); this.watermark = new Watermark(config.$datadir); this.owm = config.watermark || {}; // Initialize watermark await this.watermark.start(); this.wm = await this.watermark.get(this.id); if (!this.wm.last) { this.wm.last = this.owm; } await this.watermark.save(this.wm); this.reading = null; callback(); } /** * Fetches data from the HTTP endpoint and processes it. * @returns {Promise<void>} Resolves when data is fetched and processed. */ fetch() { if (this.reading) return this.reading; let wm = this.wm; let last = wm.last; let options = this.options(last); let url = this.url(last); let method = this.method(last); options.url = options.url || url || 'http://localhost'; options.method = options.method || method || 'GET'; logger.silly(`${this.id} http query`, url, options); this.reading = new Promise((ok, rej) => { request(options, (error, res, body) => { if (error) return rej(error); else if (res.statusCode != 200) return rej(body); else { let ctype = res.headers['content-type']; if (ctype && ctype.toLowerCase().startsWith('application/json')) { try { body = JSON.parse(body); } catch (ex) { logger.warn(`${this.id}`, ex); } } if (!Array.isArray(body)) body = [body]; body.forEach(b => { wm.last = b; this.queue.push({ headers: res.headers, httpVersion: res.httpVersion, url: this.url.href, statusCode: res.statusCode, originalMessage: b }); }); return ok(); } }); }).then(async (res) => { await this.watermark.save(this.wm); this.reading = false; return res; }).catch((err) => { logger.error(err); this.reading = false; throw err; }); return this.reading; } /** * Starts the HTTPInput and begins fetching data. * @param {Function} callback - Callback function to process fetched data. */ start(callback) { if (this.ival) { var fnloop = async () => { let isError = false; try { await this.fetch(); while (this.queue.size()) { await immediate(); callback(null, await this.queue.pop()); } isError = false; } catch (err) { logger.error(`${this.id} Error fetching data`, err); isError = true; } // Schedule the next HTTP query let wm = this.wm; let last = wm.last; let xprival = isError ? this.retry : this.ival; if (xprival) { let ival = typeof xprival === 'number' ? xprival : xprival(last); logger.silly(`${this.id} next http query in ${ival} ms`); this.timer = setTimeout(() => fnloop(), ival); } else { logger.info(`${this.id} No defined interval for http query / retry`); } }; fnloop(); } else { callback(); } } /** * Retrieves the next item from the queue. * @param {Function} callback - Callback function to process the next item. */ async next(callback) { if (!this.queue.size()) { do { await this.fetch(); if (!this.queue.size()) { await timer(500); } } while (!this.queue.size()); } try { let item = await this.queue.pop(1000); callback(null, item); } catch (err) { callback(err); } } /** * Stops the HTTPInput and performs cleanup. * @param {Function} callback - Callback function to signal completion. */ stop(callback) { if (this.timer) { clearInterval(this.timer); } callback(); } /** * Pauses the HTTPInput by halting data fetching. * @param {Function} callback - Callback function to signal completion. */ pause(callback) { this.stop(callback); } /** * Resumes the HTTPInput by restarting data fetching. * @param {Function} callback - Callback function to signal completion. */ resume(callback) { this.start(callback); } /** * Generates a unique key for the input entry. * @param {Object} entry - Input entry object. * @returns {string} Unique key for the entry. */ key(entry) { return `${entry.input}:${entry.type}@${entry.url}`; } } module.exports = HTTPInput;