Skip to content
This repository has been archived by the owner on Apr 22, 2024. It is now read-only.

Socketio #436

Merged
merged 8 commits into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ Removed

Fixed
=====
- Fix start debug mode using the command kytos -D
- Web interface:
- Fixed memory and CPU usage
- Fix starting debug mode using the command kytos -D

Security
========
Expand Down
2 changes: 1 addition & 1 deletion etc/kytos/logging.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ level: INFO
handlers: syslog,console

[logger_api_server]
level: INFO
level: WARNING
qualname: werkzeug
handlers:

Expand Down
18 changes: 7 additions & 11 deletions kytos/core/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from urllib.request import urlopen

from flask import Flask, request, send_from_directory
from flask_socketio import SocketIO
from flask_socketio import SocketIO, join_room, leave_room


class APIServer:
Expand All @@ -30,6 +30,12 @@ def __init__(self, app_name, listen='0.0.0.0', port=8181):

self.app = Flask(app_name, root_path=self.flask_dir)
self.server = SocketIO(self.app, async_mode='threading')
self._enable_websocket_rooms()

def _enable_websocket_rooms(self):
socket = self.server
socket.on_event('join', join_room)
socket.on_event('leave', leave_room)

def run(self):
"""Method used to run the APIServer."""
Expand All @@ -55,16 +61,6 @@ def register_rest_endpoint(self, url, function, methods):
self.app.add_url_rule(new_endpoint_url, function.__name__,
function, methods=methods)

def register_websockets(self, websockets):
"""Method used to register all channels from websockets given."""
for websocket in websockets.values():
for event, function, namespace in websocket.events:
self.register_websocket(event, function, namespace)

def register_websocket(self, name, function, namespace='/'):
"""Method used to register websocket channel."""
self.server.on_event(name, function, namespace)

def register_web_ui(self):
"""Method used to register routes to the admin-ui homepage."""
self.app.add_url_rule('/', self.web_ui.__name__, self.web_ui)
Expand Down
14 changes: 1 addition & 13 deletions kytos/core/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
from kytos.core.napps.manager import NAppsManager
from kytos.core.switch import Switch
from kytos.core.tcp_server import KytosRequestHandler, KytosServer
from kytos.core.websocket import LogWebSocket

__all__ = ('Controller',)

Expand Down Expand Up @@ -94,9 +93,6 @@ def __init__(self, options=None):
#: datetime.datetime: Time when the controller finished starting.
self.started_at = None

#: dict: Hash with all websockets used by Kytos.
self.websockets = {}

#: logging.Logger: Logger instance used by Kytos.
self.log = None

Expand All @@ -111,17 +107,10 @@ def __init__(self, options=None):
#: from napps.<username>.<napp_name> import ?....
sys.path.append(os.path.join(self.options.napps, os.pardir))

def register_websockets(self):
"""Method used to register all websockets."""
log = LogWebSocket()
self.websockets['log'] = log
LogManager.add_stream_handler(log.stream)

self.api_server.register_websockets(self.websockets)

def enable_logs(self):
"""Method used to register kytos log and enable the logs."""
LogManager.load_config_file(self.options.logging, self.options.debug)
LogManager.enable_websocket(self.api_server.server)
self.log = logging.getLogger(__name__)

def start(self, restart=False):
Expand Down Expand Up @@ -181,7 +170,6 @@ def start_controller(self):
Starts a thread for each buffer handler.
Load the installed apps.
"""
self.register_websockets()
self.log.info("Starting Kytos - Kytos Controller")
self.server = KytosServer((self.options.listen,
int(self.options.port)),
Expand Down
15 changes: 10 additions & 5 deletions kytos/core/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
import inspect
import re
from configparser import RawConfigParser
from logging import Formatter, StreamHandler, config, getLogger
from logging import Formatter, config, getLogger
from pathlib import Path

from kytos.core.websocket import WebSocketHandler

__all__ = ('LogManager', 'NAppLog')
log = getLogger(__name__)

Expand Down Expand Up @@ -64,13 +66,16 @@ def _catch_config_file_exception(cls, config_file):
'logging configuration.', config_file)

@classmethod
def add_stream_handler(cls, stream):
"""Output all logs to the given stream.
def enable_websocket(cls, socket):
"""Output logs to a web socket.

Args:
stream: Object that supports ``write()`` and ``flush()``.
socket: socketio's socket.

Returns:
logging.StreanHandler: Handler with the socket as stream.
"""
handler = StreamHandler(stream)
handler = WebSocketHandler.get_handler(socket)
cls.add_handler(handler)
return handler

