The Vish Visualization Shell 0.3
Vish
WebVish.cpp

Demonstration how to implement a http protocol using the VISH socket callback mechanisms.

Demonstration how to implement a http protocol using the VISH socket callback mechanisms.

#include "WebVish.hpp"
#include <unordered_map>
#include <string>
#include <memory>
#include <numeric>
#include <ocean/plankton/VCreator.hpp>
#include <memcore/Verbose.hpp>
#include <ocean/streams/VStream.hpp>
#include <ocean/shrimp/VTime.hpp>
#include <memcore/PathOpen.hpp>
#include <unordered_map>
#include "fpng.h"
#include "HtmlInputCreator.hpp"
#include "ViewerCanvas.hpp"
namespace Wizt
{
#undef Verbose
#define Verbose(X) AppVerbose("WWW", X)
static bool isNotOfInterestForWebVish(const VObject &VObj)
{
string ObjectName = VObj.Name();
// rather than searching by name, go by input type:
/*
A VObject that provides a VFramebuffer as output is a
viewer; we don't need to display those in the Web GUI.
*/
if (VObj.getImplementation(typeid(VFrameBuffer)))
return true;
if (ObjectName.find("Viewer") != std::string::npos ||
ObjectName.find("qVishMainWindow") != std::string::npos ||
ObjectName.find("Bookmarks") != std::string::npos ||
ObjectName.find("DefaultScene") != std::string::npos)
return true;
return false;
}
struct NetworkObjectHtml : Wizt::VManagedObjectIterator
{
string pool_tab_header, pool_tab_content;
string net_html;
int RangeValueID = 1,
ObjectID = 0;
NetworkObjectHtml()
{
pool_tab_header = "<DIV CLASS=\"tab\">\n";
}
bool apply(int priority,
const RefPtr<VManagedObject> &V, const std::string &ModulePath) override
{
RefPtr<VObject> VObj = V;
if (!VObj)
return true;
string ObjectName = VObj->Name();
if (isNotOfInterestForWebVish(*VObj))
return true;
// Currently not showing object with stated names, later they should be displayed in a seperate menue and possibly sorted out in other ways
string ObjectIDName = std::to_string(ObjectID++);
string html = "<A NAME=\"" + ObjectIDName + "\" >\n";
html += " <TABLE class='object-table' BORDER=2><TR><TH class='object-table-header' COLSPAN=2>" + VObj->BaseName() + "</TH></TR>\n";
int Inputs = 0;
VObj->iterate_inputs([this, VObj, ObjectIDName, &Inputs, &html](const RefPtr<VSlot> &what, int ExpertLevel)
{
if (!what) return true;
if (!what -> getParameter()) return true;
if (ExpertLevel > 2) return true;
if (what -> inactive) return true;
if (what -> getClass() > 0) return true; // ignore GUI slots
Inputs++;
// NOTE: include the submit() function here in the oninput() if any action of the slider
// should be submitted immediately. This is only useful for really fast connections.
//string submit = "submit();";
//This function is directly implemented inside the createInputHtml() functions for using less input params. However this function could be serialized if needed.
// string submit = {};
// local context not supported yet
RefPtr < ValuePool > Context;
RefPtr < VValueBase > Val = what -> getParameter() -> getValue(Context, "");
if (Val)
{
// Common data needed for HTML creation
const string&objectName = VObj -> Name();
const string&objectIDName = ObjectIDName;
// const string&slotName = what -> SlotName();
/*
This call to the functor array replaces the if-battle .
*/
const auto&HTMLInputCreator = getHTMLInputCreatorFunctor( Val->getType() );
if (HTMLInputCreator)
html += HTMLInputCreator(Val, objectName, objectIDName, what, ExpertLevel);
#if 0
// Determine the type of Val and generate the appropriate HTML
if (RefPtr < VValue < double >> Float = Val) {
html += createSliderHtml(Val, objectName, objectIDName, what, ExpertLevel);
} else if (RefPtr < VValue < bool >> BoolVal = Val) {
html += createCheckboxHtml(Val, objectName, objectIDName, what, ExpertLevel);
} else if (RefPtr < VValue < string >> StringVal = Val) {
html += createInputHtml(Val, objectName, objectIDName, what, ExpertLevel);
} else if (RefPtr < VValue < std::vector < double >>> VecIntVal = Val) {
html += createNumberInputsHtml(Val, objectName, objectIDName, what, ExpertLevel);
} else {
// For other types, use a generic string representation and pass to createInputHtml
html += createInputHtml(Val, objectName, objectIDName, what, ExpertLevel);
}
#endif
}
return true; });
html += " </TABLE>\n";
html += "</A>\n";
if (Inputs > 0)
{
pool_tab_header +=
" <BUTTON class=\"tablinks\" onclick=\"showVFrame(event, '" + ObjectName + "')\">" + ObjectName + "</BUTTON>\n";
pool_tab_content += " <DIV ID=\"" + ObjectName + "\" class=tabcontent align=center>" + html + "</DIV>\n";
net_html += html;
}
return true;
}
void gather()
{
pool_tab_header += " </DIV CLASS=\"tab\">\n";
}
};
void WebVish::ChangeTime(const string &ViewerName, double howmuch)
{
RefPtr<VParameter> TimeParameter = VObject::findUniqueOutputObject(typeid(VTime),nullptr,true,nullptr);
RefPtr<VValueParameter<VTime>> TheTimes = TimeParameter;
if (!TheTimes)
return;
double Tmin = (*TheTimes)().Tmin(),
Tmax = (*TheTimes)().Tmax();
RefPtr<ValuePool> Context;
VTime Tnow;
TheTimes->getValue(Tnow, Context, {});
double t = Tnow();
t += howmuch * (Tmax - Tmin);
if (t < Tmin)
t = Tmin;
if (t > Tmax)
t = Tmax;
Tnow() = t;
TheTimes->setValue(Tnow, Context, "", false, NullPtr());
}
void WebVish::steerObject(VObject &VObj, const map<string, string> &HttpVariables)
{
RefPtr<ValuePool> Context;
for (const auto &var : HttpVariables)
{
if (RefPtr<VSlot> theSlotToBeModified = VObj.getSlot(var.first))
{
if (RefPtr<VValueBase> Val = theSlotToBeModified->getParameter()->getValue(Context, {}))
{
if (RefPtr<VValue<double>> Float = Val)
{
double Value = 0.0, Min = 0.0, Max = 1.0;
VValueTrait<double>::setValueFromText(Value, var.second);
theSlotToBeModified->getProperty("min", Min);
theSlotToBeModified->getProperty("max", Max);
double NewValue = Value / 1000.0 * (Max - Min) + Min;
Verbose(10) << " --> modifying " << VObj.Name() << " FLOAT ==> " << NewValue;
theSlotToBeModified->setValue(NewValue, Context);
continue;
}
}
Verbose(10) << " --> modifying " << VObj.Name() << " ==> " << var.first;
theSlotToBeModified->setValueFromText(var.second, Context);
}
else
{
Verbose(0) << " CANNOT modify " << VObj.Name() << " inexistent slot ==> " << var.first;
}
}
}
void WebVish::steerObject(VObject &VObj, const vector<string> &HTTP_GET_VARS)
{
Verbose(10) << " --> modifying object " << VObj.Name();
map<string, string> HttpVariables;
for (const auto &var : HTTP_GET_VARS)
{
string VarName = left_of(var, '=');
string VarValue = right_of(var, '=');
HttpVariables[VarName] = VarValue;
}
steerObject(VObj, HttpVariables);
}
void WebVish::steerViewer(const string &ViewerName, const vector<string> &HTTP_GET_VARS)
{
if (HTTP_GET_VARS.size() < 1)
return;
if (HTTP_GET_VARS[0] == "moveCamera=ZoomOut")
{
ZoomInOut(ViewerName, 0.5);
}
else if (HTTP_GET_VARS[0] == "moveCamera=ZoomIn")
{
ZoomInOut(ViewerName, -0.5);
}
else if (HTTP_GET_VARS[0] == "time=past")
{
ChangeTime(ViewerName, -0.01);
}
else if (HTTP_GET_VARS[0] == "time=future")
{
ChangeTime(ViewerName, 0.01);
}
}
string WebVish::httpGet(const string &HTTP) const
{
vector<string> HTTP_GET;
split(HTTP, HTTP_GET, '?', false, '\n');
vector<string> HTTP_PATH;
if (HTTP_GET.size() > 0)
split(HTTP_GET[0], HTTP_PATH, '/', false);
vector<string> HTTP_GET_VARS;
if (HTTP_GET.size() > 1)
split(HTTP_GET[1], HTTP_GET_VARS, '&', false);
if (HTTP_PATH.size() < 2)
return {};
const string &TopCommand = HTTP_PATH[0];
if ("objects" == TopCommand)
{
if (RefPtr<VObject> VObj =
VObject::find(HTTP_PATH[1]))
{
steerObject(*VObj, HTTP_GET_VARS);
}
else
{
Verbose(0) << "No such Vish object " << HTTP_PATH[1];
}
return {};
}
if ("create" == TopCommand)
{
string CreatorName = HTTP_PATH[1];
if (HTTP_PATH.size() > 2)
CreatorName += "/" + HTTP_PATH[2];
if (RefPtr<VCreatorBase> VObjCreator =
{
createObject(*VObjCreator, HTTP_GET_VARS);
}
else
{
Verbose(0) << "No such Vish Creator " << CreatorName;
}
return {};
}
if ("Viewer" == TopCommand)
{
steerViewer(HTTP_PATH[1], HTTP_GET_VARS);
}
else
{
Verbose(0) << "Unknown HTTP path " << HTTP_PATH[0];
if (HTTP_GET_VARS.size() > 0)
Verbose(0) << "Unknown HTTP variable " << HTTP_GET_VARS[0];
}
//
// Todo: invoke a callback that has been registered
// for a given TopCommand
//
// TopCommand
return {};
}
string WebVish::viewerHtml(const string &ViewerName)
{
return Canvas(ViewerName, CurrentViewerImage[ViewerName] );
}
bool is(const std::unordered_map<string, string> &HTTP_Request,
const string &Value, const string &Key)
{
const auto &has = HTTP_Request.find(Value);
if (has == HTTP_Request.end())
return false;
return has->second == Key;
}
string WebVish::handleURL(string &url, string &referer_url)
{
auto question_mark_pos = url.find('?');
auto last_slash_pos = url.find_last_of('/');
string command = (last_slash_pos != string::npos && question_mark_pos != string::npos) ? url.substr(last_slash_pos + 1, question_mark_pos - last_slash_pos - 1) : "";
// Find positions in the referer URL
auto ref_question_mark_pos = referer_url.find('?');
auto ref_last_slash_pos = referer_url.find_last_of('/');
// Extract the path and parameters from referer_url
string ref_path = (ref_last_slash_pos != string::npos) ? referer_url.substr(ref_last_slash_pos + 1) : referer_url;
string ref_params = (ref_question_mark_pos != string::npos) ? referer_url.substr(ref_question_mark_pos + 1) : "";
// Remove everything before ref_last_slash_pos in the referer URL
if (ref_last_slash_pos != string::npos)
{
referer_url = referer_url.substr(ref_last_slash_pos);
}
// Extract the command from the updated referer URL
string refCommand = (ref_last_slash_pos != string::npos && ref_question_mark_pos != string::npos) ? referer_url.substr(1, ref_question_mark_pos - ref_last_slash_pos - 1) : "";
// AppVerbose("DBG", 0) << "Refferer Command: " << refCommand;
// Check if the command or refferer command is 'param'
// usage: /param?Camera{zoom,speed}&Background{red}
if ("param" == command || "param" == refCommand)
{
if ("param" == refCommand)
{
command = refCommand;
url = referer_url;
}
AppVerbose("DBG", 0) << "URL: " << url;
if (question_mark_pos == string::npos && command == "param")
{
return "No parameters found.";
}
// Extract the parameter string (everything after '?')
question_mark_pos = url.find('?');
string param_string = url.substr(question_mark_pos + 1);
// AppVerbose("DBG", 0)<< "Param string: " << param_string;
std::unordered_map<string, list<string>> params;
// Split parameters by '&'
list<string> param_pairs;
split(param_string, param_pairs, '&');
// Parse the parameters
for (const auto &pair : param_pairs)
{
string varName = left_of(pair, '{');
// AppVerbose("DBG", 0) << varName; // Get the variable name (Camera, Background)
string subParams = left_of(right_of(pair, '{'), '}');
// AppVerbose("DBG", 0) << subParams; // Get the sub-parameters
split(subParams, params[varName], ',', false); // Split the sub-parameters
}
string finishedPage = "<iframe srcdoc='";
string iFrameContent;
for (const auto &param : params)
{
const std::string &varName = param.first;
const std::list<std::string> &subParams = param.second;
RefPtr<VObject> VObj = VObject::find(varName);
// AppVerbose("DBG", 0) << "Object Name: " << varName;
if (VObj)
{
iFrameContent += "<div class=\"" + varName + "\">" + varName;
for (const auto &subParam : subParams)
{
// local context not supported yet
if (RefPtr<VSlot> slot = VObj->getSlot(subParam))
{
RefPtr<ValuePool> Context;
RefPtr<VValueBase> Val = slot->getParameter()->getValue(Context, {});
if (Val)
{
const auto &HTMLInputCreator = getHTMLInputCreatorFunctor(Val->getType());
if (HTMLInputCreator)
iFrameContent += HTMLInputCreator(Val, varName, "0", slot, 0);
}
}
}
iFrameContent += "</div>";
}
}
string output = iFrameContent;
string find = "'";
string replaceWith = "&#39;";
// Start from the beginning of the string
size_t pos = 0;
while ((pos = output.find(find, pos)) != string::npos)
{
// Replace the found character and move past the replacement
output.replace(pos, find.length(), replaceWith);
pos += replaceWith.length();
}
finishedPage += output;
//This disables redirects
//finishedPage += "' sandbox='allow-forms allow-scripts' style='width:100%; height:100%; border:none;'></iframe>";
finishedPage += "' style='width:100%; height:100%; border:none;'></iframe>";
return finishedPage;
}
return {};
}
string WebVish::communicate(const string &gotit, socket_t id)
{
/*
GET / HTTP/1.1
Host: localhost:7000
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,* / *;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cache-Control: max-age=0
Sec-Fetch-Dest: iframe
Sec-Fetch-Mode: same-origin
Sec-Fetch-Mode: navigate
*/
// Verbose(5) << "FULL HTTP COMMAND " << gotit;
std::unordered_map<string, string> HTTP_Request;
{
vector<string> HTTP_HEADER;
split(gotit, HTTP_HEADER, '\n');
for (const auto &H : HTTP_HEADER)
{
auto [Key, Value] = split_at_first(H, ' ');
auto _n = Value.length();
if (_n > 0 and Value[_n - 1] == '\r')
Value = Value.substr(0, _n - 1);
HTTP_Request[Key] = Value;
}
}
for (const auto &H : HTTP_Request)
{
Verbose(5) << "HTTP: [" << H.first << "] ---> [" << H.second << "]";
}
vector<string> HTTP_PATH;
auto HTTP_GET = HTTP_Request.find("GET");
bool GET = HTTP_GET != HTTP_Request.end();
if (GET)
{
split(HTTP_GET->second, HTTP_PATH, ' ', false, '\n');
}
if (HTTP_PATH.size() > 0)
{
if ("/favicon.ico" == HTTP_PATH[0])
return {};
}
vector<string> referer_url;
auto HTTP_REFERER = HTTP_Request.find("Referer:");
bool REF = HTTP_REFERER != HTTP_Request.end();
if (REF)
{
split(HTTP_REFERER->second, referer_url, ' ', false, '\n');
}
if (referer_url.empty())
{
referer_url = {"null"};
}
if (HTTP_PATH.size() > 0)
{
Verbose(5) << "HTTP GET --> [" << HTTP_PATH[0] << "]";
// AppVerbose("DBG", 0) << "Request: " << gotit;
string result = handleURL(HTTP_PATH[0], referer_url[0]);
if (!result.empty())
return result;
if (GET)
{
result = httpGet(HTTP_PATH[0]);
if (!result.empty())
return result;
}
}
// string ViewerName = "MonoViewer";
string ViewerName = RenderSource->getSource()->Name();
updateViewerImage(ViewerName, 1024, 768);
/* if (gotit.contains("Sec-Fetch-Site: same-origin")
)
{
return viewerHtml(ViewerName);
}
*/
if (is(HTTP_Request, "Sec-Fetch-Dest:", "iframe"))
{
return viewerHtml(ViewerName);
}
NetworkObjectHtml ObjectPool;
ObjectPool.gather();
static constinit const char WebVish_Header_html[] = {
#embed "WebVish_Header.html"
};
return string(WebVish_Header_html, sizeof(WebVish_Header_html)) +
R"HTML(
<BODY onload = "load()">
<div id="mySidebar" class="sidebar">
<a href="javascript:void(0)" class="closebtn" onclick="toggleNav()">×</a>
<div id="searchBar">
<input style="margin-left: 8px; padding: 8px;" type="text" id="searchInput" onkeyup="searchForms()" placeholder="Search for objects...">
</div>
)HTML" + CreationHTML() +
R"HTML(
</div>
<div id="main">
<button class="openbtn" onclick="toggleNav()">☰ Object Creation</button>
<button onclick="toggleMode()">Change Theme</button>)HTML" +
R"HTML(</DIV>
<H2 ALIGN=CENTER><A HREF=/>WebVISH Control Suite</A></H2>
<DIV class=row>
<DIV class=col>
<DIV class=scroll>
)HTML" + ObjectPool.pool_tab_header +
ObjectPool.pool_tab_content
// + ObjectPool.net_html
+ R"HTML(
</DIV class=scroll>
</DIV>
<DIV class="col" style="padding: 0px;">
)HTML" + viewerHtml(ViewerName) + R"HTML(
</DIV class=col>
</DIV class=row>
</DIV>
</BODY>
</HTML>
)HTML";
}
WebVish::WebVish(const string &name, int p, const RefPtr<VCreationPreferences> &VP)
: HTTPServer(7007, name, p, VP), StatusIndicator(this, "Web Server is up and running at <A HREF=\"http://localhost:7007/\">localhost</A>. "
"Use the full hostname or IP address when accessing from another location."),
RenderSource(this, "viewer", VFrameBuffer(), 5)
{
/*
Connect to a viewer with the given name, assuming that one is the
primary viewer to be used for the web context.
If this viewer does not exist, screengrabbing will fail.
Another viewer can be selected in the GUI at expert level 5 .
TODO: Probably we want some failsafe fallback here such to use
any other viewer if the specified one is not available,
starting with a list of possible viewers.
*/
attachToViewer("MonoViewer");
if (RefPtr<VObject> vobj = RenderSource->getSource() )
{
Verbose(0) << "WebVish is connected to " << vobj->Name();
}
fpng::fpng_init();
}
bool WebVish::attachToViewer(const string &ViewerName)
{
RefPtr<VParameter> FramebufferProvider;
/*
Iterate over all objects that provide framebuffers.
These are the various viewers.
This iteration could also be used to expose a list of
possible viewers in the web interface.
*/
int N = findOutputObjects(RenderSource->getType(),
[ViewerName, &FramebufferProvider](const RefPtr<VObject> &vobj,
const type_info &Type, const RefPtr<VSlot> &Slot)
{
if (vobj->Name() == ViewerName)
{
FramebufferProvider = Slot->getParameter();
return false; // stop iteration
}
return true; // continue iteration
});
if (!FramebufferProvider or N < 1)
return false;
attach(RenderSource->getParameter(), FramebufferProvider);
return true;
}
bool WebVish::update(VRequest &Context, double)
{
// TODO: set object status with accumulated error message
// but must also set the WebVish as self-scheduling Sink
// such that update is called at all.
return true;
}
using namespace ProtoOcean;
static OmegaRef<VCreator<WebVish>>
myCreator(Category("Create") / VIdentifier("Webserver"), ObjectQuality::BETA);
} // namespace Wizt
static RefPtr< VManagedObject > find(const string &s)
Find an object with a certain name.
Definition VManagedObject.hpp:328
Abstract iterator class for iterating over a Domain of objects.
Definition VManagedObject.hpp:63
static int traverse(const type_info &, VManagedObjectIterator &VIt, int p_start, int p_end)
Iterate through a domain for certain levels.
Definition VManagedObject.cpp:701
static VManagedObjectPtr find(const type_info &, const std::string &s)
Find a certain managed object by its name.
Definition VManagedObject.cpp:595
string CreatorName() const
Retrieve the name of the object's creator.
Definition VObject.cpp:71
static RefPtr< VParameter > findUniqueOutputObject(const type_info &Type, const RefPtr< VCreationPreferences > &VCP, bool ReallyUnique, const VObject *NotThisOne)
Find an existing VObject that implements the specified type, returning exactly that parameter which i...
Definition VObject.cpp:1037
static int findOutputObjects(const type_info &Type, OutputObjectIterator &GOutputs)
Find all objects that implement a certain output type.
Definition VObject.cpp:938
static void ZoomInOut(const string &ViewerName, double howmuch)
A routine that zooms the camera associated with Viewer "Viewer1" by a certain amount.
Definition webVishViewer.cpp:178
void updateViewerImage(const string &ViewerName, unsigned Width, unsigned Height)
Write a snapshot to the given socket connection.
Definition webVishViewer.cpp:59
string communicate(const string &gotit, socket_t id)
Parsing an HTTP request and returning the favor by sending something as HTML (or different mime-type)...
Definition WebVish.cpp:477
size_t split(std::basic_string< E > const &s, C &container, E const delimiter, bool keepBlankFields=true, E const terminator=0)
Splitting a string using a given delimiter.
Definition stringutil.hpp:115
std::tuple< std::basic_string< E >, std::basic_string< E > > split_at_first(std::basic_string< E > const &s, const E c)
Split a string into two at first occurance of character 'c'.
Definition stringutil.hpp:392
std::nullptr_t NullPtr
A type indicating an invalid pointer, similar to the NULL pointer in C, but type-safe.
Definition DynPtr.hpp:368
StrongPtr< Object, ObjectBase > RefPtr
Convenience template typedef to use RefPtr instead of StrongPtr.
Definition RefPtr.hpp:776
The Panthalassa namespace allows to conveniently specify the properties of a VCreator object during c...
Definition VCreatorProperties.hpp:385
Wizt::VCreatorProperty< Wizt::VCreatorProperties::CATEGORY > Category
Classification, such as Display or Computer or Demo.
Definition VCreatorProperties.hpp:390
The Vish namespace.
Definition Anemone.cpp:17