#!/usr/bin/env node /** * conductor-mock-server.js * * A minimal HTTP mock server for local Conductor development and validation. * It echoes request bodies through routes that mimic the public HTTPBin API, * so Conductor project examples can run fully offline or from within Docker. * * Usage: * node examples/mock-server/conductor-mock-server.js * * Listens on: * http://localhost:8787 (host access) * http://host.docker.internal:8787 (Docker container access on Mac/Win) * * Routes: * POST /anything — parse JSON body, return { json: , method, url, headers } * GET /get — return { args: , method, url, headers } * * — 404 JSON error */ 'use strict'; const http = require('http'); const url = require('url'); const PORT = 8787; const HOST = '0.0.0.0'; // ── Request-body reader ─────────────────────────────────────────────────────── function readBody(req) { return new Promise((resolve, reject) => { const chunks = []; req.on('data', (chunk) => chunks.push(chunk)); req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); req.on('error', reject); }); } // ── Response helpers ────────────────────────────────────────────────────────── function sendJson(res, status, body) { const payload = JSON.stringify(body, null, 2); res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload), 'Access-Control-Allow-Origin': '*', }); res.end(payload); } // ── Route handlers ──────────────────────────────────────────────────────────── async function handleAnything(req, res) { // Parse body (if any) let parsedJson = null; const rawBody = await readBody(req); if (rawBody.trim()) { try { parsedJson = JSON.parse(rawBody); } catch (e) { sendJson(res, 400, { error: 'invalid_json', message: `Request body is not valid JSON: ${e.message}`, }); return; } } const parsed = url.parse(req.url, true); sendJson(res, 200, { method: req.method, url: req.url, args: parsed.query, headers: req.headers, json: parsedJson, data: rawBody || '', }); } function handleGet(req, res) { const parsed = url.parse(req.url, true); sendJson(res, 200, { method: req.method, url: req.url, args: parsed.query, headers: req.headers, }); } // ── Server ──────────────────────────────────────────────────────────────────── const server = http.createServer(async (req, res) => { // Strip query string for routing const pathname = url.parse(req.url).pathname; // OPTIONS preflight (CORS) if (req.method === 'OPTIONS') { res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*' }); res.end(); return; } try { if (pathname === '/anything') { await handleAnything(req, res); } else if (pathname === '/get' && req.method === 'GET') { handleGet(req, res); } else { sendJson(res, 404, { error: 'not_found', message: `No mock route for ${req.method} ${pathname}. ` + `Available: POST /anything, GET /get`, }); } } catch (err) { sendJson(res, 500, { error: 'internal_error', message: err instanceof Error ? err.message : String(err), }); } }); server.listen(PORT, HOST, () => { console.log(`Conductor mock server listening on http://${HOST}:${PORT}`); console.log(' POST /anything — echoes JSON body as { json: }'); console.log(' GET /get — echoes query parameters as { args: }'); console.log('Press Ctrl+C to stop.'); });