1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2025-05-10 04:57:13 +02:00
Sandu Liviu Catalin 4a6bfc086c Major plugin refactor and cleanup.
Switched to POCO library for unified platform/library interface.
Deprecated the external module API. It was creating more problems than solving.
Removed most built-in libraries in favor of system libraries for easier maintenance.
Cleaned and secured code with help from static analyzers.
2021-01-30 08:51:39 +02:00

392 lines
9.6 KiB
C++

//
// WebNotifier.cpp
//
// This sample demonstrates a combination of Data and Net libraries by
// creating a database, registering callbacks for insert/update events
// and sending database modifications to the web client through web socket.
// Since callbacks are only registered for session, in order to see the
// effects, database updates should be done through the shell provided by
// this example.
//
// This is only a demo. For production-grade a better web socket management
// facility as well as persisting notification functionality (e.g. via
// triggers and external functions) should be used.
//
// Copyright (c) 2008, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
#include "Poco/Delegate.h"
#include "Poco/Timespan.h"
#include "Poco/Exception.h"
#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/NetException.h"
#include "Poco/Data/Session.h"
#include "Poco/Data/RowFormatter.h"
#include "Poco/Data/RecordSet.h"
#include "Poco/Data/SQLite/Notifier.h"
#include "Poco/Data/SQLite/Connector.h"
#include <iostream>
using Poco::delegate;
using Poco::Timespan;
using Poco::Exception;
using Poco::NullPointerException;
using Poco::Net::ServerSocket;
using Poco::Net::WebSocket;
using Poco::Net::WebSocketException;
using Poco::Net::HTTPRequestHandler;
using Poco::Net::HTTPRequestHandlerFactory;
using Poco::Net::HTTPServer;
using Poco::Net::HTTPServerRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::HTTPServerResponse;
using Poco::Net::HTTPServerParams;
using namespace Poco::Data::Keywords;
using Poco::Data::Session;
using Poco::Data::Statement;
using Poco::Data::RowFormatter;
using Poco::Data::RecordSet;
using Poco::Data::SQLite::Notifier;
#define PROMPT "sql>"
class PageRequestHandler: public HTTPRequestHandler
/// Return a HTML document with some JavaScript creating
/// a WebSocket connection.
{
public:
void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response)
{
response.setChunkedTransferEncoding(true);
response.sendFile("WebNotifier.html", "text/html");
}
};
/////////////////
// WebSocket //
/////////////////
class WebSocketRequestHandler: public HTTPRequestHandler
/// Handler for the WebSocket connection.
{
public:
WebSocketRequestHandler() : _pWS(0), _flags(0)
{
}
~WebSocketRequestHandler()
{
shutdown();
}
void shutdown()
{
if (_pWS)
{
_pWS->shutdown();
delete _pWS;
}
}
void send(const std::string& buffer)
/// Pushes data to web client.
{
std::cout << "Sending data: " << buffer << std::endl;
_pWS->sendFrame(buffer.data(), (int) buffer.size(), _flags);
}
void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response)
/// Creates WebSocket and accepts the connection request from web client.
{
try
{
if (!_pWS)
{
_pWS = new WebSocket(request, response);
Timespan ts(600, 0);
_pWS->setReceiveTimeout(ts);
_pWS->setSendTimeout(ts);
}
std::cout << std::endl << "WebSocket connection established." << std::endl << PROMPT;
char buffer[1024];
int n;
do
{
n = _pWS->receiveFrame(buffer, sizeof(buffer), _flags);
}
while (n > 0 || (_flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
std::cout << "WebSocket connection closed." << std::endl;
}
catch (WebSocketException& exc)
{
std::cout << exc.displayText() << std::endl;
switch (exc.code())
{
case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
response.set("Sec-WebSocket-Version", WebSocket::WEBSOCKET_VERSION);
// fallthrough
case WebSocket::WS_ERR_NO_HANDSHAKE:
case WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
case WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
response.setContentLength(0);
response.send();
break;
}
}
}
public:
WebSocket* _pWS;
int _flags;
};
class RequestHandlerFactory: public HTTPRequestHandlerFactory
/// Web request handler factory.
{
public:
RequestHandlerFactory() : _pHandler(0)
{
}
HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request)
{
std::string uri = request.getURI();
if (uri == "/")
{
return new PageRequestHandler;
}
else if (uri == "/ws")
{
if (!_pHandler) _pHandler = new WebSocketRequestHandler;
return _pHandler;
}
if (uri != "/favicon.ico")
std::cout << "Unknown URI: " << uri << std::endl;
return 0;
}
WebSocketRequestHandler& handler()
{
if (!_pHandler) throw NullPointerException("WebSocket not connected.");
return *_pHandler;
}
private:
WebSocketRequestHandler* _pHandler;
};
////////////////
// Database //
////////////////
class CSVFormatter : public RowFormatter
/// Formatter, passed to DB statement.
{
public:
std::string& formatValues(const ValueVec& vals, std::string& formattedValues)
/// Formats the result into comma separated list of values.
{
std::ostringstream str;
ValueVec::const_iterator it = vals.begin();
ValueVec::const_iterator end = vals.end();
for (; it != end;)
{
str << it->convert<std::string>();
if (++it != end) str << ',';
else break;
}
return formattedValues = str.str();
}
};
class DBEventHandler
/// Handler for DB insert/update events.
{
public:
DBEventHandler(RequestHandlerFactory& factory):
_session("SQLite", "sample.db"),
_factory(factory),
_notifier(_session)
/// Constructor; opens/initializes the database and associates
/// notification events with their respective handlers.
{
initDB();
_notifier.insert += delegate(this, &DBEventHandler::onInsert);
_notifier.update += delegate(this, &DBEventHandler::onUpdate);
}
~DBEventHandler()
/// Destructor; unregisters the notification events.
{
_notifier.insert -= delegate(this, &DBEventHandler::onInsert);
_notifier.update -= delegate(this, &DBEventHandler::onUpdate);
}
std::size_t execute(const std::string& sql)
/// Exectutes the SQL statement.
{
Statement stmt = (_session << sql);
return stmt.execute();
}
Session& session()
{
return _session;
}
private:
void initDB()
{
_session << "DROP TABLE IF EXISTS Person", now;
_session << "CREATE TABLE Person (Name VARCHAR(30), Address VARCHAR, Age INTEGER(3))", now;
}
Notifier* notifier(const void* pSender)
{
return reinterpret_cast<Notifier*>(const_cast<void*>(pSender));
}
void notify(Poco::Int64 rowID)
/// Executes the query and sends the data to the web client.
{
std::ostringstream os;
CSVFormatter cf;
Statement stmt = (_session << "SELECT rowid, Name, Address, Age FROM Person WHERE rowid = ?", use(rowID), format(cf), now);
os << RecordSet(stmt);
_factory.handler().send(os.str());
}
void onInsert(const void* pSender)
/// Insert event handler; retrieves the data for the affected row
/// and calls notify.
{
Notifier* pN = notifier(pSender);
Poco::Int64 rowID = pN->getRow();
std::cout << "Inserted row " << rowID << std::endl;
notify(rowID);
}
void onUpdate(const void* pSender)
/// Update event handler; retrieves the data for the affected row
/// and calls notify.
{
Notifier* pN = notifier(pSender);
Poco::Int64 rowID = pN->getRow();
std::cout << "Updated row " << rowID << std::endl;
notify(rowID);
}
Session _session;
RequestHandlerFactory& _factory;
Notifier _notifier;
};
void doHelp()
/// Displays help.
{
std::cout << "Poco Data/Net example - HTML Page notifications from DB events" << std::endl;
std::cout << "" << std::endl;
std::cout << "To observe the functionality, take following steps:" << std::endl;
std::cout << "" << std::endl;
std::cout << "1) Run a web browser and connect to http://localhost:9980 ." << std::endl;
std::cout << "2) Wait until \"WebSocket connection established.\" is displayed." << std::endl;
std::cout << "3) Issue SQL commands to see the web page updated, e.g.:" << std::endl;
std::cout << "\tINSERT INTO Person VALUES('Homer Simpson', 'Springfield', 42);" << std::endl;
std::cout << "\tINSERT INTO Person VALUES('bart Simpson', 'Springfield', 12);" << std::endl;
std::cout << "\tUPDATE Person SET Age=38 WHERE Name='Homer Simpson';" << std::endl;
std::cout << "\tUPDATE Person SET Name='Bart Simpson' WHERE Name='bart Simpson';" << std::endl;
std::cout << "" << std::endl;
std::cout << "To end the program, enter \"exit\"." << std::endl;
std::cout << "" << std::endl;
std::cout << "To view this help, enter \"help\" or \"?\"." << std::endl;
}
void doShell(DBEventHandler& dbEventHandler)
/// Displays the shell and dispatches commands.
{
doHelp();
while (true)
{
std::cout << PROMPT;
char cmd[512] = {0};
std::cin.getline(cmd, 512);
if (strncmp(cmd, "exit", 4) == 0)
break;
try
{
if ((strncmp(cmd, "help", 4) == 0) || cmd[0] == '?')
doHelp();
if (strlen(cmd) > 0)
{
std::size_t rows = dbEventHandler.execute(cmd);
std::cout << rows << " row" << ((rows != 1) ? "s" : "") << " affected." << std::endl;
}
}
catch(Exception& ex)
{
std::cout << ex.displayText() << std::endl;
}
}
}
///////////
// Main //
///////////
int main(int argc, char** argv)
{
// register SQLite connector
Poco::Data::SQLite::Connector::registerConnector();
// HTTPServer instance
RequestHandlerFactory* pFactory = new RequestHandlerFactory;
HTTPServer srv(pFactory, 9980);
// DB stuff
DBEventHandler dbEventHandler(*pFactory);
// Start the HTTPServer
srv.start();
// Run shell
doShell(dbEventHandler);
// Stop the HTTPServer
srv.stop();
return 0;
}