weatherstation/firmware/libraries/ESP8266_Influxdb/test/server/server.js
2022-09-16 09:20:19 +02:00

797 lines
26 KiB
JavaScript

const express = require('express');
const readline = require('readline');
var os = require('os');
const e = require('express');
const app = express();
const mgmtApp = express();
const port = 999;
const mgmtPort = 998;
var pointsdb = [];
var lastUserAgent = '';
var chunked = false;
var delay = 0;
var permanentError = 0;
const prefix = '';
var server = undefined;
mgmtApp.use (function(req, res, next) {
var data='';
req.setEncoding('utf8');
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.body = data;
next();
});
});
mgmtApp.get('/start', (req,res) => {
if(server === undefined) {
console.log('Starting server');
server = app.listen(port);
server.on('close',function() {
pointsdb = [];
server = undefined;
console.log('Server closed');
});
server.on('clientError', (err, socket) => {
console.log("client error", err);
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
res.status(201).send(`Listening on http://${server.address().address}:${server.address().port}`);
} else {
server.
res.status(204).end();
};
});
mgmtApp.get('/stop', (req,res) => {
if(server === undefined) {
res.status(404).end();
} else {
console.log('Shutting down server');
server.close();
res.status(200).end();
};
});
mgmtApp.get('/status', (req,res) => {
if(server === undefined) {
res.status(404).send("stopped");
} else {
res.status(200).send("running");
}
});
mgmtApp.post('/log', (req,res) => {
console.log('===' + req.body + '=====');
res.status(204).end();
})
app.use (function(req, res, next) {
var data='';
req.setEncoding('utf8');
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.body = data;
next();
});
});
app.get(prefix + '/test/user-agent', (req,res) => {
res.status(200).send(lastUserAgent);
})
app.get(prefix + '/ready', (req,res) => {
lastUserAgent = req.get('User-Agent');
res.status(200).send("<html><body><h1>OK</h1></body></html>");
})
app.get(prefix + '/health', (req,res) => {
lastUserAgent = req.get('User-Agent');
res.status(200).send("<html><body><h1>OK</h1></body></html>");
})
app.get(prefix + '/ping', (req,res) => {
lastUserAgent = req.get('User-Agent');
if(req.query['verbose'] == 'true') {
res.status(200).send("<html><body><h1>OK</h1></body></html>");
} else {
res.status(204).end();
}
})
app.post(prefix + '/api/v2/write', (req,res) => {
chunked = false;
if(checkWriteParams(req, res) && handleAuthentication(req, res)) {
//console.log('Write');
//console.log(req.body);
var points = parsePoints(req.body);
if(permanentError > 0) {
console.log('Pernament error ' + permanentError);
res.status(permanentError).send("Internal server error");
}
if(Array.isArray(points) && points.length > 0) {
var point = points[0];
if(point.tags.hasOwnProperty('direction')) {
if(permanentError > 0) {
if(point.tags.direction == 'permanent-unset' ) {
permanentError = 0;
}
} else {
switch(point.tags.direction) {
case '429-1':
console.log('Limit exceeded');
res.set("Retry-After","10");
console.log('Retry-After 10');
res.status(429).send("Limit exceeded");
break;
case '429-2':
console.log('Limit exceeded');
console.log('Retry-After default');
res.status(429).send("Limit exceeded");
break;
case '503-1':
res.set("Retry-After","10");
console.log('Server overloaded');
console.log('Retry-After 10');
res.status(503).send("Server overloaded");
break;
case '503-2':
console.log('Server overloaded');
console.log('Retry-After default');
res.status(503).send("Server overloaded");
break;
case 'delete-all':
pointsdb = [];
res.status(204).end();
break;
case 'status':
points = [];
const code = parseInt(point.tags['x-code']);
console.log("Set code: " + code);
res.status(code).send("bad request");
break;
case 'chunked':
chunked = true;
console.log("Set chunked = true");
break;
case 'timeout':
delay = parseInt(point.tags.timeout)*1000;
console.log("Set delay: " + delay);
break;
case 'permanent-set':
permanentError = parseInt(point.tags['x-code']);
console.log("Set permanentError: " + permanentError);
res.status(permanentError).send("bad request");
break;
}
}
points.shift();
}
console.log("write " + points.length + ' points');
points.forEach((item, index) => {
pointsdb.push(item);
})
if(res.statusCode < 299) {
res.status(204).end();
}
} else {
res.status(204).end();
}
}
if(res.statusCode != 204) {
console.log('Responded with ' + res.statusCode);
}
})
app.post(prefix + '/write', (req,res) => {
if(checkWriteParamsV1(req, res) ) {
var points = parsePoints(req.body);
if(Array.isArray(points) && points.length > 0) {
var point = points[0];
if(point.tags.hasOwnProperty('direction')) {
switch(point.tags.direction) {
case 'delete-all':
pointsdb = [];
buckets = [];
res.status(204).end();
break;
case '400':
points = [];
res.status(400).send("bad request");
break;
case '500':
points = [];
res.status(500).send("internal server error");
break;
}
points.shift();
}
console.log("write " + points.length + ' points');
points.forEach((item, index) => {
pointsdb.push(item);
})
if(res.statusCode < 299) {
res.status(204).end();
}
} else {
res.status(204).end();
}
}
if(res.statusCode != 204) {
console.log('Responded with ' + res.statusCode);
}
})
app.post(prefix + '/api/v2/delete', (req,res) => {
console.log('Deleteting points');
pointsdb = [];
buckets = [];
res.status(204).end();
});
var queryRes = {
"singleTable":`#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
#group,false,false,true,true,false,false,true,true,true,true
#default,_result,,,,,,,,,
,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.4,f,test,1,adsfasdf
,,1,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,3,adsfasdf
\r
`,
"nil-value": `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,,f,test,1,adsfasdf
,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,,adsfasdf
,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:11:32.225467895Z,1122.45,f,test,3,
\r
`,
"multiTables":`#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,unsignedLong,string,string,string,string
,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
,_result,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,14,f,test,1,adsfasdf
,_result,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,66,f,test,1,adsfasdf
\r
#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,string,string,string
,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
,_result1,1,2020-02-16T22:19:49.747562847Z,2020-02-17T22:19:49.747562847Z,2020-02-17T10:34:08.135814545Z,-4,i,test,1,adsfasdf
,_result1,1,2020-02-16T22:19:49.747562847Z,2020-02-17T22:19:49.747562847Z,2020-02-16T22:08:44.850214724Z,-1,i,test,1,adsfasdf
\r
#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,boolean,string,string,string,string
,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
,_result2,2,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,false,b,test,0,brtfgh
,_result2,2,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.969100374Z,true,b,test,0,brtfgh
\r
#datatype,string,long,dateTime:RFC3339Nano,dateTime:RFC3339Nano,dateTime:RFC3339Nano,duration,string,string,string,base64Binary
,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
,_result3,3,2020-02-10T22:19:49.747562847Z,2020-02-12T22:19:49.747562847Z,2020-02-11T10:34:08.135814545Z,1d2h3m4s,d,test,0,eHh4eHhjY2NjY2NkZGRkZA==
,_result3,3,2020-02-10T22:19:49.747562847Z,2020-02-12T22:19:49.747562847Z,2020-02-12T22:08:44.969100374Z,22h52s,d,test,0,ZGF0YWluYmFzZTY0
\r
`,
"diffNum-data":`#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,duration,base64Binary,dateTime:RFC3339
,result,table,_start,_stop,_time,deviceId,sensor,elapsed,note,start
,,0,2020-04-28T12:36:50.990018157Z,2020-04-28T12:51:50.990018157Z,2020-04-28T12:38:11.480545389Z,1467463,BME280,1m1s,ZGF0YWluYmFzZTY0,2020-04-27T00:00:00Z,2345234
\r
`,
"diffNum-type-vs-header":`#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,duration,base64Binary,dateTime:RFC3339
,result,table,_start,_stop,_time,deviceId,sensor,elapsed,note
,,0,2020-04-28T12:36:50.990018157Z,2020-04-28T12:51:50.990018157Z,2020-04-28T12:38:11.480545389Z,1467463,BME280,1m1s,ZGF0YWluYmFzZTY0,2020-04-27T00:00:00Z
\r
`,
"flux-error":`{"code":"invalid","message":"compilation failed: loc 4:17-4:86: expected an operator between two expressions"}`,
"invalid-datatype":`#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,int,string,duration,base64Binary,dateTime:RFC3339
,result,table,_start,_stop,_time,deviceId,sensor,elapsed,note,start
,,0,2020-04-28T12:36:50.990018157Z,2020-04-28T12:51:50.990018157Z,2020-04-28T12:38:11.480545389Z,1467463,BME280,1m1s,ZGF0YWluYmFzZTY0,2020-04-27T00:00:00Z
,,0,2020-04-28T12:36:50.990018157Z,2020-04-28T12:51:50.990018157Z,2020-04-28T12:39:36.330153686Z,1467463,BME280,1h20m30.13245s,eHh4eHhjY2NjY2NkZGRkZA==,2020-04-28T00:00:00Z
\r
`,
"missing-datatype":`,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
,_result3,3,2020-02-10T22:19:49.747562847Z,2020-02-12T22:19:49.747562847Z,2020-02-11T10:34:08.135814545Z,1d2h3m4s,d,test,0,eHh4eHhjY2NjY2NkZGRkZA==
,_result3,3,2020-02-10T22:19:49.747562847Z,2020-02-12T22:19:49.747562847Z,2020-02-12T22:08:44.969100374Z,22h52s,d,test,0,eHh4eHhjY2NjY2NkZGRkZA==
\r
`,
"error-it-row-full":`#datatype,string,string
,error,reference
,failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time,897
\r
`,
"error-it-row-no-reference":`#datatype,string,string
,error,reference
,failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time,
\r
`,
"error-it-row-no-message":`#datatype,string,string
,error,reference
,,
\r
`,
"empty":``
};
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
app.post(prefix+'/api/v2/query', (req,res) => {
//console.log("Query with: " + req.body);
if(checkQueryParams(req, res) && handleAuthentication(req, res)) {
var queryObj = JSON.parse(req.body);
var data = '';
var status = 200;
if (queryObj["query"].startsWith('testquery-')) {
var qi = queryObj["query"].substring(10) ;
console.log('query: ' + qi + ' dataset');
if(qi.endsWith('error')) {
status = 400;
}
data = queryRes[qi];
} else if(queryObj["query"] === 'echo') {
status = 444;
data = JSON.stringify(queryObj);
} else if(pointsdb.length > 0) {
console.log('query: ' + pointsdb.length + ' points');
data = convertToCSV(pointsdb);
}
if(data.length > 0) {
if(delay) {
sleep(delay);
delay = 0;
}
//console.log(data);
if(chunked) {
var i = data.length/3;
res.set("Transfer-Encoding","chunked");
res.status(status);
res.write(data.substring(0, i+1));
res.write(data.substring(i+1, 2*i+1));
res.write(data.substring(2*i+1));
res.end();
chunked = false;
} else {
res.status(status).send(data);
}
} else {
res.status(status).end();
}
} else {
res.status(400).send('invalid query request');
}
});
const orgID = `e2e2d84ffb3c4f85`;
const orgName = `my-org`;
var buckets = []
function bucketResp(bucket) {
return `{
"id": "${bucket.id}",
"orgID": "${bucket.orgID}",
"type": "user",
"name": "${bucket.name}",
"retentionRules": [
{
"type": "expire",
"everySeconds": ${bucket.expire},
"shardGroupDurationSeconds": 604800
}
],
"createdAt": "2021-08-24T07:53:17.2525733Z",
"updatedAt": "2021-08-24T07:53:17.2525734Z",
"links": {
"labels": "/api/v2/buckets/3eae77843acbebee/labels",
"members": "/api/v2/buckets/3eae77843acbebee/members",
"org": "/api/v2/orgs/e2e2d84ffb3c4f85",
"owners": "/api/v2/buckets/3eae77843acbebee/owners",
"self": "/api/v2/buckets/3eae77843acbebee",
"write": "/api/v2/write?org=e2e2d84ffb3c4f85\u0026bucket=3eae77843acbebee"
},
"labels": []
}
`
}
function bucketsResp(buckets){
return `{
"links": {
"self": "/api/v2/buckets?descending=false\u0026limit=20\u0026offset=0"
},
"buckets": [
${buckets}
]
}
`
}
const invalidIDResp = `{
"code": "invalid",
"message": "id must have a length of 16 bytes"
}
`
const notfoundResp = `{
"code": "not found",
"message": "bucket not found"
}`
const orgNotFoundReps= `{
"code": "not found",
"message": "organization not found"
}`
app.get(prefix+'/api/v2/buckets', (req,res) => {
if(handleAuthentication(req, res)) {
var name = req.query['name'];
var id = req.query['id'];
res.set("Content-Type","application/json");
res.status(200);
if(name) { //filter by name
console.log("GET buckets name: " + name)
let b = buckets.find((value)=> {
return value.name === name
})
if(b) {
res.send(bucketResp(b))
} else { //invalid name
res.send(bucketsResp(''))
}
} else if(id) { //filter by id
console.log("GET buckets id: " + id)
if(id.length != 16) { //invalid name
res.status(400)
res.send(invalidIDResp)
return
}
let b = buckets.find((value)=> {
return value.id === id
})
if(b) {
res.send(bucketResp(b))
} else {
res.status(404)
res.send(notfoundResp)
}
} else { //return all buckets
console.log("GET all buckets")
bucketsJson = buckets.reduce((total, value, index) => {
return total + (index > 0?",\n":"") + bucketResp(value)
},'')
res.send(bucketsResp(bucketsJson))
}
}
});
function invalidJSONResp(err) {
return `{
"code": "invalid",
"message": "${err}"
}`
}
function conflictBucketResp(bucket) {
return `{
"code": "conflict",
"message": "bucket with name ${bucket} already exists"
}`
}
const idBase = '0123456789abcdef'
function genID() {
let result = '';
for ( let i = 0; i < 16; i++ ) {
result += idBase.charAt(Math.floor(Math.random() * 16));
}
return result;
}
app.post(prefix+'/api/v2/buckets', (req,res) => {
console.log('Post buckets')
if(handleAuthentication(req, res)) {
let newBucket = undefined
try {
newBucket = JSON.parse(req.body)
} catch(err) {
res.status(400).send(invalidJSONResp(err))
return
}
if(newBucket.orgID !== orgID ) {
res.status(404).send(orgNotFoundReps)
return
}
//console.log('Finding name ' + newBucket.name)
let b = buckets.find((value)=> {
//console.log(' testing ', value)
return value.name?value.name === newBucket.name: false;
})
if(b) {
res.status(422).send(conflictBucketResp(newBucket.name ))
return
}
expire = 0
if(newBucket.retentionRules && newBucket.retentionRules.length > 0) {
expire = newBucket.retentionRules[0].everySeconds
}
let bucket = {
"name": newBucket.name,
"orgID": orgID,
"expire": expire,
"id": genID()
}
buckets.push(bucket)
res.status(201).send(bucketResp(bucket))
}
})
app.delete(prefix+'/api/v2/buckets/:id', (req,res) => {
console.log('Delete buckets')
if(handleAuthentication(req, res)) {
let id = req.params['id']
if(!id) {
res.sendStatus(405)
return
}
//console.log('Finding id ' + id)
let i = buckets.findIndex((value)=> {
//console.log(' testing ', value)
return value.id?value.id === id:false
})
if(i<0) {
res.status(404).send(notfoundResp)
return
}
buckets.splice(i,1)
res.sendStatus(204)
}
})
function orgsResp(orgs) {
return `{
"links": {
"self": "/api/v2/orgs"
},
"orgs": [
${orgs}
]
}`
}
const orgResp = `{
"links": {
"buckets": "/api/v2/buckets?org=my-org",
"dashboards": "/api/v2/dashboards?org=my-org",
"labels": "/api/v2/orgs/e2e2d84ffb3c4f85/labels",
"logs": "/api/v2/orgs/e2e2d84ffb3c4f85/logs",
"members": "/api/v2/orgs/e2e2d84ffb3c4f85/members",
"owners": "/api/v2/orgs/e2e2d84ffb3c4f85/owners",
"secrets": "/api/v2/orgs/e2e2d84ffb3c4f85/secrets",
"self": "/api/v2/orgs/e2e2d84ffb3c4f85",
"tasks": "/api/v2/tasks?org=my-org"
},
"id": "${orgID}",
"name": "${orgName}",
"description": "",
"createdAt": "2021-08-18T06:24:02.427946Z",
"updatedAt": "2021-08-18T06:24:02.427946Z"
}`
app.get(prefix+'/api/v2/orgs', (req,res) => {
if(handleAuthentication(req, res)) {
var name = req.query['org'];
if(name){
if(name === orgName) {
res.status(200).send(orgsResp(orgResp))
} else {
res.status(200).send(orgsResp(""))
}
} else {
res.status(200).send(orgsResp(orgResp))
}
}
})
function parsePoints(data) {
var lines = data.split("\n");
var points = [];
lines.forEach((line, index) => {
var parts = line.split(" ");
if (parts.length > 1) {
var measTags = parts[0];
var fields = parts[1].split(",");
var point = {};
var keys = measTags.split(",");
point.measurement = keys[0];
point.tags = {};
point.fields = {};
if (keys.length > 1) {
for (var i = 1; i < keys.length; i++) {
var keyval = keys[i].split("=");
point.tags[keyval[0]] = keyval[1];
}
}
for (var i = 0; i < fields.length; i++) {
var keyval = fields[i].split("=");
var value = keyval[1];
if (typeof value === 'string' && value.endsWith("i")) {
value = value.substring(0, value.length - 1);
}
point.fields[keyval[0]] = value;
}
if(parts.length>2) {
point.timestamp = parts[2];
}
points.push(point);
}
});
if (points.length > 0) {
return points;
}
else {
return data;
}
}
const AuthToken = "Token my-token";
function handleAuthentication(req, res) {
var auth = req.get('Authorization');
if(auth && auth != AuthToken) {
res.status(401).send(`{"code":"unauthorized","message":"unauthorized access"}`);
return false;
}
let u = req.query['u']
let p = req.query['p']
if(u && p && (p != "my secret password" || u != "user")) {
res.status(401).send(`{"code":"unauthorized","message":"invalid user or password"}`);
return false;
}
return true;
}
const AllowedPrecisions = ['ns','us','ms','s'];
function checkWriteParams(req, res) {
var bucket = req.query['bucket'];
var precision = req.query['precision'];
if(!checkOrg(req, res)) {
return false;
} else if(bucket != 'my-bucket') {
res.status(404).send(`{"code":"not found","message":"bucket \"${bucket}\" not found"}`);
return false;
} else if(typeof precision !== 'undefined' && AllowedPrecisions.indexOf(precision)==-1) {
res.status(400).send(`{"code":"bad request ","message":"precision \"${precision}\" is not valid"}`);
return false;
} else {
return true;
}
}
const AllowedPrecisionsV1 = ['ns','u','ms','s'];
function checkWriteParamsV1(req, res) {
var db = req.query['db'];
var user = req.query['u'];
var pass = req.query['p'];
var precision = req.query['precision'];
if(db != 'my-db') {
res.status(404).send(`{"code":"not found","message":"database \"${db}\" not found"}`);
return false;
} else if(typeof precision !== 'undefined' && AllowedPrecisionsV1.indexOf(precision)==-1) {
res.status(400).send(`precision \"${precision}\" is not valid`);
return false;
} else if (user !== 'user' && pass != 'my secret pass') {
res.status(401).send("unauthorized")
} else {
return true;
}
}
function checkQueryParams(req, res) {
let org = req.query['org']
if(org) {
return checkOrg(req, res);
}
return true
}
function checkOrg(req, res) {
var org = req.query['org'];
if(org !== 'my-org') {
res.status(404).send(`{"code":"not found","message":"organization name \"${org}\" not found"}`);
return false;
} else {
return true;
}
}
function objectToCSV(obj, type, level) {
var line = '';
if(level == 1) line = type==0?'#datatype,':',';
var i = 0;
for (var index in obj) {
if (i>0) line += ',';
if(typeof obj[index] == 'object') {
line += objectToCSV(obj[index], type, level+1);
} else if(type == 0) { //datatype header
line += 'string';
} else if(type == 1) {
line += index;
} else {
line += obj[index];
}
i++;
}
return line;
}
function convertToCSV(objArray) {
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
var str = '';
if(array.length > 0) {
str = objectToCSV(array[0], 0, 1) + '\r\n';
str += objectToCSV(array[0], 1, 1) + '\r\n';
}
for (var i = 0; i < array.length; i++) {
var line = '';
line = objectToCSV(array[i], 2, 1);
str += line + '\r\n';
}
return str;
}
var mgmtServer = mgmtApp.listen(mgmtPort)
var rl = readline.createInterface(process.stdin, process.stdout);
rl.on('line', function(line) {
rl.close();
}).on('close',function(){
if(server !== undefined) {
server.close();
}
mgmtServer.close();
process.exit(0);
});
var ifaces = os.networkInterfaces();
console.log("Available interfaces:")
Object.keys(ifaces).forEach(function (ifname) {
var alias = 0;
ifaces[ifname].forEach(function (iface) {
if ('IPv4' !== iface.family || iface.internal !== false) {
// skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
return;
}
if (alias >= 1) {
// this single interface has multiple ipv4 addresses
console.log(' ', ifname + ':' + alias, iface.address);
} else {
// this interface has only one ipv4 adress
console.log(' ', ifname, iface.address);
}
++alias;
});
});
console.log(`Listening on http://${mgmtServer.address().address}:${mgmtServer.address().port}`)
console.log(`Press Enter to exit`)