Remoto - VFS: VFS_httpd_browser.cpp Source File
Remoto - VFS
VFS_httpd_browser.cpp
Go to the documentation of this file.
1 
2 #include <QFileInfo>
3 #include <QCoreApplication>
4 #include <QHostInfo>
5 #include <QProcess>
6 
7 #include "VFS_httpd_browser.h"
8 
9 #include "qhttpserver.h"
10 #include "qhttprequest.h"
11 #include "qhttpresponse.h"
12 
13 #include "VFS.h"
14 
42 VFS_httpd_browser::VFS_httpd_browser(QString dataRootPath, quint16 port, QHostAddress addr, bool ssl, QString sslCertPath, QString sslKeyPath)
43 : VFS_httpd(port,addr,ssl,sslCertPath,sslKeyPath)
44 , _dataRootPath(dataRootPath)
45 , _dataRoot(nullptr)
46 {
47 }
48 
50 {
51 }
52 
68 void VFS_httpd_browser::handle(QHttpRequest *req, QHttpResponse *resp)
69 {
70  VFS::LOG( QString("Request: %1").arg(req->url().toString()), 8, className() );
71 
72  VFS_httpd_browser_responder *r = new VFS_httpd_browser_responder(req, resp, dataRoot(), _port); //as a new object so it can delete itself later
73 
74  if (r->_needsToIssueRequest)
75  {
76  VFS_node *_node = dataRoot();
77 
78  if (r->_mode == "browse")
79  {
81  QString p = r->_path.mid(strlen("/browse"));
82  p = QUrl::fromPercentEncoding(p.toUtf8());
83  t->_path = p;
84  issueRequest(_node,t);
85  }
86  else if (r->_mode == "report")
87  {
89  QString p = r->_path.mid(strlen("/report"));
90  p = QUrl::fromPercentEncoding(p.toUtf8());
91  t->_path = p;
92  issueRequest(_node,t);
93  }
94  else if (r->_mode == "read")
95  {
97  QString p = r->_path.mid(strlen("/read"));
98  p = QUrl::fromPercentEncoding(p.toUtf8());
99  t->_path = p;
100  issueRequest(_node,t);
101  }
102  else
103  {
104  r->_status = 400; //Bad Request
105  r->respond();
106  }
107  }
108  else
109  r->respond();
110 }
111 
118 {
119  if (_dataRoot) return _dataRoot;
120 
121  VFS_request r(VFS_request::none,nullptr,_dataRootPath,"httpd_browser");
122  _dataRoot = VFS::root()->find(&r);
123  _dataRootPath = r._path;
124 
125  return _dataRoot;
126 }
127 
142 : VFS_request(type,t,"","httpd_browser")
143 , _m_responder(r)
144 {
145 }
146 
148 {
149  //printf("HTTP BROWSER REQUEST DELETE\n");
150 }
151 
163 {
164  //printf("HTTPD RESPONDER!\n");
165 
166  if (_success)
167  {
169  }
170  else
171  {
172  _m_responder->_status = 404;
173  VFS::WARN( QString("'%1' Status: %2: %3").arg(_m_responder->_path).arg(_m_responder->_status).arg(STATUS_CODES.value(_m_responder->_status)), 0, "httpd_browser" );
174  VFS::WARN( _reason, 0, "httpd_browser" );
175  }
176 
178 }
179 
198 VFS_httpd_browser_responder::VFS_httpd_browser_responder(QHttpRequest *req, QHttpResponse *resp, VFS_node *node, quint16 port)
199 : VFS_httpd_responder(req,resp)
200 , _needsToIssueRequest(false)
201 , _done(false)
202 , _process(false)
203 , _node(node)
204 , _port(port)
205 {
206  connect(_m_req, SIGNAL(data(const QByteArray&)), this, SLOT(accumulate(const QByteArray&)), Qt::UniqueConnection);
207  connect(_m_resp, SIGNAL(done()), this, SLOT(done()), Qt::UniqueConnection);
208 
209  validatePath(_m_req->url()); //sets url and path
210 }
211 
213 {
214 }
215 
221 {
222  _done = true;
223 
224  deleteLater();
225 }
226 
232 {
233  if (_done)
234  return;
235 
236 // connect(_m_req, SIGNAL(data(const QByteArray&)), this, SLOT(accumulate(const QByteArray&)), Qt::UniqueConnection);
237  //connect(_m_req, SIGNAL(end()), this, SLOT(reply()));
238  //connect(_m_req, SIGNAL(end()), _m_resp, SLOT(end()));
239 // connect(_m_resp, SIGNAL(done()), this, SLOT(done()), Qt::UniqueConnection);
240 
241  switch(_status)
242  {
243  case 200: { QByteArray page = fetchResponse();
244  _m_resp->setHeader("Content-Type", _mime);
245  _m_resp->writeHead(_status);
246  _m_resp->write(page);
247  _m_resp->end();
248  }
249  break;
250 
251  default: { _m_resp->writeHead(_status);
252  _m_resp->end(errorPage());
253  }
254  break;
255  }
256 }
257 
287 {
288  _url = url;
289  _path = _url.path();
290 
291  if (_path=="/") _path="/index.rhtml";
292 
293  if (_path.startsWith("/browse"))
294  {
295  _status = 200;
296  _mime = "text/html";
297  _mode = "browse";
298  _needsToIssueRequest = true;
299  }
300  else if (_path.startsWith("/report"))
301  {
302  _status = 200;
303  _mime = "text/html";
304  _mode = "report";
305  _needsToIssueRequest = true;
306  }
307  else if (_path.startsWith("/read"))
308  {
309  _status = 200;
310  _mime = "application/json";
311  _mode = "read";
312  _needsToIssueRequest = true;
313  }
314  else if (_path.startsWith("/status"))
315  {
316  _status = 200;
317  _mime = "application/json";
318  _mode = "status";
319  }
320  else if (_path.startsWith("/command"))
321  {
322  _status = 200;
323  _mime = "text/html";
324  _mode = "command";
325  }
326  else if (!QFile::exists(":/VFS_httpd_browser"+_path))
327  {
328  _status = 404;
329  VFS::WARN( QString("'%1' Status: %2: %3").arg(_path).arg(_status).arg(STATUS_CODES.value(_status)), 0, "httpd_browser" );
330  _mode = "error";
331  }
332  else
333  {
334  _status = 200;
335  QFileInfo info(_path);
336  _mime = VFS_httpd::MIME_TYPES.value(info.suffix().toLower(),QString("text/html"));
337  _mode = "file";
338 
339  if (info.suffix().toLower()=="rhtml") _process = true;
340  }
341 }
342 
356 {
357  QByteArray reply;
358 
359  if (_mode == "browse")
360  {
361  _path.remove(0, strlen("/browse"));
362  reply = lsDir();
363  }
364  else if (_mode == "report")
365  {
366  //path.remove(0, strlen("/report"));
367  reply = _data.toJson();
368  }
369  else if (_mode == "read")
370  {
371  reply = _data.toJson();
372  }
373  else if (_mode == "status")
374  {
375  QStringList pp = _path.split("/",Qt::SkipEmptyParts);
376  pp.removeFirst(); //chop off "/status/"
377 
378  QJsonObject r;
379  r["outstanding"] = (qint64) VFS_request::_refcount;
380  r["sample"] = (qint64) VFS_request::_sampleCount;
381 
382  QJsonObject c;
383  c["count"] = getSystemValue("cpucount");
384  c["sample"] = getSystemValue("cpusample");
385 
386  QJsonObject m;
387  m["total"] = getSystemValue("memtotal");
388  m["sample"] = getSystemValue("memsample");
389 
390  QJsonObject u;
391  u["ms"] = VFS::uptime();
392  u["string"] = VFS::uptimeString();
393 
394  QJsonObject s;
395  s["cpu"] = c;
396  s["memory"] = m;
397  s["uptime"] = u;
398 
399  QJsonObject o;
400 
401  if (!pp.length()) //path=="", return all statuses
402  {
403  o["requests"] = r;
404  o["system"] = s;
405 
406  return QJsonDocument(o).toJson(QJsonDocument::Compact);
407  }
408  else if (pp[0]=="requests")
409  {
410  pp.removeFirst(); //remove "requests/"
411 
412  if (!pp.length())
413  {
414  return QJsonDocument(r).toJson(QJsonDocument::Compact);
415  }
416  else if (pp[0] == "outstanding")
417  {
418  QJsonObject oo {{"outstanding",r["outstanding"]}};
419  return QJsonDocument(oo).toJson(QJsonDocument::Compact);
420  }
421  else if (pp[0] == "sample")
422  {
423  QJsonObject oo {{"sample",r["sample"]}};
424  return QJsonDocument(oo).toJson(QJsonDocument::Compact);
425  }
426  }
427  else if (pp[0]=="system")
428  {
429  pp.removeFirst(); //remove "system/"
430 
431  if (!pp.length())
432  {
433  return QJsonDocument(s).toJson(QJsonDocument::Compact);
434  }
435  else if (pp[0] == "cpu")
436  {
437  return QJsonDocument(s["cpu"].toObject()).toJson(QJsonDocument::Compact);
438  }
439  else if (pp[0] == "memory")
440  {
441  return QJsonDocument(s["memory"].toObject()).toJson(QJsonDocument::Compact);
442  }
443  else if (pp[0] == "uptime")
444  {
445  return QJsonDocument(s["uptime"].toObject()).toJson(QJsonDocument::Compact);
446  }
447  }
448 
449  _status = 404;
450  VFS::WARN( QString("unknown status request: %1").arg(_path), 0, "httpd_browser" );
451  return errorPage();
452  }
453  else if (_mode == "command")
454  {
455  _path.remove(0, strlen("/command/"));
456 
457  if (_path=="exit")
458  { QCoreApplication::exit(0);
459  reply = "";
460  }
461  else if (_path == "parameters")
462  {
463  reply = VFS::parameters().toJson();
464  }
465  else
466  {
467  _status = 404;
468  VFS::WARN( QString("unknown command: %1").arg(_path), 0, "httpd_browser" );
469  return errorPage();
470  }
471  }
472  else if (_mode == "file")
473  {
474  QFile page(":/VFS_httpd_browser"+_path);
475  if (!page.open(QIODevice::ReadOnly))
476  {
477  _status = 403;
478  VFS::WARN( QString("'%1' Status: %2: %3").arg(_path).arg(_status).arg(STATUS_CODES.value(_status)), 0, "httpd_browser" );
479  return errorPage();
480  }
481 
482  reply = page.readAll();
483 
484  if (_process) reply = processRHTML(reply);
485  }
486  else
487  {
488  _status = 403;
489  VFS::WARN( QString("'%1' Status: %2: %3").arg(_path).arg(_status).arg(STATUS_CODES.value(_status)), 0, "httpd_browser" );
490  return errorPage();
491  }
492 
493  return reply;
494 }
495 
496 // this is better:
497 // http://stackoverflow.com/questions/5636375/how-to-create-a-collapsing-tree-table-in-html-css-js
498 
505 {
506  QByteArray reply;
507  QString s;
508  QJsonObject o = _data.object();
509  QJsonObject::iterator i = o.begin();
510 
511  reply += "<ul class='dirListing'>\n";
512 
513  while (i != o.end())
514  {
515  QString s = i.key();
516 
517  //if (s.right(1) == "/")
518  reply += QString("\t<li class='vfs_node vfs_node_closed' path='"+_path+"/"+s+"'>" + s + "</li>\n").toUtf8();
519  //else
520  // reply += "\t<li class='vfs_node vfs_node_file' path='"+path+s+"'><span class='permissions'>type rwxrwxrwx</span>" + s + "</li>\n";
521 
522  ++i;
523  }
524 
525  reply += "</ul>\n";
526 
527  return reply;
528 }
529 
551 QByteArray VFS_httpd_browser_responder::processRHTML(QByteArray page)
552 {
553  QRegExp token("\\[###(.*)###\\]");
554  token.setMinimal(true);
555 
556  //printf("PROCESSING!\n");
557 
558  QByteArray processed;
559 
560  while(token.indexIn(page) > -1)
561  {
562  QStringList commandArgs = token.capturedTexts()[1].split(","); //tokens may have arguments separated by commas
563  QString command = commandArgs.takeFirst().toLower();
564 
565  if (command=="hostname") processed = QHostInfo::localHostName().toUtf8();
566  else if (command=="port") processed = QString("%1").arg(_port).toUtf8();
567  else if (command=="app_path") processed = QCoreApplication::applicationFilePath().toUtf8();
568  else if (command=="arguments") processed = QCoreApplication::arguments().join(" ").toUtf8();
569  else if (command=="version") processed = QCoreApplication::applicationVersion().toUtf8();
570  else if (command=="starttime") processed = QDateTime::fromMSecsSinceEpoch(VFS::starttime()).toString(Qt::TextDate).toUtf8();
571  else if (command=="uptime") processed = VFS::uptimeString().toUtf8();
572  else if (command=="vfs_requests") processed = QString("%1").arg(VFS_request::_refcount).toUtf8();
573  else processed = QString("<div class='warning'>Unknown Command: <b>%1</b></div>").arg(command).toUtf8();
574 
575  page = page.replace(token.pos(0),token.matchedLength(),processed);
576  }
577 
578  return page;
579 }
580 
594 {
595  QString kernel = QSysInfo::kernelType().toLower();
596  QString command;
597  QString suffix;
598 
599  if (kernel == "linux")
600  {
601  if (which == "cpucount")
602  {
603  command = "grep -c ^processor /proc/cpuinfo";
604  }
605  else if (which == "cpusample")
606  {
607  command = "sar -u 1 1 | grep verage | perl -pe \"s/\\s+/+/g\" | cut -f 3,5 -d '+' | bc -l";
608  //command = "ps -A -o %cpu | awk '{s+=$1} END {print s/`grep -c ^processor /proc/cpuinfo`}'"; //not sure why 100- here, but not darwin
609  suffix = "%";
610  }
611  else if (which == "memtotal")
612  {
613  command = "awk '( $1 == \"MemTotal:\" ) { print $2/1048576 }' /proc/meminfo";
614  suffix = " gb";
615  }
616  else if (which == "memsample")
617  {
618  command = "free -b | grep Mem | perl -pe \"s/\\s+/ /g\" | cut -f 2,4 -d \" \" | perl -pe \"s/\\s+/ \\/ /\" | perl -pe \"s/(.*)/scale=10; 100*(1-1\\/(\\1))/\" | bc -l";
619  suffix = "%";
620  }
621  }
622  else if (kernel == "darwin")
623  {
624  if (which == "cpucount")
625  {
626  command = "sysctl -n hw.ncpu";
627  }
628  else if (which == "cpusample")
629  {
630  command = "ps -A -o %cpu | awk '{s+=$1} END {print s}'";
631  suffix = "%";
632  }
633  else if (which == "memtotal")
634  {
635  command = "awk '( $1 == \"MemTotal:\" ) { print $2/1048576 }' /proc/meminfo";
636  suffix = " gb";
637  }
638  else if (which == "memsample")
639  {
640  //command = "free -b | grep Mem | perl -pe \"s/\\s+/ /g\" | cut -f 2,4 -d \" \" | perl -pe \"s/\\s+/ \\/ /\" | perl -pe \"s/(.*)/scale=10; 100*(1-1\\/(\\1))/\" | bc -l";
641  command = "memory_pressure | grep percentage | perl -pe \"s/([^\\d]*)(\\d+)([^\\d]*)/scale=20; \\2 \\/ 100\\n/\" | bc -l";
642  suffix = "%";
643  }
644  }
645  else
646  VFS::ERROR(QString("Unknown kernel: %1. Can't fetch system value.").arg(kernel),0,"httpd_browser");
647 
648  if (!command.isEmpty())
649  {
650  QProcess process;
651  process.start("bash", QStringList() << "-c" << command);
652  process.waitForFinished(5000); // will wait for 5 seconds to finish
653 
654  QString stdout = process.readAllStandardOutput();
655  QString stderr = process.readAllStandardError();
656 
657  //printf("stdout: %s\n",qUtf8Printable(stdout));
658  //printf("stderr: %s\n",qUtf8Printable(stderr));
659 
660  if (!stderr.isEmpty())
661  VFS::WARN(stderr,0,"httpd_browser");
662 
663  return stdout.trimmed()+suffix;
664  }
665 
666  return QJsonValue::Null;
667 }
A subclass of VFS_request.
virtual void execute()
Execute the request now that it has been satisfied.
VFS_httpd_browser_responder * _m_responder
The responder object used during execute()
VFS_httpd_browser_request(VFS_request::requestType type, VFS_node *t, VFS_httpd_browser_responder *r)
A subclass of VFS_httpd_responder.
QJsonValue getSystemValue(QString which)
Return information about the system running VFS.
virtual void respond()
based on http status, write page contents or an error to the responder
QByteArray fetchResponse()
Actually fetch and/or prep the response data.
QByteArray lsDir()
Convert an incoming json object to an html <ul> for display.
virtual void validatePath(QUrl url)
Based on the incoming url, decide what work needs to be done.
QJsonDocument _data
The response data, populated after a VFS_request is complete, or immediately if not asynchronous.
void done()
Mark the responder as _done, and schedule a deletion with deleteLater().
QByteArray processRHTML(QByteArray page)
Detokenize an .rhtml file.
QString _mode
either browse, report, command, or file. Used in fetchFile()
bool _process
A flag meaning that the response data must be detokenized. This is used for .rhtml files.
quint16 _port
The port that VFS_httpd_browser is listening on. Only used as a print value when processing ....
VFS_httpd_browser_responder(QHttpRequest *req, QHttpResponse *resp, VFS_node *node, quint16 port)
bool _needsToIssueRequest
A flag that means the request is asynchronous and will be satisfied after a VFS_request completes.
bool _done
If the _m_resp has finished, or was aborted or deleted, mark this as true.
Q_INVOKABLE VFS_httpd_browser(QString dataRootPath, quint16 port, QHostAddress addr=QHostAddress::Any, bool ssl=false, QString sslCertPath="", QString sslKeyPath="")
virtual void handle(QHttpRequest *req, QHttpResponse *resp)
Handle an incoming request.
VFS_node * dataRoot()
Resolve the _dataRootPath into a VFS_node.
VFS_node * _dataRoot
The node found by resolving _dataRootPath.
QString _dataRootPath
The VFS root path to serve from.
Holds the request and response objects for future use.
Definition: VFS_httpd.h:45
QHttpResponse * _m_resp
http response object
Definition: VFS_httpd.h:55
QUrl _url
requested url
Definition: VFS_httpd.h:57
virtual void accumulate(const QByteArray &)
This seems to be used during an http push request.
Definition: VFS_httpd.cpp:279
QString _path
path component of url
Definition: VFS_httpd.h:58
QHttpRequest * _m_req
http request object
Definition: VFS_httpd.h:54
virtual QByteArray errorPage()
The default error page.
Definition: VFS_httpd.cpp:296
QString _mime
mime type to send
Definition: VFS_httpd.h:59
int _status
http status
Definition: VFS_httpd.h:61
Creates httpd server node for VFS.
Definition: VFS_httpd.h:15
static QHash< QString, QString > MIME_TYPES
The mime type map.
Definition: VFS_httpd.h:24
VFS_node is the base class from which all other VFS_node classes derive.
Definition: VFS_node.h:143
virtual void issueRequest(VFS_request *t)
A convenience function.
Definition: VFS_node.cpp:1933
VFS_node * find(QString path)
Find a node by string path.
Definition: VFS_node.cpp:1100
QString className()
Return the class name of a node.
Definition: VFS_node.cpp:2039
The base class for all requests between nodes.
Definition: VFS_node.h:54
requestType
Requests perform one of these actions.
Definition: VFS_node.h:63
@ read
read full contents (4)
Definition: VFS_node.h:68
@ report
provide node report, for debugging (12)
Definition: VFS_node.h:76
@ ls
list children of a node (1)
Definition: VFS_node.h:65
static quint32 _sampleCount
A count of the number of VFS_requests since the last sample was taken.
Definition: VFS_node.h:123
QString _reason
if something (probably bad) happened, this is the reason
Definition: VFS_node.h:108
QString _path
the target path remnant... the remaining path element once the request has found its target
Definition: VFS_node.h:95
bool _success
if the request was successfully completed
Definition: VFS_node.h:107
QJsonDocument _data
the request payload
Definition: VFS_node.h:102
static long _refcount
A reference counter for VFS_request instances, used for debugging to ensure all instances are properl...
Definition: VFS_node.h:122
quint16 _port
TCP port to listen on, or 0 to find an available one.
static VFS * root()
Return the root node of the VFS filesystem.
Definition: VFS.cpp:399
static void LOG(QString message, int level=0, QString user="server")
Send a message to the VFS::_messages VFS_stream.
Definition: VFS.cpp:209
static void ERROR(QString message, int level=0, QString user="server")
Send a message to the VFS::_errors VFS_stream.
Definition: VFS.cpp:307
static QString uptimeString(bool ms=true)
Get the uptime of this VFS instance as a string.
Definition: VFS.cpp:443
static void WARN(QString message, int level=0, QString user="server")
Send a message to the VFS::_warnings VFS_stream.
Definition: VFS.cpp:258
static qint64 starttime()
Get the epoch start time of this VFS instance, in milliseconds.
Definition: VFS.cpp:422
static qint64 uptime()
Get the uptime of this VFS instance, in milliseconds.
Definition: VFS.cpp:432
static QJsonDocument parameters()
Fetch the parameters assigned to this VFS, as per the config file.
Definition: VFS.cpp:412
setter type
a setter DOCME
url(value, options)