mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2025-05-10 04:57:13 +02:00
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.
392 lines
9.6 KiB
C++
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;
|
|
}
|