Expand Down
90 changes: 39 additions & 51 deletions kytos/core/websocket.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,49 @@
"""Module to abstract a WebSockets."""
"""WebSocket abstraction."""
import logging
from io import StringIO

from flask_socketio import emit
__all__ = ('WebSocketHandler')


class LogWebSocket:
"""Class used to send logs using socketio."""
class WebSocketHandler:
"""Log handler that logs to web socket."""

def __init__(self, **kwargs):
"""The constructor of LogWebSocket receive the parameters below.
@classmethod
def get_handler(cls, socket):
"""Output logs to a web socket, filtering unwanted messages.

Args:
stream(StringIO()): buffer used to store the logs
buffer_max_size(int): Max size used by stream.
socket: socketio socket.
stream: Object that supports ``write()`` and ``flush()``.
"""
self.stream = kwargs.get('stream', StringIO())
self.buffer_max_size = kwargs.get('buffer_max_size', 100)
self.buff = []
self._set_log_level(logging.ERROR)
stream = WebSocketStream(socket)
handler = logging.StreamHandler(stream)
handler.addFilter(cls._filter_web_requests)
return handler

@staticmethod
def _set_log_level(level):
for name in 'engineio', 'socketio':
log = logging.getLogger(name)
log.setLevel(level)

@property
def events(self):
"""Method used to return all channels used by LogWebsocket instance."""
return [('show logs', self.handle_messages, '/logs')]

def update_buffer(self):
"""Method used to update the buffer from stream of logs."""
self.stream.seek(0)
msg = self.stream.read().split('\n')[:-1]

if not msg:
return

self.buff.extend(msg)
self.stream.truncate(0)

if len(self.buff) >= self.buffer_max_size:
new_size = self.buffer_max_size/2
self.buff = self.buff[new_size:]

def handle_messages(self, json):
"""Method used to send logs to clients."""
current_line = json.get('current_line', 0)
max_lines = json.get('max_lines', 50)
self.update_buffer()

# Don't send more than max_lines. If remaining lines exceed max_lines,
# we send the latest messages
total_new_lines = len(self.buff) - current_line
new_lines = min(total_new_lines, max_lines)
result = {"buff": self.buff[-new_lines:],
"last_line": len(self.buff)}
emit('show logs', result)
def _filter_web_requests(record):
"""Only allow web server messages with level higher than info.

Do not print web requests (INFO level) to avoid infinit loop when
printing the logs in the web interface with long-polling mode.
"""
return record.name != 'werkzeug' or record.levelno > logging.INFO
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please open an issue in order to us use a proper websocket listen. Maybe with wampp



class WebSocketStream:
"""Make loggers write to web socket."""

def __init__(self, socketio):
"""Receive the socket to write to."""
self._io = socketio
self._content = ''

def write(self, content):
"""Store a new line."""
self._content += content

def flush(self):
"""Send lines and reset the content."""
lines = self._content.split('\n')[:-1]
self._content = ''
self._io.emit('show logs', lines, room='log')
6 changes: 3 additions & 3 deletions kytos/web-ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ <h4 class="modal-title text-center" id="saveLayoutLabel">Save this layout</h4>
</a>
</li>
<li class="nav-middle-spacer">&nbsp;</li>
<li class="nav-kytos-api-status">
<li class="nav-kytos-api-status">
<i class="fa fa-power-off api-status" aria-hidden="true"></i>
<span class='hidden-sm hidden-xs'>Kytos </span>API
</li>
Expand Down Expand Up @@ -269,7 +269,7 @@ <h1>Other Settings</h1>
</div>
<div class="checkbox" data-label-width="0" data-size="mini">
<label for="enable_log">
<input id='enable_log' type="checkbox" data-label-width="0" data-size="mini"> Enable Logs
<input id='enable_log' type="checkbox" data-label-width="0" data-size="mini" onchange="toggle_log(this.checked)"> Enable Logs
</label>
</div>
</div>
Expand All @@ -294,7 +294,7 @@ <h1>Other Settings</h1>
<script src="static/js/vendors/jquery-ui.min.js"></script>
<script src="static/js/vendors/jquery.mCustomScrollbar.js"></script>
<script src="static/js/vendors/mapbox-gl.js"></script>
<script src="static/js/vendors/socket.io.min.js"></script>
<script src="static/js/vendors/socket.io.js"></script>
<script src="static/js/kytos-settings.js"></script>
<script src="static/js/kytos-control-panel.js"></script>
<script src="static/js/kytos-helper.js"></script>
Expand Down
Loading