Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
U
uniset2
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
UniSet project repositories
uniset2
Commits
cc7c1c60
Commit
cc7c1c60
authored
Sep 06, 2017
by
Pavel Vainerman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
(LogDB): перевёл работу WebSocket на eventloop.
parent
5dbbca92
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
191 additions
and
163 deletions
+191
-163
LogDB.cc
extensions/LogDB/LogDB.cc
+142
-138
LogDB.h
extensions/LogDB/LogDB.h
+38
-19
websocket-test.html
extensions/LogDB/websocket-test.html
+11
-6
No files found.
extensions/LogDB/LogDB.cc
View file @
cc7c1c60
...
...
@@ -872,100 +872,24 @@ void LogDB::onWebSocketSession(Poco::Net::HTTPServerRequest& req, Poco::Net::HTT
return
;
}
auto
ws
=
newWebSocket
(
req
,
resp
,
seg
[
2
]);
auto
ws
=
newWebSocket
(
&
req
,
&
resp
,
seg
[
2
]);
if
(
!
ws
)
return
;
LogWebSocketGuard
lk
(
ws
,
this
);
char
buf
[
100
]
;
dblog3
<<
myname
<<
"(onWebSocketSession): start session for "
<<
req
.
clientAddress
().
toString
()
<<
endl
;
while
(
true
)
{
try
{
std
::
string
txt
=
ws
->
get
();
if
(
txt
.
empty
()
)
continue
;
int
flags
=
WebSocket
::
FRAME_TEXT
;
if
(
txt
==
"."
)
flags
=
WebSocket
::
FRAME_FLAG_FIN
|
WebSocket
::
FRAME_OP_PING
;
ssize_t
ret
=
ws
->
sendFrame
(
txt
.
data
(),
txt
.
size
(),
flags
);
if
(
ret
<
0
)
{
dbwarn
<<
myname
<<
"(websocket): "
<<
req
.
clientAddress
().
toString
()
<<
" write to socket error("
<<
errno
<<
"): "
<<
strerror
(
errno
)
<<
endl
;
if
(
errno
==
EPIPE
||
errno
==
EBADF
)
{
dbwarn
<<
myname
<<
"(websocket): "
<<
req
.
clientAddress
().
toString
()
<<
" write error.. terminate session.."
<<
endl
;
}
return
;
}
// проверяем соединение
// ssize_t n = ws->receiveFrame(buf, sizeof(buf), flags);
// cerr << "read from websocket: " << n << endl;
// if( (flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_CLOSE )
// {
// dbwarn << myname << "(websocket): "
// << req.clientAddress().toString()
// << " connection closed.." << endl;
// return;
// }
continue
;
}
catch
(
WebSocketException
&
exc
)
{
switch
(
exc
.
code
()
)
{
case
WebSocket
:
:
WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION
:
resp
.
set
(
"Sec-WebSocket-Version"
,
WebSocket
::
WEBSOCKET_VERSION
);
case
WebSocket
:
:
WS_ERR_NO_HANDSHAKE
:
case
WebSocket
:
:
WS_ERR_HANDSHAKE_NO_VERSION
:
case
WebSocket
:
:
WS_ERR_HANDSHAKE_NO_KEY
:
resp
.
setStatusAndReason
(
HTTPResponse
::
HTTP_BAD_REQUEST
);
resp
.
setContentLength
(
0
);
resp
.
send
();
break
;
}
}
catch
(
const
Poco
::
Net
::
NetException
&
e
)
{
dbwarn
<<
myname
<<
"(websocket):NetException: "
<<
req
.
clientAddress
().
toString
()
<<
" error: "
<<
e
.
displayText
()
<<
endl
;
}
catch
(
Poco
::
IOException
&
ex
)
{
dbwarn
<<
myname
<<
"(websocket): IOException: "
<<
req
.
clientAddress
().
toString
()
<<
" error: "
<<
ex
.
displayText
()
<<
endl
;
}
return
;
}
// т.к. вся работа происходит в eventloop
// то здесь просто ждём..
ws
->
waitCompletion
();
dblog3
<<
myname
<<
"(onWebSocketSession): finish session for "
<<
req
.
clientAddress
().
toString
()
<<
endl
;
}
// -----------------------------------------------------------------------------
std
::
shared_ptr
<
LogDB
::
LogWebSocket
>
LogDB
::
newWebSocket
(
Poco
::
Net
::
HTTPServerRequest
&
req
,
Poco
::
Net
::
HTTPServerResponse
&
resp
,
std
::
shared_ptr
<
LogDB
::
LogWebSocket
>
LogDB
::
newWebSocket
(
Poco
::
Net
::
HTTPServerRequest
*
req
,
Poco
::
Net
::
HTTPServerResponse
*
resp
,
const
std
::
string
&
logname
)
{
using
Poco
::
Net
::
WebSocket
;
...
...
@@ -986,10 +910,10 @@ std::shared_ptr<LogDB::LogWebSocket> LogDB::newWebSocket( Poco::Net::HTTPServerR
if
(
!
log
)
{
resp
.
setStatus
(
HTTPResponse
::
HTTP_BAD_REQUEST
);
resp
.
setContentType
(
"text/html"
);
resp
.
setStatusAndReason
(
HTTPResponse
::
HTTP_NOT_FOUND
);
std
::
ostream
&
err
=
resp
.
send
();
resp
->
setStatus
(
HTTPResponse
::
HTTP_BAD_REQUEST
);
resp
->
setContentType
(
"text/html"
);
resp
->
setStatusAndReason
(
HTTPResponse
::
HTTP_NOT_FOUND
);
std
::
ostream
&
err
=
resp
->
send
();
err
<<
"Not found '"
<<
logname
<<
"'"
;
err
.
flush
();
return
nullptr
;
...
...
@@ -997,8 +921,7 @@ std::shared_ptr<LogDB::LogWebSocket> LogDB::newWebSocket( Poco::Net::HTTPServerR
uniset_rwmutex_wrlock
lock
(
wsocksMutex
);
std
::
shared_ptr
<
LogWebSocket
>
ws
=
make_shared
<
LogWebSocket
>
(
req
,
resp
,
log
);
std
::
shared_ptr
<
LogWebSocket
>
ws
=
make_shared
<
LogWebSocket
>
(
req
,
resp
,
log
,
loop
);
wsocks
.
emplace_back
(
ws
);
return
ws
;
}
...
...
@@ -1007,8 +930,6 @@ void LogDB::delWebSocket( std::shared_ptr<LogWebSocket>& ws )
{
uniset_rwmutex_wrlock
lock
(
wsocksMutex
);
ws
->
term
();
// надо вызывать под mutex-ом, т.к. там идёт вызов
for
(
auto
it
=
wsocks
.
begin
();
it
!=
wsocks
.
end
();
it
++
)
{
if
(
(
*
it
).
get
()
==
ws
.
get
()
)
...
...
@@ -1035,88 +956,171 @@ void LogDB::onPingWebSockets( ev::timer& t, int revents )
s
->
ping
();
}
// -----------------------------------------------------------------------------
LogDB
::
LogWebSocket
::
LogWebSocket
(
Poco
::
Net
::
HTTPServerRequest
&
req
,
Poco
::
Net
::
HTTPServerResponse
&
resp
,
std
::
shared_ptr
<
Log
>&
_log
)
:
Poco
::
Net
::
WebSocket
(
req
,
resp
)
LogDB
::
LogWebSocket
::
LogWebSocket
(
Poco
::
Net
::
HTTPServerRequest
*
_req
,
Poco
::
Net
::
HTTPServerResponse
*
_resp
,
std
::
shared_ptr
<
Log
>&
_log
,
ev
::
dynamic_loop
&
loop
)
:
Poco
::
Net
::
WebSocket
(
*
_req
,
*
_resp
),
req
(
_req
),
resp
(
_resp
)
// log(_log)
{
setBlocking
(
false
);
con
=
_log
->
signal_on_read
().
connect
(
sigc
::
mem_fun
(
*
this
,
&
LogWebSocket
::
add
));
io
.
set
(
loop
);
io
.
set
<
LogDB
::
LogWebSocket
,
&
LogDB
::
LogWebSocket
::
event
>
(
this
);
io
.
start
();
}
// -----------------------------------------------------------------------------
LogDB
::
LogWebSocket
::~
LogWebSocket
()
{
if
(
!
cancelled
)
term
();
// удаляем всё что осталось
while
(
!
wbuf
.
empty
())
{
delete
wbuf
.
front
();
wbuf
.
pop
();
}
}
// -----------------------------------------------------------------------------
void
LogDB
::
LogWebSocket
::
event
(
ev
::
io
&
watcher
,
int
revents
)
{
if
(
EV_ERROR
&
revents
)
return
;
if
(
revents
&
EV_WRITE
)
write
(
watcher
);
}
// -----------------------------------------------------------------------------
std
::
string
LogDB
::
LogWebSocket
::
get
()
void
LogDB
::
LogWebSocket
::
ping
()
{
if
(
cancelled
)
return
""
;
return
;
wbuf
.
emplace
(
new
UTCPCore
::
Buffer
(
"."
));
io
.
set
(
ev
::
WRITE
);
}
// -----------------------------------------------------------------------------
void
LogDB
::
LogWebSocket
::
add
(
LogDB
::
Log
*
log
,
const
string
&
txt
)
{
if
(
cancelled
||
txt
.
empty
())
return
;
wbuf
.
emplace
(
new
UTCPCore
::
Buffer
(
txt
));
io
.
set
(
ev
::
WRITE
);
}
// -----------------------------------------------------------------------------
void
LogDB
::
LogWebSocket
::
write
(
ev
::
io
&
w
)
{
UTCPCore
::
Buffer
*
msg
=
0
;
if
(
wbuf
.
empty
()
)
{
io
.
set
(
EV_NONE
);
return
;
}
msg
=
wbuf
.
front
();
if
(
!
msg
)
return
;
using
Poco
::
Net
::
WebSocket
;
using
Poco
::
Net
::
WebSocketException
;
using
Poco
::
Net
::
HTTPResponse
;
using
Poco
::
Net
::
HTTPServerRequest
;
std
::
string
ret
;
int
flags
=
WebSocket
::
FRAME_TEXT
;
if
(
msg
->
len
==
1
)
// это пинг состоящий из "."
flags
=
WebSocket
::
FRAME_FLAG_FIN
|
WebSocket
::
FRAME_OP_PING
;
try
{
uniset_rwmutex_wrlock
lk
(
mqmut
);
ssize_t
ret
=
sendFrame
(
msg
->
dpos
(),
msg
->
nbytes
(),
flags
);
if
(
!
mqueue
.
empty
()
)
if
(
ret
<
0
)
{
ret
=
mqueue
.
front
();
mqueue
.
pop
();
return
ret
;
cerr
<<
"(websocket): "
<<
req
->
clientAddress
().
toString
()
<<
" write to socket error("
<<
errno
<<
"): "
<<
strerror
(
errno
)
<<
endl
;
if
(
errno
==
EPIPE
||
errno
==
EBADF
)
{
cerr
<<
"(websocket): "
<<
req
->
clientAddress
().
toString
()
<<
" write error.. terminate session.."
<<
endl
;
term
();
}
return
;
}
}
{
std
::
unique_lock
<
std
::
mutex
>
lk
(
mut
);
event
.
wait
(
lk
);
}
msg
->
pos
+=
ret
;
if
(
cancelled
)
return
""
;
if
(
msg
->
nbytes
()
==
0
)
{
wbuf
.
pop
();
delete
msg
;
}
uniset_rwmutex_wrlock
lk
(
mqmut
);
if
(
!
wbuf
.
empty
()
)
io
.
set
(
EV_WRITE
);
if
(
!
mqueue
.
empty
()
)
return
;
}
catch
(
WebSocketException
&
exc
)
{
ret
=
mqueue
.
front
();
mqueue
.
pop
();
switch
(
exc
.
code
()
)
{
case
WebSocket
:
:
WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION
:
resp
->
set
(
"Sec-WebSocket-Version"
,
WebSocket
::
WEBSOCKET_VERSION
);
case
WebSocket
:
:
WS_ERR_NO_HANDSHAKE
:
case
WebSocket
:
:
WS_ERR_HANDSHAKE_NO_VERSION
:
case
WebSocket
:
:
WS_ERR_HANDSHAKE_NO_KEY
:
resp
->
setStatusAndReason
(
HTTPResponse
::
HTTP_BAD_REQUEST
);
resp
->
setContentLength
(
0
);
resp
->
send
();
break
;
}
}
return
ret
;
}
// -----------------------------------------------------------------------------
void
LogDB
::
LogWebSocket
::
ping
()
{
if
(
cancelled
)
return
;
catch
(
const
Poco
::
Net
::
NetException
&
e
)
{
uniset_rwmutex_wrlock
lk
(
mqmut
);
// в качестве ping-а просто добавляем фейковое сообщение '.'
mqueue
.
push
(
"."
);
cerr
<<
"(websocket):NetException: "
<<
req
->
clientAddress
().
toString
()
<<
" error: "
<<
e
.
displayText
()
<<
endl
;
}
event
.
notify_all
();
catch
(
Poco
::
IOException
&
ex
)
{
cerr
<<
"(websocket): IOException: "
<<
req
->
clientAddress
().
toString
()
<<
" error: "
<<
ex
.
displayText
()
<<
endl
;
}
term
();
}
// -----------------------------------------------------------------------------
void
LogDB
::
LogWebSocket
::
add
(
LogDB
::
Log
*
log
,
const
string
&
txt
)
void
LogDB
::
LogWebSocket
::
term
(
)
{
if
(
cancelled
)
return
;
{
uniset_rwmutex_wrlock
lk
(
mqmut
);
mqueue
.
push
(
txt
);
}
event
.
notify_all
();
cancelled
=
true
;
con
.
disconnect
();
io
.
stop
();
finish
.
notify_all
();
}
// -----------------------------------------------------------------------------
void
LogDB
::
LogWebSocket
::
term
()
void
LogDB
::
LogWebSocket
::
waitCompletion
()
{
cancelled
=
true
;
con
.
disconnect
();
// вот тут не безпасный вызов, т.е. sigc там в других потоках используется
event
.
notify_all
(
);
std
::
unique_lock
<
std
::
mutex
>
lk
(
finishmut
)
;
while
(
!
cancelled
)
finish
.
wait
(
lk
);
}
// -----------------------------------------------------------------------------
#endif
...
...
extensions/LogDB/LogDB.h
View file @
cc7c1c60
...
...
@@ -62,7 +62,7 @@ namespace uniset
Ожидается что контролируемых логов будет не очень много (максимум несколько десятков)
и каждый лог будет генерировать не более 2-5 мегабайт записей. Поэтому sqlite должно хватить.
\section sec_LogDB_Conf Конфигурирвание LogDB
\section sec_LogDB_Conf Конфигурир
о
вание LogDB
<LogDB name="LogDB" ...>
<logserver name="" ip=".." port=".." cmd=".." description=".."/>
...
...
@@ -92,13 +92,28 @@ namespace uniset
/count?logname - Получить текущее количество записей
\section sec_LogDB_WEBSOCK LogDB Поддержка web socket
В LogDB встроена возможность realtime чтения логов, через websocket.
Подключение (создание) сокета происходит по адресу
\code
ws://host:port/logdb/ws/logname
\endcode
Где \a logname - это имя логсервера от которого мы хотим получать логи (см. \ref sec_LogDB_Conf).
\section sec_LogDB_DETAIL LogDB Технические детали
Вся релизация построена на "однопоточном" eventloop. В нём происходит,
чтение данных от логсерверов, посылка сообщений в websockets.
При этом обработка запросов REST API реалиуется отдельными потоками контролируемыми libpoco.
\todo Добавить настройки таймаутов, размера буфера, размера для резервирования под строку, количество потоков для http и т.п.
\todo Добавить ротацию БД
\todo Сделать настройку, для формата даты и времени при выгрузке из БД (при формировании json).
\todo Возможно в /logs стоит в ответе сразу возвращать и общее количество в БД (это один лишний запрос, каждый раз).
\todo Возможно в последствии оптимизировать таблицы (нормализовать) если будет тормозить. Сейчас пока прототип.
\todo Пока не очень эффективная работа с датой и временем (заодно подумать всё-таки в чём хранить)
\todo WebSocket: Сделать запись через UTCPCore::Buffer, чтобы не терять данные при записи в сокет
\todo WebSocket: Доделать ограничение на размер буфера для каждого сокета
\todo WebSocket: доделать настройку всевозможных timeout-ов
\todo WebSocket: доделать проверку соединения
...
...
@@ -155,7 +170,7 @@ namespace uniset
// XX m - минут, h-часов, d-дней, M - месяцев
static
std
::
string
qLast
(
const
std
::
string
&
p
);
std
::
shared_ptr
<
LogWebSocket
>
newWebSocket
(
Poco
::
Net
::
HTTPServerRequest
&
req
,
Poco
::
Net
::
HTTPServerResponse
&
resp
,
const
std
::
string
&
logname
);
std
::
shared_ptr
<
LogWebSocket
>
newWebSocket
(
Poco
::
Net
::
HTTPServerRequest
*
req
,
Poco
::
Net
::
HTTPServerResponse
*
resp
,
const
std
::
string
&
logname
);
void
delWebSocket
(
std
::
shared_ptr
<
LogWebSocket
>&
ws
);
void
onPingWebSockets
(
ev
::
timer
&
t
,
int
revents
);
#endif
...
...
@@ -229,37 +244,41 @@ namespace uniset
public
Poco
::
Net
::
WebSocket
{
public
:
LogWebSocket
(
Poco
::
Net
::
HTTPServerRequest
&
req
,
Poco
::
Net
::
HTTPServerResponse
&
resp
,
std
::
shared_ptr
<
Log
>&
log
);
LogWebSocket
(
Poco
::
Net
::
HTTPServerRequest
*
req
,
Poco
::
Net
::
HTTPServerResponse
*
resp
,
std
::
shared_ptr
<
Log
>&
log
,
ev
::
dynamic_loop
&
loop
);
virtual
~
LogWebSocket
();
// получение очередного сообщения
// (с засыпанием в случае отсутствия сообщения в очереди)
std
::
string
get
();
void
event
(
ev
::
io
&
watcher
,
int
revents
);
// вызывается из потока eventloop..
void
ping
();
// вызывается из потока eventloop..
void
add
(
Log
*
log
,
const
std
::
string
&
txt
);
// надо вызывать только из потока eventloop
// т.к. идёт обращение sigc::connection
void
term
();
void
waitCompletion
();
protected
:
std
::
mutex
mut
;
std
::
condition_variable
event
;
uniset
::
uniset_rwmutex
mqmut
;
std
::
queue
<
std
::
string
>
mqueue
;
void
write
(
ev
::
io
&
w
);
ev
::
io
io
;
std
::
mutex
finishmut
;
std
::
condition_variable
finish
;
std
::
atomic_bool
cancelled
=
{
false
};
sigc
::
connection
con
;
// std::shared_ptr<Log> log;
sigc
::
connection
con
;
// подписка на появление логов..
Poco
::
Net
::
HTTPServerRequest
*
req
;
Poco
::
Net
::
HTTPServerResponse
*
resp
;
// очередь данных на посылку..
std
::
queue
<
UTCPCore
::
Buffer
*>
wbuf
;
};
class
LogWebSocketGuard
...
...
extensions/LogDB/websocket-test.html
View file @
cc7c1c60
<html>
<!--
Base on example from
https://github.com/pocoproject/poco/blob/develop/Net/samples/WebSocketServer/src/WebSocketServer.cpp
-->
<head>
<title>
WebSocketServer
</title>
<script
type=
"text/javascript"
>
...
...
@@ -7,10 +11,10 @@
if
(
"WebSocket"
in
window
)
{
var
ws
=
new
WebSocket
(
"ws://localhost:8888/logdb/ws/logserver1"
);
ws
.
onopen
=
function
()
{
ws
.
send
(
"Hello, world!"
);
};
//
ws.onopen = function()
//
{
//
ws.send("Hello, world!");
//
};
ws
.
onmessage
=
ws
.
onmessage
=
function
(
evt
)
{
...
...
@@ -33,8 +37,8 @@
</script>
</head>
<body>
<h1>
WebSocket Server
</h1>
<p><a
href=
"javascript:WebSocketTest()"
>
Run WebSocket
</a></p>
<h1>
Test uniset logdb websocket
</h1>
<p><a
href=
"javascript:WebSocketTest()"
>
Connect to 'logserver1'
</a></p>
<div
id=
"logs"
></div>
</body>
</html>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment