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"
{
#undef Verbose
#define Verbose(X) AppVerbose("WWW", X)
static bool isNotOfInterestForWebVish(const VObject &VObj)
{
string ObjectName = VObj.Name();
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;
}
{
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;
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;
Inputs++;
RefPtr < ValuePool > Context;
RefPtr < VValueBase > Val = what -> getParameter() -> getValue(Context, "");
if (Val)
{
const string&objectName = VObj -> Name();
const string&objectIDName = ObjectIDName;
const auto&HTMLInputCreator = getHTMLInputCreatorFunctor( Val->getType() );
if (HTMLInputCreator)
html += HTMLInputCreator(Val, objectName, objectIDName, what, ExpertLevel);
#if 0
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 {
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<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")
{
}
else if (HTTP_GET_VARS[0] == "moveCamera=ZoomIn")
{
}
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 =
{
steerObject(*VObj, HTTP_GET_VARS);
}
else
{
Verbose(0) << "No such Vish object " << HTTP_PATH[1];
}
return {};
}
if ("create" == TopCommand)
{
if (HTTP_PATH.size() > 2)
if (RefPtr<VCreatorBase> VObjCreator =
{
createObject(*VObjCreator, HTTP_GET_VARS);
}
else
{
}
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];
}
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) : "";
auto ref_question_mark_pos = referer_url.find('?');
auto ref_last_slash_pos = referer_url.find_last_of('/');
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) : "";
if (ref_last_slash_pos != string::npos)
{
referer_url = referer_url.substr(ref_last_slash_pos);
}
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) : "";
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.";
}
question_mark_pos = url.find('?');
string param_string = url.substr(question_mark_pos + 1);
std::unordered_map<string, list<string>> params;
list<string> param_pairs;
split(param_string, param_pairs,
'&');
for (const auto &pair : param_pairs)
{
string varName = left_of(pair, '{');
string subParams = left_of(right_of(pair, '{'), '}');
split(subParams, params[varName],
',',
false);
}
string finishedPage = "<iframe srcdoc='";
string iFrameContent;
for (const auto ¶m : params)
{
const std::string &varName = param.first;
const std::list<std::string> &subParams = param.second;
if (VObj)
{
iFrameContent += "<div class=\"" + varName + "\">" + varName;
for (const auto &subParam : subParams)
{
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 replaceWith = "'";
size_t pos = 0;
while ((pos = output.find(
find, pos)) != string::npos)
{
output.replace(pos,
find.length(), replaceWith);
pos += replaceWith.length();
}
finishedPage += output;
finishedPage += "' style='width:100%; height:100%; border:none;'></iframe>";
return finishedPage;
}
return {};
}
{
std::unordered_map<string, string> HTTP_Request;
{
vector<string> HTTP_HEADER;
split(gotit, HTTP_HEADER,
'\n');
for (const auto &H : HTTP_HEADER)
{
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] << "]";
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 = RenderSource->getSource()->Name();
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
+ 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)
{
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;
[ViewerName, &FramebufferProvider](const RefPtr<VObject> &vobj,
const type_info &Type, const RefPtr<VSlot> &Slot)
{
if (vobj->Name() == ViewerName)
{
FramebufferProvider = Slot->getParameter();
return false;
}
return true;
});
if (!FramebufferProvider or N < 1)
return false;
attach(RenderSource->getParameter(), FramebufferProvider);
return true;
}
bool WebVish::update(VRequest &Context, double)
{
return true;
}
static OmegaRef<VCreator<WebVish>>
myCreator(
Category(
"Create") / VIdentifier(
"Webserver"), ObjectQuality::BETA);
}
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