summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Fankhauser hiddenalpha.ch2022-12-01 11:50:06 +0100
committerAndreas Fankhauser hiddenalpha.ch2022-12-01 11:50:06 +0100
commit26365aed4c321b91cf6ea737cd23eff37341d5f1 (patch)
treed5a663866e986b35575d803e7b7ea6c65dbed60f
parent6e7d341e6ba407e2f22ddbf3faec36f0876a8a67 (diff)
parent68e9acb83b6590a3298b3636d1a65d7725eed87f (diff)
downloadUnspecifiedGarbage-26365aed4c321b91cf6ea737cd23eff37341d5f1.zip
UnspecifiedGarbage-26365aed4c321b91cf6ea737cd23eff37341d5f1.tar.gz
Merge 'Add HttpFlood js utils from obsolete project' to master
-rw-r--r--src/main/nodejs/HttpFlood/HttpFlood.js227
-rw-r--r--src/main/nodejs/HttpFlood/HttpNullsink.js130
-rw-r--r--src/main/nodejs/HttpFlood/SetGateleenHook.js127
-rw-r--r--src/main/nodejs/HttpFlood/putshowcaseresources.js133
4 files changed, 617 insertions, 0 deletions
diff --git a/src/main/nodejs/HttpFlood/HttpFlood.js b/src/main/nodejs/HttpFlood/HttpFlood.js
new file mode 100644
index 0000000..e6cd545
--- /dev/null
+++ b/src/main/nodejs/HttpFlood/HttpFlood.js
@@ -0,0 +1,227 @@
+;(function(){ "use strict";
+
+const http = require("http");
+const util = require("util");
+const DevNull = { write:function(){} };
+
+const hrtime = process.hrtime;
+const stdin = process.stdin ;
+const stdlog = process.stderr;
+const stdout = process.stdout;
+const noop = function(){};
+
+
+setTimeout(main);
+
+
+function printHelp(){
+ stdout.write("\n"
+ +" hiddenalphas HTTP pressure test utility.\n"
+ +"\n"
+ +"Options:\n"
+ +"\n"
+ +" --host <ip|hostname>\n"
+ +" Eg: 127.0.0.1\n"
+ +"\n"
+ +" --port <int>\n"
+ +" Eg: 7013\n"
+ +"\n"
+ +" --path <str>\n"
+ +" Eg: /houston/services/nullsink\n"
+ +"\n"
+ +" --max-parallel <int>\n"
+ +" Defaults to 42.\n"
+ +"\n"
+ +" --inter-request-gap <int>\n"
+ +" Milliseconds to wait before starting another request when the previous\n"
+ +" one has ended.\n"
+ +" Defaults to zero.\n"
+ +"\n");
+}
+
+
+function parseArgs( cls_flood, argv ){
+ // Some defaults
+ cls_flood.maxParallel = 42;
+ cls_flood.interRequestGapMs = 0;
+ // Parse args
+ for( var i=2 ; i<argv.length ; ++i ){
+ var arg = argv[i];
+ if( arg == "--help" ){
+ printHelp();
+ return -1;
+ }else if( arg == "--host" ){
+ cls_flood.host = argv[++i];
+ if( !cls_flood.host ){ stdlog.write("Arg --host: Value missing\n"); return -1; }
+ }else if( arg == "--port" ){
+ cls_flood.port = parseInt(argv[++i]);
+ if( isNaN(cls_flood.port) ){ stdlog.write("Arg --port: Cannot parse "+ argv[i]+"\n"); return -1; }
+ }else if( arg == "--path" ){
+ cls_flood.reqPath = argv[++i];
+ if( !cls_flood.reqPath ){ stdlog.write("Arg --path: Value missing\n"); return -1; }
+ }else if( arg == "--max-parallel"){
+ cls_flood.maxParallel = parseInt(argv[++i]);
+ if( isNaN(cls_flood.maxParallel) ){ stdlog.write("Arg --max-parallel: Cannot parse "+ argv[i]+"\n"); return -1; }
+ }else if( arg == "--inter-request-gap"){
+ cls_flood.interRequestGapMs = parseInt(argv[++i]);
+ if( isNaN(cls_flood.interRequestGapMs) ){ stdlog.write("Arg --inter-request-gap: Cannot parse "+argv[i]); return -1; }
+ }else{
+ stdlog.write("Unknown arg: "+ arg +"\n");
+ return -1;
+ }
+ }
+ // A few validity checks.
+ if( cls_flood.host === null ){ stdlog.write("Arg --host missing\n"); return -1; }
+ if( cls_flood.port === null ){ stdlog.write("Arg --port missing\n"); return -1; }
+ if( cls_flood.reqPath === null ){ stdlog.write("Arg --path missing\n"); return -1; }
+ if( ! cls_flood.reqPath.startsWith("/") ){ cls_flood.reqPath = "/"+ cls_flood.reqPath; }
+ return 0;
+}
+
+
+function main() {
+ const cls_flood = Object.seal({
+ host: null, port: null, reqPath: null,
+ //
+ interRequestGapMs: null,
+ isNullsink: null,
+ maxParallel: null,
+ totalReqCount: 0,
+ statsIntervalMs: 3000,
+ httpAgent: null,
+ method: "PUT",
+ printLowMs: 50,
+ printHigMs: Number.MAX_SAFE_INTEGER,
+ });
+
+ if( parseArgs(cls_flood, process.argv) ) return;
+
+ cls_flood.httpAgent = new http.Agent({
+ keepAlive:true, maxSockets:cls_flood.maxParallel, keepAliveMsecs: 42000
+ });
+
+ flood_sendParallelRequests(cls_flood);
+ stdin.on("data", function(){}); // <- Allows entering newlines in console :)
+}
+
+
+function flood_sendParallelRequests( cls_flood ){
+ stdlog.write("Flood '"+ cls_flood.method +" "
+ + cls_flood.host +":"+ cls_flood.port + cls_flood.reqPath +"'\n '- on "
+ + cls_flood.maxParallel +" connections. Print if t > "
+ + cls_flood.printLowMs
+ +"\n" );
+ let floodBegin = hrtime();
+ let prevStatsPrint = floodBegin;
+ let numRequestsTotl = 0;
+ let numRequests = 0;
+ for( let iSt=0 ; iSt < cls_flood.maxParallel ; ++iSt ){
+ fireOne();
+ }
+ function fireOne(){ flood_performHttpRequest(cls_flood, onOneDone); }
+ function onOneDone(){
+ numRequests += 1;
+ let now = hrtime();
+ let msSinceStatsPrint = hrtimeDiffMs(now, prevStatsPrint);
+ if( msSinceStatsPrint > cls_flood.statsIntervalMs ){
+ numRequestsTotl += numRequests;
+ printStats(now, numRequestsTotl, numRequests, msSinceStatsPrint);
+ prevStatsPrint = now;
+ numRequests = 0;
+ }
+ // Use the free slot to fire another request.
+ if( cls_flood.interRequestGapMs > 0 ){
+ setTimeout(fireOne, cls_flood.interRequestGapMs);
+ }else{
+ fireOne();
+ }
+ }
+ function printStats( now, numRequestsTotl, numRequests, msSinceStatsPrint ){
+ const reqPerSecStr = (" "+ Math.floor(numRequests / msSinceStatsPrint * 1000)).substr(-6);
+ const numReqTotalStr = (" "+ numRequestsTotl).substr(-9);
+ const runningSinceSecStr = (" "+ Math.floor(hrtimeDiffMs(now, floodBegin)/1000)).substr(-9);
+ stdlog.write("Stats: "
+ + reqPerSecStr +"/sec, "
+ + numReqTotalStr +" req total, "
+ + runningSinceSecStr +"s running"
+ +"\n");
+ }
+}
+
+
+function flood_performHttpRequest( cls_flood, onResponseEndCb ) {
+ const cls_req = Object.seal({
+ cls_flood: cls_flood,
+ onResponseEndCb: onResponseEndCb || noop,
+ req: null, rsp: null,
+ tsReqBegin: hrtime(), tsRspBegin: null, tsRspEnd: null,
+ });
+ let path = cls_flood.reqPath;
+ let headers = undefined;
+ if( path.indexOf("/{vehicleId}/") != -1 ){
+ let vehicleId = "vehiku00"+ Math.floor(Math.random()*4000);
+ path = path.replace( /\/{vehicleId}\//, "/"+ vehicleId +"/" );
+ headers = { "x-vehicleid": vehicleId };
+ }
+ const req = cls_req.req = http.request({
+ hostname: cls_flood.host, port: cls_flood.port,
+ method: cls_flood.method, path: path,
+ headers: headers,
+ agent: cls_flood.httpAgent,
+ });
+ req.on("error", function( err ){ console.error(err); });
+ req.on("response", function( rsp ){
+ cls_req.rsp = rsp;
+ cls_req.tsRspBegin = hrtime();
+ rsp.on("data", onResponseData.bind(0,cls_req));
+ rsp.on("end", onResponseEnd.bind(0,cls_req));
+ let s = rsp.statusCode;
+ if( s == 200 || s == 404 ){
+ // Fine
+ }else{
+ stdlog.write( "Received a: HTTP "+ rsp.statusCode +" "+ rsp.statusMessage +"\n" );
+ }
+ });
+ if( cls_flood.method != "GET" ){
+ req.write( '{ "info":"Nume es guguseli tscheison zum testle" }' );
+ }
+ req.end();
+}
+
+
+function onResponseData( cls_req, rspBodyChunk ) {
+ const rsp = cls_req.rsp;
+ if( ! rsp.isContinuedBodyChunk ){
+ rsp.isContinuedBodyChunk = true;
+// stdout.write("\n");
+ }
+// stdout.write(rspBodyChunk);
+// stdout.write("\n");
+}
+
+
+function onResponseEnd( cls_req, rsp ){
+ const cls_flood = cls_req.cls_flood;
+ cls_req.tsRspEnd = hrtime();
+ let reqTime = Math.round(hrtimeDiffMs( cls_req.tsRspBegin, cls_req.tsReqBegin ));
+ let rspTime = Math.round(hrtimeDiffMs( cls_req.tsRspEnd, cls_req.tsRspBegin ));
+ let totTime = Math.round(hrtimeDiffMs( cls_req.tsRspEnd, cls_req.tsReqBegin ));
+ if( totTime < cls_flood.printLowMs ){
+ // Do NOT print
+ }else if( totTime > cls_flood.printHigMs ){
+ // Do NOT print
+ }else{
+// stdlog.write(util.format( "%s%d%s%d%s%d\n",
+// "HttpCycle: ", totTime, "ms, TTFB: ", reqTime, ", DownloadMs: ", rspTime ));
+ }
+ cls_req.onResponseEndCb();
+}
+
+
+function hrtimeDiffMs( subtrahend, minuend ){
+ return 1000 * (subtrahend[0] - minuend[0])
+ + 0.000001 * (subtrahend[1] - minuend[1]) ;
+}
+
+
+}()); /*endOfModuleScope*/
diff --git a/src/main/nodejs/HttpFlood/HttpNullsink.js b/src/main/nodejs/HttpFlood/HttpNullsink.js
new file mode 100644
index 0000000..97e1653
--- /dev/null
+++ b/src/main/nodejs/HttpFlood/HttpNullsink.js
@@ -0,0 +1,130 @@
+;(function(){ "use strict";
+
+const http = require("http");
+const util = require("util");
+const DevNull = { write:function(){} };
+
+const hrtime = process.hrtime;
+const stdin = process.stdin ;
+const stdout = process.stdout;
+const stdlog = process.stderr;
+
+
+setTimeout(main);
+
+
+function printHelp(){
+ stdout.write("\n"
+ +" hiddenalphas simple HTTP server just responding '200 OK' for every\n"
+ +" incoming request.\n"
+ +"\n"
+ +" Options:\n"
+ +"\n"
+ +" --host <ip|hostname> (default 127.0.0.1)\n"
+ +" Listen address.\n"
+ +"\n"
+ +" --port <int>\n"
+ +" Listen port.\n"
+ +"\n"
+ +" --statsIntervalMs <int> (default 5000)\n"
+ +" Interval when to print statistics.\n"
+ +"\n");
+}
+
+
+function parseArgs( cls_nullsink, args ){
+ cls_nullsink.host = "127.0.0.1";
+ cls_nullsink.port = null;
+ cls_nullsink.statsIntervalMs = 5000;
+ for( let i=2 ; i<args.length ; ++i ){
+ let arg = args[i];
+ if( arg=="--help" ){
+ printHelp(); return -1;
+ }else if( arg=="--host" ){
+ cls_nullsink.host = args[++i];
+ if( !args[i] ){ stdlog.write("Arg --host expects a value\n"); return -1; }
+ }else if( arg=="--port" ){
+ cls_nullsink.port = parseInt(args[++i]);
+ if( isNaN(cls_nullsink.port) ){ stdlog.write("Arg --port: Cannot parse "+ argv[i]+"\n"); return -1; }
+ }else if( arg=="--statsIntervalMs" ){
+ cls_nullsink.statsIntervalMs = parseInt(args[++i]);
+ if( isNaN(cls_nullsink.statsIntervalMs) ){ stdlog.write("Arg --statsIntervalMs: Cannot parse "+ argv[i]+"\n"); return -1; }
+ }else{
+ stdlog.write("Unknown arg: "+ arg +"\n");
+ }
+ }
+ if( cls_nullsink.host === null ){ stdlog.write("Arg --host missing\n"); return -1; }
+ if( cls_nullsink.port === null ){ stdlog.write("Arg --port missing\n"); return -1; }
+ return 0;
+}
+
+
+function main() {
+ const cls_nullsink = Object.seal({
+ server: null,
+ host: null,
+ port: null,
+ backlog: undefined,
+ statsIntervalMs: null,
+ srvStart: hrtime(),
+ reqTotl: 0,
+ reqCnt: 0,
+ lastStats: hrtime(),
+ });
+ if( parseArgs(cls_nullsink, process.argv) ) return;
+ launchServer(cls_nullsink);
+ logStatsPeriodically(cls_nullsink);
+ // Attaching a listener is enough to allow pressing enter in console.
+ stdin.on("data", function(){});
+}
+
+
+function launchServer( cls_nullsink ){
+ const server = cls_nullsink.server = http.createServer(onRequest.bind(0,cls_nullsink));
+ server.listen(cls_nullsink.port, cls_nullsink.host, cls_nullsink.backlog);
+ stdlog.write("Server listening on "
+ + cls_nullsink.host +":"+ cls_nullsink.port
+ +" (backlog "+ cls_nullsink.backlog +")\n");
+}
+
+
+function onRequest( cls_nullsink, req, rsp ){
+ // Just respond "200 OK" for all requests.
+ cls_nullsink.reqCnt += 1;
+ rsp.writeHead(200);
+ rsp.end();
+}
+
+
+function logStatsPeriodically( cls_nullsink ){
+ scheduleOne();
+ function scheduleOne(){
+ setTimeout(log, cls_nullsink.statsIntervalMs-1);
+ }
+ function log(){
+ const now = hrtime();
+ const durationMs = Math.floor(hrtimeDiffMs(now, cls_nullsink.lastStats));
+ const totlMs = Math.floor(hrtimeDiffMs(now, cls_nullsink.srvStart))
+ cls_nullsink.reqTotl += cls_nullsink.reqCnt;
+ stdlog.write("Stats: Consumed "
+ + cls_nullsink.reqCnt +" req in "
+ + durationMs +" ms. So avg "
+ + Math.floor(cls_nullsink.reqCnt / durationMs * 1000) +" req/sec of overall "
+ + cls_nullsink.reqTotl +" req in "
+ + Math.floor(totlMs/1000) +" sec. Avg "
+ + Math.floor(cls_nullsink.reqTotl / totlMs)
+ +"\n");
+ cls_nullsink.lastStats = now;
+ cls_nullsink.reqCnt = 0;
+ scheduleOne();
+ }
+}
+
+
+function hrtimeDiffMs( subtrahend, minuend ){
+ return 1000 * (subtrahend[0] - minuend[0])
+ + 0.000001 * (subtrahend[1] - minuend[1]) ;
+}
+
+
+}()); /*endOfModule*/
diff --git a/src/main/nodejs/HttpFlood/SetGateleenHook.js b/src/main/nodejs/HttpFlood/SetGateleenHook.js
new file mode 100644
index 0000000..2e3c27a
--- /dev/null
+++ b/src/main/nodejs/HttpFlood/SetGateleenHook.js
@@ -0,0 +1,127 @@
+;(function(){ "use strict";
+
+const http = require("http");
+const util = require("util");
+const DevNull = { write:function(){} };
+
+const stdin = process.stdin ;
+const stdout = process.stdout;
+const stdlog = process.stderr;
+
+
+setTimeout(main);
+
+
+function printHelp(){
+ stdout.write("\n"
+ +" hiddenalphas simple gateleen hook utilty.\n"
+ +"\n"
+ +" Options:\n"
+ +"\n"
+ +" --host <ip|hostname> (default 127.0.0.1)\n"
+ +" Gateleen to use.\n"
+ +"\n"
+ +" --port <int> (default 7012)\n"
+ +" Target port.\n"
+ +"\n"
+ +" --path <string>\n"
+ +" Target path where to set the hook for.\n"
+ +"\n"
+ +" --destination <url>\n"
+ +" Destination of the hook. Aka where gateleen will forward the\n"
+ +" requests to.\n"
+ +"\n"
+ +" --hook-timeout-sec <int> (default 300)\n"
+ +" Lifetime of the hook in seconds.\n"
+ +"\n"
+ +" --route\n"
+ +" Set a route hook.\n"
+ +"\n"
+ +" --listener\n"
+ +" Set a listener hook.\n"
+ +"\n");
+}
+
+
+function parseArgs( cls_hook, args ){
+ cls_hook.host = "127.0.0.1";
+ cls_hook.port = 7012;
+ cls_hook.path = null;
+ cls_hook.destination = null;
+ cls_hook.hookTimeoutSec = 300;
+ cls_hook.isRoute = false;
+ cls_hook.isListener = false;
+ for( let i=2 ; i<args.length ; ++i ){
+ let arg = args[i];
+ if( arg == "--help" ){
+ printHelp(); return -1;
+ }else if( arg == "--host" ){
+ cls_hook.host = args[++i];
+ if( !args[i] ){ stdlog.write("Arg --host expects a value\n"); return -1; }
+ }else if( arg == "--port" ){
+ cls_hook.port = parseInt(args[++i]);
+ if( isNaN(cls_hook.port) ){ stdlog.write("Arg --port: Cannot parse "+ argv[i]+"\n"); return -1; }
+ }else if( arg == "--path" ){
+ cls_hook.path = args[++i];
+ if( !args[i] ){ stdlog.write("Arg --path expects a value\n"); return -1; }
+ }else if( arg == "--destination"){
+ cls_hook.destination = args[++i];
+ if( !args[i] ){ stdlog.write("Arg --destination expects a value\n"); return -1; }
+ }else if( arg == "--hook-timeout-sec"){
+ cls_hook.hookTimeoutSec = parseInt(args[++i]);
+ if( isNaN(cls_hook.hookTimeoutSec) ){ stdlog.write("Parse --hook-timeout-sec failed: "+args[i]+"\n"); return -1; }
+ }else if( arg == "--route"){
+ cls_hook.isRoute = true;
+ }else if( arg == "--isListener"){
+ cls_hook.isListener = true;
+ }else{
+ stdlog.write("Unknown arg: "+ arg +"\n");
+ }
+ }
+ if( cls_hook.host === null ){ stdlog.write("Arg --host missing\n"); return -1; }
+ if( cls_hook.port === null ){ stdlog.write("Arg --port missing\n"); return -1; }
+ if( cls_hook.path === null ){ stdlog.write("Arg --path missing\n"); return -1; }
+ if( cls_hook.destination === null ){ stdlog.write("Arg --destination missing\n"); return -1; }
+ if( !cls_hook.isRoute && !cls_hook.isListener ){ stdlog.write("Need one of --route or --listener\n"); return -1; }
+ if( cls_hook.isRoute && cls_hook.isListener ){ stdlog.write("Cannot be --route and --listener simultaneously\n"); return -1; }
+ return 0;
+}
+
+
+function main() {
+ const cls_hook = Object.seal({
+ host: null,
+ port: null,
+ path: null,
+ destination: null,
+ hookTimeoutSec: null,
+ isRoute: false,
+ isListener: false,
+ });
+ if( parseArgs(cls_hook, process.argv) ) return;
+ setHook(cls_hook);
+}
+
+
+function setHook( cls_hook ){
+ const req = http.request({
+ hostname: cls_hook.host, port: cls_hook.port,
+ method: "PUT",
+ path: cls_hook.path + "/_hooks/"+ (cls_hook.isRoute ? "route" : "listener"),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ req.on("error", function( err ){ console.error(err); });
+ req.on("response", function( rsp ){
+ stdout.write( "HTTP "+ rsp.statusCode +" "+ rsp.statusMessage +"\n" );
+ });
+ req.end(JSON.stringify({
+ destination: cls_hook.destination,
+ methods: [],
+ }));
+}
+
+
+}()); /*endOfModule*/
+
diff --git a/src/main/nodejs/HttpFlood/putshowcaseresources.js b/src/main/nodejs/HttpFlood/putshowcaseresources.js
new file mode 100644
index 0000000..9845d61
--- /dev/null
+++ b/src/main/nodejs/HttpFlood/putshowcaseresources.js
@@ -0,0 +1,133 @@
+;(function(){ "use strict";
+
+
+const http = require("http");
+const stdout = process.stdout;
+const stdlog = process.stderr;
+const noop = function(){};
+
+
+setTimeout(main);
+
+
+function printHelp(){
+ stdout.write("\n"
+ +" Put showcase data into gateleen-playground via HTTP\n"
+ +"\n"
+ +" Options:\n"
+ +"\n"
+ +" --host <ip|hostname>\n"
+ +" Gateleen host to use.\n"
+ +"\n"
+ +" --port <int> (default 7012)\n"
+ +" TCP port of gateleen to use.\n"
+ +"\n"
+ +" --path <string>\n"
+ +" Root where to PUT trash data. Example: '/playground/tmp/my-trash'.\n"
+ +"\n");
+}
+
+
+function parseArgs( cls_putit, args ){
+ // Defaults
+ cls_putit.host = null;
+ cls_putit.port = 7012;
+ cls_putit.path = null;
+ for( let i=2 ; i<args.length ; ++i ){
+ let arg = args[i];
+ if( arg == "--help" ){
+ printHelp(); return -1;
+ }else if( arg == "--host" ){
+ cls_putit.host = args[++i];
+ if( !args[i] ){ stdlog.write("Arg --host needs value\n"); return -1; }
+ }else if( arg == "--port" ){
+ cls_putit.port = parseInt(args[++i]);
+ if( isNaN(cls_putit.port) ){ stdlog.write("Failed to parse --port: "+args[i]+"\n"); return -1; }
+ }else if( arg == "--path" ){
+ cls_putit.path = args[++i];
+ if( !args[i] ){ stdlog.write("Arg --path expects a value\n"); return -1; }
+ }else{
+ stdlog.write("Unknown arg: "+ arg +"\n");
+ }
+ }
+ if( !cls_putit.host ){ stdlog.write("Arg --host missing\n"); return -1; }
+ if( !cls_putit.path ){ stdlog.write("Arg --path missing\n"); return -1; }
+ return 0;
+}
+
+
+function main(){
+ const cls_putit = Object.seal({
+ host: null,
+ port: null,
+ path: null,
+ pendingRequests: 0,
+ httpAgent: null,
+ });
+ if( parseArgs(cls_putit, process.argv) ) return;
+ cls_putit.httpAgent = new http.Agent({
+ keepAlive:true, maxSockets:16, keepAliveMsecs: 42000,
+ });
+ putShowcase(cls_putit, onPutShowcaseComplete.bind(0,cls_putit));
+}
+
+
+function onPutShowcaseComplete( cls_putit ){
+ stdout.write("Done :) Take a look at your gateleen. There's some trash for experimenting now.\n");
+}
+
+
+function putShowcase( cls_putit, onComplete ){
+ const level1 = 10;
+ const level2 = 3000;
+ let body = {};
+ for( let i=0 ; i<42 ; ++i ){
+ body["prop-"+ i] = "Hi There :)";
+ }
+ body = JSON.stringify( body );
+ stdlog.write("PUTting trash to http://"+ cls_putit.host +":"+ cls_putit.port + cls_putit.path +"\n");
+ stdlog.write("Might take a moment. You can consult your CPU monitor meanwhile ;)\n");
+ for( let iOne=0 ; iOne < level1 ; ++iOne ){
+ for( let iTwo=0 ; iTwo < level2 ; ++iTwo ){
+ const cls_req = Object.seal({
+ cls_putit: cls_putit,
+ onComplete: onComplete,
+ req: null,
+ rsp: null,
+ });
+ const req = cls_req.req = http.request({
+ agent: cls_putit.httpAgent,
+ host: cls_putit.host,
+ port: cls_putit.port,
+ path: cls_putit.path +"/levlA-"+iOne+"/levlB-"+(iTwo),
+ method: "PUT",
+ });
+ req.on("error", function( err ){ console.error(err); });
+ req.on("response", function( rsp ){
+ cls_req.rsp = rsp;
+ if( rsp.statusCode!=200 ){
+ stdlog.write( "ERROR: HTTP "+ rsp.statusCode +" "+ rsp.statusMessage +"\n" );
+ }
+ rsp.on("data", noop);
+ rsp.on("end", onResponseEnd.bind(0,cls_req));
+ });
+ cls_putit.pendingRequests += 1;
+ cls_req.req.end(body);
+ }
+ }
+}
+
+
+function onResponseEnd( cls_req ){
+ const cls_putit = cls_req.cls_putit;
+ cls_putit.pendingRequests -= 1;
+ if( cls_putit.pendingRequests > 0 ){
+ return; // Await more.
+ }
+ if( cls_req.onComplete ){
+ cls_req.onComplete();
+ }
+}
+
+
+}());