Commit de9ac27b authored by Pavel Vainerman's avatar Pavel Vainerman

(LogDB): начало работы на REST API. Накидал скелет релизации,

а также функцию получения списка доступных логов (/list).
parent 8299f380
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<LogDB name="LogDB"> <LogDB name="LogDB">
<logserver name="logserver1" ip="localhost" port="3333" cmd="-s level1"/> <logserver name="logserver1" ip="localhost" port="3333" cmd="-s level1"/>
<!-- <logserver name="logserver2" ip="localhost" port="3333" cmd=""/> --> <logserver name="logserver2" ip="localhost" port="3333" cmd=""/>
</LogDB> </LogDB>
<settings> <settings>
......
...@@ -195,7 +195,7 @@ void LogDB::flushBuffer() ...@@ -195,7 +195,7 @@ void LogDB::flushBuffer()
if( !db->insert(qbuf.front()) ) if( !db->insert(qbuf.front()) )
{ {
dbcrit << myname << "(flushBuffer): error: " << db->error() << dbcrit << myname << "(flushBuffer): error: " << db->error() <<
" lost query: " << qbuf.front() << endl; " lost query: " << qbuf.front() << endl;
} }
qbuf.pop(); qbuf.pop();
...@@ -208,7 +208,7 @@ void LogDB::addLog( LogDB::Log* log, const string& txt ) ...@@ -208,7 +208,7 @@ void LogDB::addLog( LogDB::Log* log, const string& txt )
ostringstream q; ostringstream q;
q << "INSERT INTO log(tms,usec,name,text) VALUES('" q << "INSERT INTO logs(tms,usec,name,text) VALUES('"
<< tm.tv_sec << "','" // timestamp << tm.tv_sec << "','" // timestamp
<< tm.tv_nsec << "','" // usec << tm.tv_nsec << "','" // usec
<< log->name << "','" << log->name << "','"
...@@ -493,7 +493,7 @@ void LogDB::Log::close() ...@@ -493,7 +493,7 @@ void LogDB::Log::close()
#ifndef DISABLE_REST_API #ifndef DISABLE_REST_API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
class LogDBRequestHandler: class LogDBRequestHandler:
public Poco::Net::HTTPRequestHandler public Poco::Net::HTTPRequestHandler
{ {
public: public:
...@@ -516,138 +516,149 @@ Poco::Net::HTTPRequestHandler* LogDB::createRequestHandler( const Poco::Net::HTT ...@@ -516,138 +516,149 @@ Poco::Net::HTTPRequestHandler* LogDB::createRequestHandler( const Poco::Net::HTT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void LogDB::handleRequest( Poco::Net::HTTPServerRequest& req, Poco::Net::HTTPServerResponse& resp ) void LogDB::handleRequest( Poco::Net::HTTPServerRequest& req, Poco::Net::HTTPServerResponse& resp )
{ {
dbinfo << myname << "(handleRequest): ...." << endl;
using Poco::Net::HTTPResponse; using Poco::Net::HTTPResponse;
// std::ostream& out = resp.send(); std::ostream& out = resp.send();
resp.setContentType("text/json");
try try
{ {
respError(resp, HTTPResponse::HTTP_INTERNAL_SERVER_ERROR, "Unknown request"); // В этой версии API поддерживается только GET
} if( req.getMethod() != "GET" )
catch( std::exception& ex ) {
{ auto jdata = respError(resp, HTTPResponse::HTTP_BAD_REQUEST,"method must be 'GET'");
dbcrit << myname << "(handleRequest): request error: " << ex.what() << endl; jdata->stringify(out);
respError(resp, HTTPResponse::HTTP_INTERNAL_SERVER_ERROR, ex.what()); out.flush();
} return;
}
#if 0
// В этой версии API поддерживается только GET
if( req.getMethod() != "GET" )
{
resp.setStatus(HTTPResponse::HTTP_BAD_REQUEST);
resp.setContentType("text/json");
std::ostream& out = resp.send();
Poco::JSON::Object jdata;
jdata.set("error", resp.getReasonForStatus(resp.getStatus()));
jdata.set("ecode", (int)resp.getStatus());
jdata.set("message", "method must be 'GET'");
jdata.stringify(out);
out.flush();
return;
}
Poco::URI uri(req.getURI()); Poco::URI uri(req.getURI());
if( log->is_info() ) dblog3 << req.getHost() << ": query: " << uri.getQuery() << endl;
log->info() << req.getHost() << ": query: " << uri.getQuery() << endl;
std::vector<std::string> seg; std::vector<std::string> seg;
uri.getPathSegments(seg); uri.getPathSegments(seg);
// example: http://host:port/api/version/ObjectName // example: http://host:port/api/version/logdb/..
if( seg.size() < 3 if( seg.size() < 4
|| seg[0] != "api" || seg[0] != "api"
|| seg[1] != UHTTP_API_VERSION || seg[1] != uniset::UHttp::UHTTP_API_VERSION
|| seg[2].empty() ) || seg[2].empty()
{ || seg[2] != "logdb")
resp.setStatus(HTTPResponse::HTTP_BAD_REQUEST); {
resp.setContentType("text/json"); ostringstream err;
std::ostream& out = resp.send(); err << "Bad request structure. Must be /api/" << uniset::UHttp::UHTTP_API_VERSION << "/logdb/xxx";
Poco::JSON::Object jdata; auto jdata = respError(resp, HTTPResponse::HTTP_BAD_REQUEST,err.str());
jdata.set("error", resp.getReasonForStatus(resp.getStatus())); jdata->stringify(out);
jdata.set("ecode", (int)resp.getStatus()); out.flush();
jdata.set("message", "BAD REQUEST STRUCTURE"); return;
jdata.stringify(out); }
out.flush();
return;
}
const std::string objectName(seg[2]); auto qp = uri.getQueryParameters();
auto qp = uri.getQueryParameters();
resp.setStatus(HTTPResponse::HTTP_OK); resp.setStatus(HTTPResponse::HTTP_OK);
resp.setContentType("text/json"); string cmd = seg[3];
std::ostream& out = resp.send();
try if( cmd == "help" )
{
if( objectName == "help" )
{ {
out << "{ \"help\": [" out << "{ \"help\": ["
"{\"help\": {\"desc\": \"this help\"}}," "{\"help\": {\"desc\": \"this help\"}},"
"{\"list\": {\"desc\": \"list of objects\"}}," "{\"list\": {\"desc\": \"list of logs\"}},"
"{\"ObjectName\": {\"desc\": \"ObjectName information\"}}," "{\"read?logname&offset=N&limit=M\": {\"desc\": \"read logs\"}},"
"{\"ObjectName/help\": {\"desc\": \"help for ObjectName\"}}," "{\"apidocs\": {\"desc\": \"https://github.com/Etersoft/uniset2\"}}"
"{\"apidocs\": {\"desc\": \"https://github.com/Etersoft/uniset2\"}}" "]}";
"]}";
}
else if( objectName == "list" )
{
auto json = registry->httpGetObjectsList(qp);
json->stringify(out);
}
else if( seg.size() == 4 && seg[3] == "help" ) // /api/version/ObjectName/help
{
auto json = registry->httpHelpByName(objectName, qp);
json->stringify(out);
}
else if( seg.size() >= 4 ) // /api/version/ObjectName/xxx..
{
auto json = registry->httpRequestByName(objectName, seg[3], qp);
json->stringify(out);
} }
else else
{ {
auto json = registry->httpGetByName(objectName, qp); auto json = httpGetRequest(cmd, qp);
json->stringify(out); json->stringify(out);
} }
} }
// catch( Poco::JSON::JSONException jsone )
// {
// std::cout << "JSON ERROR: " << jsone.message() << std::endl;
// }
catch( std::exception& ex ) catch( std::exception& ex )
{ {
ostringstream err; auto jdata = respError(resp, HTTPResponse::HTTP_INTERNAL_SERVER_ERROR,ex.what());
err << ex.what(); jdata->stringify(out);
resp.setStatus(HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
resp.setContentType("text/json");
Poco::JSON::Object jdata;
jdata.set("error", err.str());
jdata.set("ecode", (int)resp.getStatus());
jdata.stringify(out);
} }
out.flush(); out.flush();
#endif
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void LogDB::respError( Poco::Net::HTTPServerResponse& resp, Poco::JSON::Object::Ptr LogDB::respError( Poco::Net::HTTPServerResponse& resp,
Poco::Net::HTTPResponse::HTTPStatus estatus, Poco::Net::HTTPResponse::HTTPStatus estatus,
const string& message ) const string& message )
{ {
resp.setStatus(estatus); resp.setStatus(estatus);
resp.setContentType("text/json"); resp.setContentType("text/json");
std::ostream& out = resp.send();
Poco::JSON::Object::Ptr jdata = new Poco::JSON::Object(); Poco::JSON::Object::Ptr jdata = new Poco::JSON::Object();
jdata->set("error", resp.getReasonForStatus(resp.getStatus())); jdata->set("error", resp.getReasonForStatus(resp.getStatus()));
jdata->set("ecode", (int)resp.getStatus()); jdata->set("ecode", (int)resp.getStatus());
jdata->set("message", message); jdata->set("message", message);
jdata->stringify(out); return jdata;
out.flush(); }
// -----------------------------------------------------------------------------
Poco::JSON::Object::Ptr LogDB::httpGetRequest( const string& cmd, const Poco::URI::QueryParameters& p )
{
if( cmd == "list" )
return httpGetList(p);
ostringstream err;
err << "Unknown command '" << cmd << "'";
throw uniset::SystemError(err.str());
}
// -----------------------------------------------------------------------------
Poco::JSON::Object::Ptr LogDB::httpGetList( const Poco::URI::QueryParameters& p )
{
if( !db )
{
ostringstream err;
err << "DB unavailable..";
throw uniset::SystemError(err.str());
}
Poco::JSON::Object::Ptr jdata = new Poco::JSON::Object();
Poco::JSON::Array::Ptr jlist = uniset::json::make_child_array(jdata, "logs");
#if 0
// Получение из БД
// хорошо тем, что возвращаем список реально доступных логов (т.е. тех что есть в БД)
// плохо тем, что если в конфигурации добавили какие-то логи, но в БД
// ещё ничего не попало, мы их не увидим
ostringstream q;
q << "SELECT COUNT(*), name FROM logs GROUP BY name";
DBResult ret = db->query(q.str());
if( !ret )
return jdata;
for( auto it = ret.begin(); it!=ret.end(); ++it )
{
Poco::JSON::Object::Ptr j = new Poco::JSON::Object();
j->set("name", it.as_string("name"));
jlist->add(j);
}
#else
// Получение из конфигурации
// хорошо тем, что если логов ещё не было
// то всё-равно видно, какие доступны потенциально
// плохо тем, что если конфигурацию поменяли (убрали какой-то лог)
// а в БД записи по нему остались, то мы не получим к ним доступ
/*! \todo пока список logservers формируется только в начале (в конструкторе)
* можно не защищаться mutex-ом, т.к. мы его не меняем
* если вдруг в REST API будет возможность добавлять логи.. нужно защищаться
* либо переделывать обработку
*/
for( const auto& s: logservers )
{
Poco::JSON::Object::Ptr j = new Poco::JSON::Object();
j->set("name", s->name);
jlist->add(j);
}
#endif
return jdata;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#endif #endif
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include <chrono> #include <chrono>
#include <ev++.h> #include <ev++.h>
#include <sigc++/sigc++.h> #include <sigc++/sigc++.h>
#include <Poco/JSON/Object.h>
#include "UniSetTypes.h" #include "UniSetTypes.h"
#include "LogAgregator.h" #include "LogAgregator.h"
#include "DebugStream.h" #include "DebugStream.h"
...@@ -68,12 +69,22 @@ namespace uniset ...@@ -68,12 +69,22 @@ namespace uniset
http-сервер. Параметры запуска можно указать при помощи: http-сервер. Параметры запуска можно указать при помощи:
--prefix-httpserver-host и --prefix-httpserver-port. --prefix-httpserver-host и --prefix-httpserver-port.
Запросы принимаются по: api/version/logdb/...
\todo Добавить настройки таймаутов, размера буфера, размера для резервирования под строку,... /help - Получение списка доступных команд
\todo Реализовать посылку команд /list - список доступных логов
/read?logname&offset=N&limit=M - получение логов 'logname'
Не обязательные параметры:
offset - начиная с,
limit - количество в ответе.
\todo Добавить настройки таймаутов, размера буфера, размера для резервирования под строку, количество потоков для http и т.п.
\todo Добавить ротацию БД \todo Добавить ротацию БД
\todo REST API: продумать команды и реализовать \todo REST API: продумать команды и реализовать
\todo Продумать поддержку websocket \todo Продумать поддержку websocket
\todo Возможно в последствии оптимизировать таблицы (нормализовать) если будет тормозить. Сейчас пока прототип.
*/ */
class LogDB: class LogDB:
public EventLoopServer public EventLoopServer
...@@ -113,7 +124,9 @@ namespace uniset ...@@ -113,7 +124,9 @@ namespace uniset
void onCheckBuffer( ev::timer& t, int revents ); void onCheckBuffer( ev::timer& t, int revents );
void addLog( Log* log, const std::string& txt ); void addLog( Log* log, const std::string& txt );
#ifndef DISABLE_REST_API #ifndef DISABLE_REST_API
void respError( Poco::Net::HTTPServerResponse& resp, Poco::Net::HTTPResponse::HTTPStatus s, const std::string& message ); Poco::JSON::Object::Ptr respError( Poco::Net::HTTPServerResponse& resp, Poco::Net::HTTPResponse::HTTPStatus s, const std::string& message );
Poco::JSON::Object::Ptr httpGetRequest( const std::string& cmd, const Poco::URI::QueryParameters& p );
Poco::JSON::Object::Ptr httpGetList( const Poco::URI::QueryParameters& p );
#endif #endif
std::string myname; std::string myname;
std::unique_ptr<SQLiteInterface> db; std::unique_ptr<SQLiteInterface> db;
......
...@@ -8,8 +8,8 @@ sqlite3 $dbname <<"_EOF_" ...@@ -8,8 +8,8 @@ sqlite3 $dbname <<"_EOF_"
PRAGMA foreign_keys=ON; PRAGMA foreign_keys=ON;
DROP TABLE IF EXISTS log; DROP TABLE IF EXISTS logs;
CREATE TABLE log ( CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
tms timestamp KEY default (strftime('%s', 'now')), tms timestamp KEY default (strftime('%s', 'now')),
usec INTEGER(5) NOT NULL, usec INTEGER(5) NOT NULL,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment